style: add worker ux
This commit is contained in:
@@ -17,6 +17,11 @@ export const regionInstanceTypeListAtom = atom<
|
|||||||
{
|
{
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
description: string;
|
||||||
|
specInfo: Record<string, any>;
|
||||||
|
vendor: string;
|
||||||
|
available: boolean;
|
||||||
|
regions: string[];
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
@@ -24,6 +29,11 @@ export const regionOSImageListAtom = atom<
|
|||||||
{
|
{
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
vendor: string;
|
||||||
|
specInfo: Record<string, any>;
|
||||||
|
regions: string[];
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
@@ -42,3 +52,5 @@ export const allRegionInstanceTypeListAtom = atom<
|
|||||||
regions: string[];
|
regions: string[];
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
|
export const fromClusterCreationAtom = atom(false);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
|
|||||||
legendOptions,
|
legendOptions,
|
||||||
gridOptions,
|
gridOptions,
|
||||||
titleOptions,
|
titleOptions,
|
||||||
showArea
|
showArea = 0.25
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
grid,
|
grid,
|
||||||
@@ -87,7 +87,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
|
|||||||
const data = _.map(seriesData, (item: any) => {
|
const data = _.map(seriesData, (item: any) => {
|
||||||
const colors = genColors({
|
const colors = genColors({
|
||||||
color: item.color,
|
color: item.color,
|
||||||
alpha1: 0.5,
|
alpha1: 0.25,
|
||||||
alpha2: 0.1
|
alpha2: 0.1
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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_5jrvj6qs39.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_84hy7h371ty.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { AutoComplete, Form, Spin } from 'antd';
|
import { AutoComplete, Form, Spin } from 'antd';
|
||||||
import type { AutoCompleteProps } from 'antd/lib';
|
import type { AutoCompleteProps } from 'antd/lib';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { SealFormItemProps } from './types';
|
import { SealFormItemProps } from './types';
|
||||||
import Wrapper from './wrapper';
|
import Wrapper from './wrapper';
|
||||||
|
import AutoCompleteLabel from './wrapper/auto-complete-label';
|
||||||
import SelectWrapper from './wrapper/select';
|
import SelectWrapper from './wrapper/select';
|
||||||
|
|
||||||
const SealAutoComplete: React.FC<
|
const SealAutoComplete: React.FC<
|
||||||
@@ -24,6 +26,7 @@ const SealAutoComplete: React.FC<
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const [isFocus, setIsFocus] = useState(false);
|
const [isFocus, setIsFocus] = useState(false);
|
||||||
|
const [_isFocus_, _setIsFocus_] = useState(false);
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
let status = '';
|
let status = '';
|
||||||
if (isInFormItems) {
|
if (isInFormItems) {
|
||||||
@@ -41,6 +44,7 @@ const SealAutoComplete: React.FC<
|
|||||||
if (!props.disabled && !isFocus) {
|
if (!props.disabled && !isFocus) {
|
||||||
inputRef.current?.focus?.();
|
inputRef.current?.focus?.();
|
||||||
setIsFocus(true);
|
setIsFocus(true);
|
||||||
|
_setIsFocus_(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,17 +53,20 @@ const SealAutoComplete: React.FC<
|
|||||||
if (trim) {
|
if (trim) {
|
||||||
value = value?.trim?.();
|
value = value?.trim?.();
|
||||||
}
|
}
|
||||||
|
_setIsFocus_(false);
|
||||||
props.onChange?.(value, option);
|
props.onChange?.(value, option);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnFocus = (e: any) => {
|
const handleOnFocus = (e: any) => {
|
||||||
setIsFocus(true);
|
setIsFocus(true);
|
||||||
|
_setIsFocus_(true);
|
||||||
props.onFocus?.(e);
|
props.onFocus?.(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnBlur = (e: any) => {
|
const handleOnBlur = (e: any) => {
|
||||||
if (!props.value) {
|
if (!props.value) {
|
||||||
setIsFocus(false);
|
setIsFocus(false);
|
||||||
|
_setIsFocus_(false);
|
||||||
}
|
}
|
||||||
e.target.value = e.target.value?.trim?.();
|
e.target.value = e.target.value?.trim?.();
|
||||||
props.onBlur?.(e);
|
props.onBlur?.(e);
|
||||||
@@ -79,6 +86,39 @@ const SealAutoComplete: React.FC<
|
|||||||
return addAfter;
|
return addAfter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderAutoCompleteLabel = () => {
|
||||||
|
console.log(
|
||||||
|
'renderAutoCompleteLabel===',
|
||||||
|
props.value,
|
||||||
|
props.showSearch,
|
||||||
|
_isFocus_,
|
||||||
|
isFocus
|
||||||
|
);
|
||||||
|
if (!props.showSearch || _isFocus_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let selectItem: {
|
||||||
|
label: React.ReactNode;
|
||||||
|
value: string | number;
|
||||||
|
} = props.options?.find((item: any) => item.value === props.value) as {
|
||||||
|
label: React.ReactNode;
|
||||||
|
value: string | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectItem) {
|
||||||
|
selectItem = { label: props.value, value: props.value };
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AutoCompleteLabel
|
||||||
|
className={classNames({
|
||||||
|
disabled: props.disabled
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{props.labelRender ? props.labelRender(selectItem) : selectItem.label}
|
||||||
|
</AutoCompleteLabel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectWrapper style={style}>
|
<SelectWrapper style={style}>
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { BGCOLOR, INPUT_INNER_PADDING } from '../config';
|
||||||
|
|
||||||
|
const AutoCompleteLabel = styled.span`
|
||||||
|
position: absolute;
|
||||||
|
left: ${INPUT_INNER_PADDING}px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
top: 26px;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
background-color: var(--ant-color-bg-container);
|
||||||
|
z-index: 10;
|
||||||
|
&.disabled {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
&.isfoucs-has-value {
|
||||||
|
// display: none;
|
||||||
|
z-index: -1;
|
||||||
|
// top: 9px;
|
||||||
|
// font-size: 12px;
|
||||||
|
// color: var(--ant-color-text);
|
||||||
|
// background-color: ${BGCOLOR};
|
||||||
|
// padding: 0 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default AutoCompleteLabel;
|
||||||
@@ -103,7 +103,7 @@ const SelectWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
&.ant-select-auto-complete {
|
&.ant-select-auto-complete {
|
||||||
.ant-select-selection-search {
|
.ant-select-selection-search {
|
||||||
inset-inline-start: ${INPUT_INNER_PADDING}px;
|
padding-inline-start: ${INPUT_INNER_PADDING}px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.ant-select-multiple.ant-cascader .ant-select-selection-search {
|
&.ant-select-multiple.ant-cascader .ant-select-selection-search {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ interface CardProps {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div.attrs({
|
||||||
|
className: 'template-card-wrapper'
|
||||||
|
})`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 22px;
|
padding: 22px;
|
||||||
@@ -43,37 +45,44 @@ const CardWrapper = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: default;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: var(--ant-color-fill-quaternary);
|
border-style: dashed;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CardContent = styled.div`
|
const CardContent = styled.div.attrs({
|
||||||
|
className: 'template-card-content'
|
||||||
|
})`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: var(--ant-color-text-tertiary);
|
color: var(--ant-color-text-tertiary);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Inner = styled.div`
|
const Inner = styled.div.attrs({
|
||||||
|
className: 'template-card-inner'
|
||||||
|
})`
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Icon = styled.div`
|
const Icon = styled.div.attrs({
|
||||||
|
className: 'template-card-icon'
|
||||||
|
})`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div.attrs({
|
||||||
|
className: 'template-card-header'
|
||||||
|
})`
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -110,7 +119,7 @@ const Card: React.FC<CardProps> = (props) => {
|
|||||||
{icon && <Icon>{icon}</Icon>}
|
{icon && <Icon>{icon}</Icon>}
|
||||||
<Inner>
|
<Inner>
|
||||||
{header && <Header>{header}</Header>}
|
{header && <Header>{header}</Header>}
|
||||||
<CardContent>{children}</CardContent>
|
{children && <CardContent>{children}</CardContent>}
|
||||||
{footer}
|
{footer}
|
||||||
</Inner>
|
</Inner>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
|
|||||||
+2
-1
@@ -3,7 +3,8 @@ import { PageActionType, StatusType } from './types';
|
|||||||
export const PageAction: Record<string, PageActionType> = {
|
export const PageAction: Record<string, PageActionType> = {
|
||||||
CREATE: 'create',
|
CREATE: 'create',
|
||||||
UPDATE: 'update',
|
UPDATE: 'update',
|
||||||
VIEW: 'view'
|
VIEW: 'view',
|
||||||
|
EDIT: 'edit'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StatusColorMap: Record<
|
export const StatusColorMap: Record<
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ export interface DropDownItem {
|
|||||||
iconfont?: boolean;
|
iconfont?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageActionType = 'create' | 'update' | 'view';
|
export type PageActionType = 'create' | 'update' | 'view' | 'edit';
|
||||||
|
|
||||||
export type StatusType =
|
export type StatusType =
|
||||||
| 'error'
|
| 'error'
|
||||||
|
|||||||
@@ -37,5 +37,9 @@ export default {
|
|||||||
'clusters.create.noInstanceTypes': 'No instance types available',
|
'clusters.create.noInstanceTypes': 'No instance types available',
|
||||||
'clusters.create.noRegions': 'No regions available',
|
'clusters.create.noRegions': 'No regions available',
|
||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'Number of workers created simultaneously in the Worker pool'
|
'Number of workers created simultaneously in the Worker pool',
|
||||||
|
'clusters.create.addworker.tips':
|
||||||
|
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
|
'clusters.create.addCommand.tips':
|
||||||
|
' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ export default {
|
|||||||
'clusters.create.noInstanceTypes': 'No instance types available',
|
'clusters.create.noInstanceTypes': 'No instance types available',
|
||||||
'clusters.create.noRegions': 'No regions available',
|
'clusters.create.noRegions': 'No regions available',
|
||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'Number of workers created simultaneously in the Worker pool'
|
'Number of workers created simultaneously in the Worker pool',
|
||||||
|
'clusters.create.addworker.tips':
|
||||||
|
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
|
'clusters.create.addCommand.tips':
|
||||||
|
' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -78,6 +82,8 @@ export default {
|
|||||||
// 35. 'clusters.create.noImages': 'No images available',
|
// 35. 'clusters.create.noImages': 'No images available',
|
||||||
// 36. 'clusters.create.noInstanceTypes': 'No instance types available',
|
// 36. 'clusters.create.noInstanceTypes': 'No instance types available',
|
||||||
// 37. 'clusters.create.noRegions': 'No regions available',
|
// 37. 'clusters.create.noRegions': 'No regions available',
|
||||||
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool'
|
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool',
|
||||||
|
// 39. 'clusters.create.addworker.tips': ' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
|
// 40. 'clusters.create.addCommand.tips': ' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ export default {
|
|||||||
'clusters.create.noInstanceTypes': 'No instance types available',
|
'clusters.create.noInstanceTypes': 'No instance types available',
|
||||||
'clusters.create.noRegions': 'No regions available',
|
'clusters.create.noRegions': 'No regions available',
|
||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'Number of workers created simultaneously in the Worker pool'
|
'Number of workers created simultaneously in the Worker pool',
|
||||||
|
'clusters.create.addworker.tips':
|
||||||
|
' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
|
'clusters.create.addCommand.tips':
|
||||||
|
' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -79,5 +83,7 @@ export default {
|
|||||||
// 36. 'clusters.create.noInstanceTypes': 'No instance types available',
|
// 36. 'clusters.create.noInstanceTypes': 'No instance types available',
|
||||||
// 37. 'clusters.create.noRegions': 'No regions available',
|
// 37. 'clusters.create.noRegions': 'No regions available',
|
||||||
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool'
|
// 38. 'clusters.workerpool.batchSize.desc': 'Number of workers created simultaneously in the Worker pool'
|
||||||
|
// 39. 'clusters.create.addworker.tips': ' Please make sure the prerequisites for <a href={link} target="_blank">{label}</a> are met before executing the following command.',
|
||||||
|
// 40. 'clusters.create.addCommand.tips': ' On the Worker that needs to be added, run the following command to join it to the cluster.'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -33,8 +33,12 @@ export default {
|
|||||||
'clusters.create.execCommand': '执行命令',
|
'clusters.create.execCommand': '执行命令',
|
||||||
'clusters.create.supportedGpu': '支持的 GPU',
|
'clusters.create.supportedGpu': '支持的 GPU',
|
||||||
'clusters.create.skipfornow': '暂时跳过',
|
'clusters.create.skipfornow': '暂时跳过',
|
||||||
'clusters.create.noImages': '没有可用的镜像',
|
'clusters.create.noImages': '无可用的镜像',
|
||||||
'clusters.create.noInstanceTypes': '没有可用的实例类型',
|
'clusters.create.noInstanceTypes': '无可用的实例类型',
|
||||||
'clusters.create.noRegions': '没有可用的区域',
|
'clusters.create.noRegions': '无可用的区域',
|
||||||
'clusters.workerpool.batchSize.desc': 'Worker池中同时创建的worker数量'
|
'clusters.workerpool.batchSize.desc': 'Worker 池中同时创建的 worker 数量',
|
||||||
|
'clusters.create.addworker.tips':
|
||||||
|
'在执行以下命令之前,请确保已满足 <a href={link} target="_blank">{label}</a> 的先决条件。',
|
||||||
|
'clusters.create.addCommand.tips':
|
||||||
|
' 在需要添加的 Worker 上运行以下命令,将其加入到集群中'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ const ClusterCreate = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectProvider = (value: ProviderType) => {
|
const handleSelectProvider = (value: string) => {
|
||||||
if (value === extraData.provider) {
|
if (value === extraData.provider) {
|
||||||
onNext();
|
onNext();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const AddWorkerCommand: React.FC<ViewModalProps> = ({ registrationInfo }) => {
|
|||||||
return (
|
return (
|
||||||
<HighlightCode
|
<HighlightCode
|
||||||
theme="dark"
|
theme="dark"
|
||||||
code={code.replace(/\\/g, '')}
|
code={code}
|
||||||
copyValue={code}
|
copyValue={code}
|
||||||
lang="bash"
|
lang="bash"
|
||||||
></HighlightCode>
|
></HighlightCode>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { BulbOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Alert } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ProviderType, ProviderValueMap } from '../config';
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
@@ -9,7 +11,42 @@ import SupportedHardware from './support-hardware';
|
|||||||
const Title = styled.div`
|
const Title = styled.div`
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Line = styled.div`
|
||||||
|
position: relative;
|
||||||
|
margin: 24px 0;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid var(--ant-color-border);
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -9px;
|
||||||
|
left: 24px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
background-color: var(--ant-color-bg-container);
|
||||||
|
border-top: 1px solid var(--ant-color-border);
|
||||||
|
border-left: 1px solid var(--ant-color-border);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
.command-info {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
// font-weight: 500;
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
@@ -26,19 +63,57 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
|
|||||||
registrationInfo
|
registrationInfo
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [currentProvider, setCurrentProvider] = React.useState<string>('cuda');
|
||||||
|
const [workerCommand, setWorkerCommand] = React.useState<Record<string, any>>(
|
||||||
|
{
|
||||||
|
label: 'NVIDIA CUDA',
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#nvidia-cuda'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectProvider = (provider: string, item: any) => {
|
||||||
|
setCurrentProvider(provider);
|
||||||
|
setWorkerCommand(item);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Container>
|
||||||
<Title>{intl.formatMessage({ id: 'clusters.create.execCommand' })}</Title>
|
<Title>
|
||||||
|
{intl.formatMessage({ id: 'clusters.create.supportedGpu' })}
|
||||||
|
</Title>
|
||||||
|
<SupportedHardware
|
||||||
|
onSelect={handleSelectProvider}
|
||||||
|
currentProvider={currentProvider}
|
||||||
|
/>
|
||||||
|
{provider === ProviderValueMap.Custom && (
|
||||||
|
<Content>
|
||||||
|
<Line></Line>
|
||||||
|
<Alert
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
icon={<BulbOutlined />}
|
||||||
|
message={
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: intl.formatMessage(
|
||||||
|
{ id: 'clusters.create.addworker.tips' },
|
||||||
|
{ label: workerCommand.label, link: workerCommand.link }
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
}
|
||||||
|
></Alert>
|
||||||
|
</Content>
|
||||||
|
)}
|
||||||
|
<div className="command-info">
|
||||||
|
{intl.formatMessage({ id: 'clusters.create.addCommand.tips' })}
|
||||||
|
</div>
|
||||||
{provider === ProviderValueMap.Kubernetes ? (
|
{provider === ProviderValueMap.Kubernetes ? (
|
||||||
<RegisterClusterInner registrationInfo={registrationInfo} />
|
<RegisterClusterInner registrationInfo={registrationInfo} />
|
||||||
) : (
|
) : (
|
||||||
<AddWorkerCommand registrationInfo={registrationInfo} />
|
<AddWorkerCommand registrationInfo={registrationInfo} />
|
||||||
)}
|
)}
|
||||||
<Title style={{ marginTop: 32 }}>
|
</Container>
|
||||||
{intl.formatMessage({ id: 'clusters.create.supportedGpu' })}
|
|
||||||
</Title>
|
|
||||||
<SupportedHardware />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { fromClusterCreationAtom } from '@/atoms/clusters';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { Link, useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -14,7 +16,7 @@ import { useProviderRegions } from '../hooks/use-provider-regions';
|
|||||||
type OptionData = {
|
type OptionData = {
|
||||||
label: string;
|
label: string;
|
||||||
datacenter: string;
|
datacenter: string;
|
||||||
value: string;
|
value: string | number;
|
||||||
icon: string;
|
icon: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,14 +72,24 @@ const NotFoundContent: React.FC<{ loading: boolean }> = ({ loading }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionRender = (
|
const NotFoundCredentialContent: React.FC = () => {
|
||||||
option: Global.BaseOption<
|
const [, setFromClusterCreation] = useAtom(fromClusterCreationAtom);
|
||||||
number,
|
const intl = useIntl();
|
||||||
{
|
const handleOnClick = () => {
|
||||||
data: OptionData;
|
console.log('click add credential');
|
||||||
}
|
setFromClusterCreation(true);
|
||||||
>
|
};
|
||||||
): React.ReactNode => {
|
|
||||||
|
return (
|
||||||
|
<NoContent>
|
||||||
|
<Link to={'/cluster-management/credentials'} onClick={handleOnClick}>
|
||||||
|
{intl.formatMessage({ id: 'clusters.button.addCredential' })}
|
||||||
|
</Link>
|
||||||
|
</NoContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionRender = (option: any): React.ReactNode => {
|
||||||
const { value } = option;
|
const { value } = option;
|
||||||
const data = option.data!;
|
const data = option.data!;
|
||||||
|
|
||||||
@@ -127,8 +139,8 @@ const CloudProvider: React.FC<CloudProviderProps> = (props) => {
|
|||||||
}, [credentialID]);
|
}, [credentialID]);
|
||||||
|
|
||||||
const labelRender = (props: {
|
const labelRender = (props: {
|
||||||
label: string;
|
label: React.ReactNode;
|
||||||
value: string;
|
value: string | number;
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const data = regions.find((item) => item.value === props.value);
|
const data = regions.find((item) => item.value === props.value);
|
||||||
if (!data) return props.label;
|
if (!data) return props.label;
|
||||||
@@ -164,6 +176,7 @@ const CloudProvider: React.FC<CloudProviderProps> = (props) => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
|
notFoundContent={<NotFoundCredentialContent />}
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={action === PageAction.EDIT}
|
||||||
label={intl.formatMessage({ id: 'clusters.credential.title' })}
|
label={intl.formatMessage({ id: 'clusters.credential.title' })}
|
||||||
required
|
required
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const OptionItem = styled.div.attrs({
|
|||||||
const DescriptionWrapper = styled.div`
|
const DescriptionWrapper = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
gap: 8px;
|
gap: 4px 8px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -94,34 +94,48 @@ const NotFoundImageContent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RenderInstanceOption = (option: any) => {
|
const RenderLabel = (data: {
|
||||||
const { data, styles } = option;
|
label: React.ReactNode;
|
||||||
|
vendor: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}) => {
|
||||||
|
const { label, vendor, style } = data;
|
||||||
|
return (
|
||||||
|
<div style={style} className="flex-center gap-8">
|
||||||
|
<IconFont type={_.get(vendorIconMap, vendor, 'icon-gpu1')}></IconFont>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RenderOption = (option: any) => {
|
||||||
|
const { data = {}, styles } = option;
|
||||||
|
const entries = Object.entries(data?.specInfo || {});
|
||||||
return (
|
return (
|
||||||
<CardContainer
|
<CardContainer
|
||||||
key={data.value}
|
key={data.value}
|
||||||
header={
|
header={
|
||||||
<span>
|
<RenderLabel
|
||||||
<IconFont
|
label={data.description || data.label}
|
||||||
type={_.get(vendorIconMap, data.vendor, 'icon-gpu1')}
|
vendor={data.vendor}
|
||||||
className="m-r-8"
|
style={styles?.header}
|
||||||
></IconFont>
|
/>
|
||||||
{data.description}
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
<DescriptionWrapper style={{ ...(styles?.description || {}) }}>
|
entries.length > 0 && (
|
||||||
{Object.entries(data?.specInfo)
|
<DescriptionWrapper style={{ ...styles?.description }}>
|
||||||
.filter(([key, value]) => value)
|
{Object.entries(data?.specInfo)
|
||||||
.map(([key, value]) => (
|
.filter(([key, value]) => value)
|
||||||
<OptionItem key={key}>
|
.map(([key, value]) => (
|
||||||
<span className="label">
|
<OptionItem key={key}>
|
||||||
{_.get(instanceTypeFieldMap, key, key)}:
|
<span className="label">
|
||||||
</span>
|
{_.get(instanceTypeFieldMap, key, key)}:
|
||||||
<span className="value">{value as string}</span>
|
</span>
|
||||||
</OptionItem>
|
<span className="value">{value as string}</span>
|
||||||
))}
|
</OptionItem>
|
||||||
</DescriptionWrapper>
|
))}
|
||||||
|
</DescriptionWrapper>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -186,19 +200,45 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
}, [currentData]);
|
}, [currentData]);
|
||||||
|
|
||||||
const imageLabelRender = (data: { label: string; value: string }) => {
|
const imageLabelRender = (data: {
|
||||||
console.log('imageLabelRender========', data);
|
label: React.ReactNode;
|
||||||
|
value: string | number;
|
||||||
|
}) => {
|
||||||
if (action === PageAction.EDIT) {
|
if (action === PageAction.EDIT) {
|
||||||
return currentData?.image_name || currentData?.os_image;
|
console.log('imageLabelRender========::', action, currentData, data);
|
||||||
|
// return currentData?.image_name || currentData?.os_image;
|
||||||
|
const vendor = _.split(currentData?.image_name || '', ' ')[0];
|
||||||
|
const iconType = _.get(vendorIconMap, vendor.toLowerCase());
|
||||||
|
return (
|
||||||
|
<div className="flex-center gap-8">
|
||||||
|
{iconType && <IconFont type={iconType}></IconFont>}
|
||||||
|
{currentData?.image_name || currentData?.os_image}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return data.label;
|
const selectImage = osImageList.find((item) => item.value === data.value);
|
||||||
|
console.log('imageLabelRender========::', selectImage);
|
||||||
|
if (selectImage) {
|
||||||
|
return (
|
||||||
|
<RenderLabel label={data.label} vendor={selectImage.vendor || ''} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return data.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const instanceLabelRender = (data: { label: string; value: string }) => {
|
const instanceLabelRender = (data: {
|
||||||
if (action === PageAction.EDIT) {
|
label: React.ReactNode;
|
||||||
return currentData?.instance_spec?.label || currentData?.instance_type;
|
value: string | number;
|
||||||
}
|
}) => {
|
||||||
return data.label;
|
const currentInstanceSpec =
|
||||||
|
instanceTypeList.find((item) => item.value === data.value) ||
|
||||||
|
instanceSpec;
|
||||||
|
return (
|
||||||
|
<RenderLabel
|
||||||
|
label={data.label || currentInstanceSpec?.label || data.value}
|
||||||
|
vendor={currentInstanceSpec?.vendor || ''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOsImageChange = (value: string) => {
|
const handleOsImageChange = (value: string) => {
|
||||||
@@ -329,7 +369,7 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
required
|
required
|
||||||
options={instanceTypeList}
|
options={instanceTypeList}
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={action === PageAction.EDIT}
|
||||||
optionRender={RenderInstanceOption}
|
optionRender={RenderOption}
|
||||||
onChange={handleInstanceTypeChange}
|
onChange={handleInstanceTypeChange}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -383,11 +423,16 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
>
|
>
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
showSearch
|
showSearch
|
||||||
notFoundContent={<NotFoundImageContent />}
|
|
||||||
onChange={handleOsImageChange}
|
onChange={handleOsImageChange}
|
||||||
filterOption={filterImageOption}
|
filterOption={filterImageOption}
|
||||||
optionRender={RenderInstanceOption}
|
optionRender={(option) =>
|
||||||
|
RenderOption({
|
||||||
|
...option,
|
||||||
|
styles: { header: { marginBlock: 5 } }
|
||||||
|
})
|
||||||
|
}
|
||||||
labelRender={imageLabelRender}
|
labelRender={imageLabelRender}
|
||||||
|
placeholder={currentData?.image_name}
|
||||||
options={osImageList}
|
options={osImageList}
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={action === PageAction.EDIT}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ import React, { useMemo } from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ProviderType } from '../config';
|
import { ProviderType } from '../config';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div<{ $cols?: number }>`
|
||||||
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: repeat(${(props) => props.$cols || 3}, 1fr);
|
||||||
gap: 22px;
|
gap: 16px;
|
||||||
|
.template-card-wrapper {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
`;
|
`;
|
||||||
@@ -22,19 +28,20 @@ const Title = styled.span`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-block: 16px 24px;
|
margin-block: 20px 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface ProviderCatalogProps {
|
interface ProviderCatalogProps {
|
||||||
onSelect?: (provider: ProviderType) => void;
|
onSelect?: (provider: string, item: any) => void;
|
||||||
currentProvider?: ProviderType;
|
cols?: number;
|
||||||
|
currentProvider?: ProviderType | string;
|
||||||
clickable?: boolean;
|
clickable?: boolean;
|
||||||
dataList: {
|
dataList: {
|
||||||
label: string;
|
label: string;
|
||||||
key: string;
|
key: string;
|
||||||
locale?: boolean;
|
locale?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
icon: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
group?: string;
|
group?: string;
|
||||||
}[];
|
}[];
|
||||||
@@ -42,6 +49,7 @@ interface ProviderCatalogProps {
|
|||||||
|
|
||||||
const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
|
const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
|
||||||
onSelect,
|
onSelect,
|
||||||
|
cols = 3,
|
||||||
dataList,
|
dataList,
|
||||||
clickable,
|
clickable,
|
||||||
currentProvider
|
currentProvider
|
||||||
@@ -66,12 +74,12 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
|
|||||||
{groupName !== 'default' && (
|
{groupName !== 'default' && (
|
||||||
<Title>{intl.formatMessage({ id: groupName })}</Title>
|
<Title>{intl.formatMessage({ id: groupName })}</Title>
|
||||||
)}
|
)}
|
||||||
<Wrapper>
|
<Wrapper $cols={cols}>
|
||||||
{items?.map((action) => (
|
{items?.map((action) => (
|
||||||
<Card
|
<Card
|
||||||
height="auto"
|
height="80px"
|
||||||
key={action.key}
|
key={action.key}
|
||||||
onClick={() => onSelect?.(action.key as ProviderType)}
|
onClick={() => onSelect?.(action.key as string, action)}
|
||||||
active={currentProvider === action.key}
|
active={currentProvider === action.key}
|
||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
clickable={clickable}
|
clickable={clickable}
|
||||||
@@ -82,7 +90,7 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
|
|||||||
}
|
}
|
||||||
icon={action.icon}
|
icon={action.icon}
|
||||||
>
|
>
|
||||||
{action.description || 'This is a description'}
|
{action.description}
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import hyponPNG from '@/assets/logo/hygon.png';
|
|||||||
import iluvatarWEBP from '@/assets/logo/Iluvatar.png';
|
import iluvatarWEBP from '@/assets/logo/Iluvatar.png';
|
||||||
import metaxLogo from '@/assets/logo/metax.png';
|
import metaxLogo from '@/assets/logo/metax.png';
|
||||||
import moorePNG from '@/assets/logo/moore _threads.png';
|
import moorePNG from '@/assets/logo/moore _threads.png';
|
||||||
import nvidiaLogo from '@/assets/logo/nvidia.png';
|
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
|
import styled from 'styled-components';
|
||||||
import ProviderCatalog from './provider-catalog';
|
import ProviderCatalog from './provider-catalog';
|
||||||
|
|
||||||
const ProviderImage = ({ src, showBg }: { src: string; showBg?: boolean }) => {
|
const ProviderImage = ({ src, showBg }: { src: string; showBg?: boolean }) => {
|
||||||
@@ -24,15 +24,19 @@ const supportedHardPlatforms = [
|
|||||||
{
|
{
|
||||||
label: 'NVIDIA CUDA',
|
label: 'NVIDIA CUDA',
|
||||||
value: 'cuda',
|
value: 'cuda',
|
||||||
key: 'nvidia',
|
description: '',
|
||||||
|
key: 'cuda',
|
||||||
locale: false,
|
locale: false,
|
||||||
icon: <ProviderImage src={nvidiaLogo} showBg />
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#nvidia-cuda',
|
||||||
|
icon: <IconFont type="icon-nvidia2" style={{ fontSize: 32 }} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'AMD ROCm',
|
label: 'AMD ROCm',
|
||||||
|
description: '',
|
||||||
value: 'rocm',
|
value: 'rocm',
|
||||||
key: 'amd',
|
key: 'rocm',
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#amd-rocm',
|
||||||
icon: (
|
icon: (
|
||||||
<IconFont
|
<IconFont
|
||||||
type="icon-amd"
|
type="icon-amd"
|
||||||
@@ -42,30 +46,38 @@ const supportedHardPlatforms = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Ascend CANN',
|
label: 'Ascend CANN',
|
||||||
|
description: '',
|
||||||
value: 'npu',
|
value: 'npu',
|
||||||
key: 'ascend',
|
key: 'npu',
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#ascend-cann',
|
||||||
icon: <ProviderImage src={ascendLogo} showBg />
|
icon: <ProviderImage src={ascendLogo} showBg />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Hygon DTK',
|
label: 'Hygon DTK',
|
||||||
|
description: '',
|
||||||
value: 'dcu',
|
value: 'dcu',
|
||||||
key: 'hygon',
|
key: 'dcu',
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#hygon-dtk',
|
||||||
icon: <ProviderImage src={hyponPNG} />
|
icon: <ProviderImage src={hyponPNG} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Moore Threads MUSA',
|
label: 'Moore Threads',
|
||||||
|
description: '',
|
||||||
value: 'musa',
|
value: 'musa',
|
||||||
key: 'musa',
|
key: 'musa',
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#moore-threads-musa',
|
||||||
icon: <ProviderImage src={moorePNG} />
|
icon: <ProviderImage src={moorePNG} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Iluvatar Corex',
|
label: 'Iluvatar Corex',
|
||||||
|
description: '',
|
||||||
value: 'corex',
|
value: 'corex',
|
||||||
key: 'corex',
|
key: 'corex',
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#iluvatar-corex',
|
||||||
icon: <ProviderImage src={iluvatarWEBP} showBg />
|
icon: <ProviderImage src={iluvatarWEBP} showBg />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,6 +85,7 @@ const supportedHardPlatforms = [
|
|||||||
value: 'cambricon',
|
value: 'cambricon',
|
||||||
key: 'cambricon',
|
key: 'cambricon',
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#cambricon-mlu',
|
||||||
icon: <ProviderImage src={CambriconPNG} />
|
icon: <ProviderImage src={CambriconPNG} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -84,9 +97,50 @@ const supportedHardPlatforms = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const SupportedHardware = () => {
|
const Header = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-self: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const renderHeader = (item: any) => {
|
||||||
return (
|
return (
|
||||||
<ProviderCatalog dataList={supportedHardPlatforms} clickable={false} />
|
<Header>
|
||||||
|
<span className="icon">{item.icon}</span>
|
||||||
|
<span className="label">{item.label}</span>
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataList = supportedHardPlatforms.map((item) => {
|
||||||
|
return {
|
||||||
|
label: renderHeader(item),
|
||||||
|
value: item.value,
|
||||||
|
key: item.key,
|
||||||
|
locale: item.locale
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SupportedHardwareProps {
|
||||||
|
onSelect?: (provider: string, item: any) => void;
|
||||||
|
currentProvider?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
||||||
|
onSelect,
|
||||||
|
currentProvider
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ProviderCatalog
|
||||||
|
onSelect={onSelect}
|
||||||
|
currentProvider={currentProvider}
|
||||||
|
dataList={supportedHardPlatforms}
|
||||||
|
clickable={true}
|
||||||
|
cols={4}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { fromClusterCreationAtom } from '@/atoms/clusters';
|
||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import { FilterBar } from '@/components/page-tools';
|
import { FilterBar } from '@/components/page-tools';
|
||||||
@@ -5,9 +6,10 @@ import { PageAction } from '@/config';
|
|||||||
import type { PageActionType } from '@/config/types';
|
import type { PageActionType } from '@/config/types';
|
||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { ConfigProvider, Empty, Table, message } from 'antd';
|
import { ConfigProvider, Empty, Table, message } from 'antd';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
createCredential,
|
createCredential,
|
||||||
@@ -52,8 +54,9 @@ const Credentials: React.FC = () => {
|
|||||||
deleteAPI: deleteCredential,
|
deleteAPI: deleteCredential,
|
||||||
contentForDelete: 'menu.clusterManagement.credentials'
|
contentForDelete: 'menu.clusterManagement.credentials'
|
||||||
});
|
});
|
||||||
|
const [isFromCluster] = useAtom(fromClusterCreationAtom);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [openModalStatus, setOpenModalStatus] = useState<{
|
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||||
provider: ProviderType;
|
provider: ProviderType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -97,6 +100,9 @@ const Credentials: React.FC = () => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await createCredential({ data: params });
|
await createCredential({ data: params });
|
||||||
|
// if (isFromCluster) {
|
||||||
|
// navigate(-1);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
setOpenModalStatus({ ...openModalStatus, open: false });
|
setOpenModalStatus({ ...openModalStatus, open: false });
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
|
import { SealColumnProps } from '@/components/seal-table/types';
|
||||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
|
||||||
import type { SortOrder } from 'antd/es/table/interface';
|
import type { SortOrder } from 'antd/es/table/interface';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { RenderInstanceOption } from '../components/pool-form';
|
import { RenderOption } from '../components/pool-form';
|
||||||
import { NodePoolListItem as ListItem } from '../config/types';
|
import { NodePoolListItem as ListItem } from '../config/types';
|
||||||
|
|
||||||
const actionItems = [
|
const actionItems = [
|
||||||
@@ -30,7 +30,7 @@ const actionItems = [
|
|||||||
const usePoolsColumns = (
|
const usePoolsColumns = (
|
||||||
handleSelect: (val: string, record: ListItem) => void,
|
handleSelect: (val: string, record: ListItem) => void,
|
||||||
sortOrder?: SortOrder
|
sortOrder?: SortOrder
|
||||||
): ColumnsType<ListItem & { dataIndex: string }> => {
|
): SealColumnProps[] => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
@@ -66,7 +66,7 @@ const usePoolsColumns = (
|
|||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
<AutoTooltip
|
<AutoTooltip
|
||||||
title={
|
title={
|
||||||
<RenderInstanceOption
|
<RenderOption
|
||||||
styles={{
|
styles={{
|
||||||
description: {
|
description: {
|
||||||
color: 'var(--color-white-quaternary)'
|
color: 'var(--color-white-quaternary)'
|
||||||
@@ -180,6 +180,7 @@ const usePoolsColumns = (
|
|||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.operation' }),
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
key: 'operations',
|
key: 'operations',
|
||||||
|
dataIndex: 'operations',
|
||||||
span: 3,
|
span: 3,
|
||||||
style: {
|
style: {
|
||||||
paddingLeft: 36
|
paddingLeft: 36
|
||||||
|
|||||||
@@ -67,15 +67,19 @@ const formatSpec = (spec: ParsedSpec): string => {
|
|||||||
|
|
||||||
if (spec.vram) parts.push(`${spec.vram} VRAM`);
|
if (spec.vram) parts.push(`${spec.vram} VRAM`);
|
||||||
if (spec.vcpus) parts.push(`${spec.vcpus} vCPUs`);
|
if (spec.vcpus) parts.push(`${spec.vcpus} vCPUs`);
|
||||||
if (spec.bootDisk) parts.push(`${spec.bootDisk} Boot disk`);
|
|
||||||
if (spec.ram) parts.push(`${spec.ram} RAM`);
|
if (spec.ram) parts.push(`${spec.ram} RAM`);
|
||||||
if (spec.scratchDisk) {
|
// if (spec.bootDisk) parts.push(`${spec.bootDisk} Boot disk`);
|
||||||
parts.push(`${spec.scratchDisk} Scratch disk`);
|
// if (spec.scratchDisk) {
|
||||||
}
|
// parts.push(`${spec.scratchDisk} Scratch disk`);
|
||||||
|
// }
|
||||||
|
|
||||||
return parts.join(' / ');
|
return parts.join(' / ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatLabel = (instanceSpec: any): string => {
|
||||||
|
return `${_.toUpper(instanceSpec.gpu_info?.model.replace(/_/g, ' '))}`;
|
||||||
|
};
|
||||||
|
|
||||||
export const useProviderRegions = () => {
|
export const useProviderRegions = () => {
|
||||||
const [regions, setRegions] = useAtom(regionListAtom);
|
const [regions, setRegions] = useAtom(regionListAtom);
|
||||||
const [, setInstanceTypes] = useAtom(regionInstanceTypeListAtom);
|
const [, setInstanceTypes] = useAtom(regionInstanceTypeListAtom);
|
||||||
@@ -119,10 +123,12 @@ export const useProviderRegions = () => {
|
|||||||
?.filter((sItem: any) => sItem.gpu_info && sItem.available)
|
?.filter((sItem: any) => sItem.gpu_info && sItem.available)
|
||||||
.map((item: any) => {
|
.map((item: any) => {
|
||||||
const specInfo = parseSpec(item);
|
const specInfo = parseSpec(item);
|
||||||
|
const label = formatLabel(item);
|
||||||
|
const description = `${label} ${item.gpu_info?.count}X`;
|
||||||
return {
|
return {
|
||||||
label: formatSpec(specInfo),
|
label: `${description} - ${formatSpec(specInfo)}`,
|
||||||
value: item.slug,
|
value: item.slug,
|
||||||
description: item.description,
|
description: description,
|
||||||
specInfo: specInfo,
|
specInfo: specInfo,
|
||||||
vendor: _.get(_.split(item.gpu_info?.model, '_'), 0),
|
vendor: _.get(_.split(item.gpu_info?.model, '_'), 0),
|
||||||
available: item.available,
|
available: item.available,
|
||||||
@@ -148,10 +154,7 @@ export const useProviderRegions = () => {
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
vendor: _.camelCase(item.distribution),
|
vendor: _.camelCase(item.distribution),
|
||||||
specInfo: {
|
specInfo: {},
|
||||||
size: `${item.size_gigabytes} GiB`,
|
|
||||||
minDiskSize: `${item.min_disk_size} GiB`
|
|
||||||
},
|
|
||||||
regions: item.regions || []
|
regions: item.regions || []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const BasicForm = forwardRef((props: BasicFormProps, ref) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageTools
|
<PageTools
|
||||||
marginBottom={26}
|
marginBottom={16}
|
||||||
left={
|
left={
|
||||||
<Title>
|
<Title>
|
||||||
{intl.formatMessage({ id: 'clusters.create.configBasic' })}
|
{intl.formatMessage({ id: 'clusters.create.configBasic' })}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../style/gpu-card.less';
|
|||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -18,7 +19,6 @@ const CardWrapper = styled.div`
|
|||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Description = styled.div`
|
const Description = styled.div`
|
||||||
@@ -36,7 +36,7 @@ export const CardContainer: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<CardWrapper>
|
<CardWrapper>
|
||||||
<Header>{header}</Header>
|
<Header>{header}</Header>
|
||||||
<Description>{description}</Description>
|
{description && <Description>{description}</Description>}
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--ipc=host \\
|
--ipc=host \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
${params.image} \\
|
${params.image} \\
|
||||||
--server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} \\
|
||||||
|
--registration-token ${params.token} \\
|
||||||
|
--worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
npu: {
|
npu: {
|
||||||
|
|||||||
Reference in New Issue
Block a user