style: cluster creation modal

This commit is contained in:
jialin
2026-01-06 17:21:32 +08:00
parent 0f7a475ec9
commit d51f00d35d
18 changed files with 224 additions and 98 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -7,6 +7,7 @@ import styled from 'styled-components';
const CardStyled = styled(Card)`
box-shadow: none !important;
background-color: none;
&.isOpen {
.ant-card-head {
border-bottom: 1px solid var(--ant-color-border-secondary);
+2 -2
View File
@@ -1,8 +1,8 @@
import { createFromIconfontCN } from '@ant-design/icons';
import './iconfont/iconfont.js';
// import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({
scriptUrl: ''
scriptUrl: '//at.alicdn.com/t/c/font_4613488_jo5x7z2z718.js'
});
export default IconFont;
+5
View File
@@ -21,6 +21,8 @@ const Wrapper = styled.div<{ $token: any }>`
--ant-table-row-selected-hover-bg: ${(props) =>
props.$token.rowSelectedHoverBg};
--ant-table-row-hover-bg: ${(props) => props.$token.rowHoverBg};
--ant-table-header-icon-color: ${(props) =>
props.$token.tableHeaderIconColor};
`;
const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
@@ -143,10 +145,13 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
pagination?.onShowSizeChange?.(current, size);
};
console.log('token.table', token);
return (
<Wrapper
$token={{
...token.Table,
tableHeaderIconColor: token.colorTextQuaternary,
colorBorderSecondary: token.colorBorderSecondary
}}
>
+2 -2
View File
@@ -53,8 +53,8 @@
margin-left: 4px;
.anticon {
font-size: 10px;
color: rgba(0, 0, 0, 29%);
font-size: 12px;
color: var(--ant-table-header-icon-color);
}
.sorter-up {
+1 -6
View File
@@ -127,12 +127,7 @@ export default (props: any) => {
const { clientRoutes } = useAppData();
const requestResourceRef = useRef<boolean>(false);
const {
setLoadingStatus,
fetchResourceData,
NoResourceModal,
loadingStatus
} = useAddResource({
const { fetchResourceData, NoResourceModal } = useAddResource({
onCreated() {
requestResourceRef.current = false;
}
@@ -13,10 +13,7 @@ const CollapseInner = styled(Collapse)`
border-radius: var(--border-radius-base) !important;
font-size: 14px !important;
font-weight: 600 !important;
&:hover {
background-color: var(--ant-color-fill-tertiary) !important;
}
background-color: var(--ant-color-fill-tertiary) !important;
}
.ant-collapse-body {
+21 -15
View File
@@ -3,6 +3,7 @@ import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import ColumnWrapper from '@/pages/_components/column-wrapper';
import { useIntl, useSearchParams } from '@umijs/max';
import { Typography } from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
@@ -29,6 +30,8 @@ import useSystemConfig from './services/use-system-config';
import { moduleMap, moduleRegistry } from './step-forms/module-registry';
import useStepList from './step-forms/use-step-list';
const { Title, Text } = Typography;
const Container = styled.div`
width: 100%;
display: flex;
@@ -41,14 +44,10 @@ const Content = styled.div`
`;
const StepsWrapper = styled.div`
width: 230px;
width: 240px;
padding-inline: 24px;
padding-block: 16px;
// background-color: var(--ant-color-fill-quaternary);
border-right: 1px solid var(--ant-color-split);
.ant-steps.ant-steps-vertical {
height: 600px;
}
`;
const MainWrapper = styled.div`
@@ -308,14 +307,15 @@ const ClusterCreate: React.FC<{
};
const handleCancel = () => {
if (clusterSession?.firstAddCluster) {
setClusterSession({
firstAddCluster: false,
firstAddWorker: false
});
onClose?.();
return;
}
// TODO: delete it if created by drawer
// if (clusterSession?.firstAddCluster) {
// setClusterSession({
// firstAddCluster: false,
// firstAddWorker: false
// });
// onClose?.();
// return;
// }
onClose?.();
};
@@ -326,7 +326,7 @@ const ClusterCreate: React.FC<{
</StepsWrapper>
<ColumnWrapper
styles={{
container: { paddingTop: 16, paddingBottom: 56 }
container: { paddingTop: 16, paddingBottom: 16 }
}}
footer={
<FooterButtons
@@ -340,7 +340,13 @@ const ClusterCreate: React.FC<{
/>
}
>
<div style={{ flex: 1 }}>
<Title level={5} style={{ marginBottom: 8 }}>
{steps[currentStep]?.subTitle || steps[currentStep]?.title}
</Title>
<Text type="secondary">
{steps[currentStep]?.description || steps[currentStep]?.content}
</Text>
<div style={{ flex: 1, marginTop: 32 }}>
{currentStep === startStep && (
<ProviderCatalog
groupIcons={{
@@ -291,6 +291,14 @@ const Clusters: React.FC = () => {
}
}, [clusterSession, dataSource.loadend, dataSource.dataList]);
useEffect(() => {
if (clusterSession?.firstAddCluster && dataSource.loadend) {
openClusterModal();
// reset session
setClusterSession(null);
}
}, [clusterSession, dataSource.loadend]);
const renderChildren = (
list: any,
options: { parent?: any; [key: string]: any }
@@ -1,34 +1,106 @@
import { Steps } from 'antd';
import { CheckCircleFilled, CheckCircleOutlined } from '@ant-design/icons';
import React from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
width: min(1200px, 100%);
margin: 0 auto;
padding-block: 0px;
.ant-steps-item-description {
max-width: 300px !important;
color: var(--ant-color-text-description) !important;
display: flex;
flex-direction: column;
gap: 24px;
`;
const Box = styled.div`
.indicates {
color: var(--ant-color-text-tertiary);
font-size: 14px;
margin-bottom: 24px;
}
`;
const StepItemWrapper = styled.div`
display: flex;
align-items: flex-start;
gap: 12px;
padding-bottom: 16px;
// border-bottom: 1px solid var(--ant-color-split);
.description {
display: flex;
flex-direction: column;
gap: 4px;
.title {
font-weight: 500;
line-height: 20px;
color: var(--ant-color-text-tertiary);
}
.content {
line-height: 22px;
font-size: 14px;
color: var(--ant-color-text-tertiary);
}
}
&.active {
.description {
.title {
color: var(--ant-color-text);
font-weight: 600;
}
.content {
color: var(--ant-color-text);
}
}
}
`;
const StepsItem: React.FC<{
active: boolean;
item: {
title: string;
content: string;
};
}> = ({ active, item }) => {
return (
<StepItemWrapper className={active ? 'active' : ''}>
<div className="icon">
{active ? (
<CheckCircleFilled
style={{ color: 'var(--ant-color-primary)', fontSize: 20 }}
/>
) : (
<CheckCircleOutlined
style={{ color: 'var(--ant-color-text-disabled)', fontSize: 20 }}
/>
)}
</div>
<div className="description">
<div className="title">{item.title}</div>
<div className="content">{item.content}</div>
</div>
</StepItemWrapper>
);
};
const ClusterSteps: React.FC<{
currentStep: number;
onChange?: (step: number) => void;
steps: any[];
}> = (props) => {
const { steps, currentStep, onChange } = props;
const { steps, currentStep = 0, onChange } = props;
return (
<Wrapper>
<Steps
items={steps}
current={currentStep}
size="small"
onChange={onChange}
orientation="vertical"
></Steps>
</Wrapper>
<Box>
<div className="indicates">
STEP {currentStep + 1} OF {steps.length}
</div>
<Wrapper>
{steps.map((item, index) => (
<StepsItem
key={index}
active={currentStep === index}
item={item}
></StepsItem>
))}
</Wrapper>
</Box>
);
};
@@ -1,4 +1,4 @@
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import styled from 'styled-components';
@@ -37,10 +37,14 @@ const FooterButtons: React.FC<FooterButtonsProps> = (props) => {
onNext();
};
return (
<Title style={{ padding: '16px 24px 32px' }}>
<Title style={{ padding: '16px 24px 24px' }}>
<div className="flex-center gap-20">
{showButtons.previous && (
<Button type="default" icon={<LeftOutlined />} onClick={onPrevious}>
<Button
type="default"
icon={<ArrowLeftOutlined />}
onClick={onPrevious}
>
{intl.formatMessage({ id: 'common.button.prev' })}
</Button>
)}
@@ -51,7 +55,7 @@ const FooterButtons: React.FC<FooterButtonsProps> = (props) => {
style={{ minWidth: 100 }}
>
{intl.formatMessage({ id: 'common.button.next' })}
<RightOutlined />
<ArrowRightOutlined />
</Button>
)}
{showButtons.skip && (
@@ -29,7 +29,7 @@ const Title = styled.span`
display: flex;
gap: 8px;
align-items: center;
font-weight: 700;
font-weight: 500;
font-size: 14px;
margin-bottom: 16px;
.icon {
@@ -100,6 +100,9 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
}, [dataList]);
const renderTitle = (item: any) => {
if (item.hiddenTitle) {
return <>{item.extra && <span className="extra">{item.extra}</span>}</>;
}
return (
<Header>
<span className="title">
@@ -110,6 +113,7 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
{intl.formatMessage({ id: item.description })}
</span>
)}
{item.extra && <span className="extra">{item.extra}</span>}
</Header>
);
};
@@ -3,7 +3,8 @@ 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 mooreLogo from '@/assets/logo/moore-logo.png';
import nvidiaLogo from '@/assets/logo/nvidia.png';
import IconFont from '@/components/icon-font';
import {
AddWorkerDockerNotes,
@@ -15,21 +16,45 @@ import ProviderCatalog from './provider-catalog';
const Box = styled.div`
.template-card-wrapper {
position: relative;
justify-content: center;
background-color: rgba(255, 255, 255, 0.1);
&.active {
background-color: var(--ant-color-fill-tertiary);
}
.template-card-icon {
margin-right: 0;
}
.template-card-inner {
line-height: 1;
flex-direction: row;
height: auto;
width: 0;
overflow: visible;
.template-card-content {
font-size: 13px;
}
}
.extra {
position: absolute;
right: 2px;
top: 2px;
padding: 2px;
border-radius: 2px;
font-size: 10px;
font-weight: 400;
background-color: var(--ant-blue-1);
color: var(--ant-color-info);
}
}
`;
const ProviderImage = ({ src, showBg }: { src: string; showBg?: boolean }) => {
const ProviderImage = ({ src, height }: { src: string; height?: number }) => {
return (
<img
src={src}
style={{
width: 32,
height: height || 20,
objectFit: 'contain'
}}
/>
@@ -52,16 +77,18 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
const supportedHardPlatforms = [
{
label: 'NVIDIA',
hiddenTitle: true,
value: GPUDriverMap.NVIDIA,
description: '',
key: GPUDriverMap.NVIDIA,
locale: false,
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA],
link: 'https://docs.gpustack.ai/latest/installation/requirements/#nvidia-gpu',
icon: <IconFont type="icon-nvidia2" style={{ fontSize: 32 }} />
icon: <ProviderImage src={nvidiaLogo} height={20} />
},
{
label: 'AMD',
hiddenTitle: true,
description: '',
value: GPUDriverMap.AMD,
key: GPUDriverMap.AMD,
@@ -70,70 +97,80 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
link: 'https://docs.gpustack.ai/latest/installation/requirements/#amd-gpu',
icon: (
<IconFont
type="icon-amd"
style={{ fontSize: 32, color: 'var(--ant-color-text)' }}
type="icon-amd-logo"
style={{ fontSize: 60, color: 'var(--ant-color-text)' }}
/>
)
},
{
label: intl.formatMessage({ id: 'vendor.ascend' }),
hiddenTitle: true,
description: '',
value: GPUDriverMap.ASCEND,
key: GPUDriverMap.ASCEND,
locale: false,
notes: AddWorkerDockerNotes[GPUDriverMap.ASCEND],
link: 'https://docs.gpustack.ai/latest/installation/requirements/#ascend-npu',
icon: <ProviderImage src={ascendLogo} showBg />
icon: <ProviderImage src={ascendLogo} height={32} />
},
{
label: intl.formatMessage({ id: 'vendor.hygon' }),
hiddenTitle: true,
description: '',
value: GPUDriverMap.HYGON,
key: GPUDriverMap.HYGON,
locale: false,
notes: AddWorkerDockerNotes[GPUDriverMap.HYGON],
link: 'https://docs.gpustack.ai/latest/installation/requirements/#hygon-dcu',
icon: <ProviderImage src={hyponPNG} />
icon: <ProviderImage src={hyponPNG} height={20} />
},
{
label: intl.formatMessage({ id: 'vendor.moorthreads' }),
description: 'common.tag.experimental',
hiddenTitle: true,
// description: 'common.tag.experimental',
extra: intl.formatMessage({ id: 'common.tag.experimental' }),
value: GPUDriverMap.MOORE_THREADS,
key: GPUDriverMap.MOORE_THREADS,
locale: false,
notes: AddWorkerDockerNotes[GPUDriverMap.MOORE_THREADS],
link: 'https://docs.gpustack.ai/latest/installation/requirements/#mthreads-gpu',
icon: <ProviderImage src={moorePNG} />
icon: <ProviderImage src={mooreLogo} height={26} />
},
{
label: intl.formatMessage({ id: 'vendor.iluvatar' }),
description: 'common.tag.experimental',
hiddenTitle: true,
// description: 'common.tag.experimental',
extra: intl.formatMessage({ id: 'common.tag.experimental' }),
value: GPUDriverMap.ILUVATAR,
key: GPUDriverMap.ILUVATAR,
locale: false,
notes: AddWorkerDockerNotes[GPUDriverMap.ILUVATAR],
link: 'https://docs.gpustack.ai/latest/installation/requirements/#iluvatar-gpu',
icon: <ProviderImage src={iluvatarWEBP} showBg />
icon: <ProviderImage src={iluvatarWEBP} height={26} />
},
{
label: intl.formatMessage({ id: 'vendor.cambricon' }),
description: 'common.tag.experimental',
hiddenTitle: true,
// description: 'common.tag.experimental',
extra: intl.formatMessage({ id: 'common.tag.experimental' }),
value: GPUDriverMap.CAMBRICON,
key: GPUDriverMap.CAMBRICON,
locale: false,
notes: AddWorkerDockerNotes[GPUDriverMap.CAMBRICON],
link: 'https://docs.gpustack.ai/latest/installation/requirements/#cambricon-mlu',
icon: <ProviderImage src={CambriconPNG} />
icon: <ProviderImage src={CambriconPNG} height={26} />
},
{
label: intl.formatMessage({ id: 'vendor.metax' }),
description: 'common.tag.experimental',
hiddenTitle: true,
// description: 'common.tag.experimental',
extra: intl.formatMessage({ id: 'common.tag.experimental' }),
value: GPUDriverMap.METAX,
key: GPUDriverMap.METAX,
locale: false,
link: 'https://docs.gpustack.ai/latest/installation/requirements/#metax-gpu',
notes: AddWorkerDockerNotes[GPUDriverMap.METAX],
icon: <ProviderImage src={metaxLogo} showBg />
icon: <ProviderImage src={metaxLogo} height={20} />
}
];
@@ -145,7 +182,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
current={current}
dataList={supportedHardPlatforms}
clickable={clickable}
cols={3}
cols={4}
/>
</Box>
);
@@ -1,4 +1,3 @@
import PageTools from '@/components/page-tools';
import { PageActionType } from '@/config/types';
import { useIntl } from '@umijs/max';
import { forwardRef, useImperativeHandle, useRef } from 'react';
@@ -40,7 +39,7 @@ const BasicForm = forwardRef((props: BasicFormProps, ref) => {
return (
<div>
<PageTools
{/* <PageTools
marginBottom={16}
left={
<Title>
@@ -48,7 +47,7 @@ const BasicForm = forwardRef((props: BasicFormProps, ref) => {
</Title>
}
marginTop={0}
></PageTools>
></PageTools> */}
<ClusterForm
provider={provider}
action={action}
@@ -15,8 +15,11 @@ export default function useStepList() {
return useMemo(
() => [
{
title: intl.formatMessage({ id: 'clusters.create.selectProvider' }),
content: '',
title: intl.formatMessage({ id: 'clusters.table.provider' }),
content: 'Choose where your cluster will run.',
subTitle: 'Select Provider',
description:
'Choose the environment where your cluster will be deployed.',
showButtons: (provider?: ProviderType) => {
return {
previous: false,
@@ -31,8 +34,10 @@ export default function useStepList() {
providers: []
},
{
title: intl.formatMessage({ id: 'clusters.create.configBasic' }),
content: '',
title: intl.formatMessage({ id: 'common.title.config' }),
content: 'Configure your cluster settings.',
description:
'Set the cluster name, description, or other essential parameters in advanced settings.',
showButtons: (provider?: ProviderType) => {
return {
previous: true,
@@ -48,7 +53,7 @@ export default function useStepList() {
},
{
title: intl.formatMessage({ id: 'clusters.button.addNodePool' }),
content: '',
content: 'Add worker pools to your cluster.',
showButtons: (provider?: ProviderType) => {
return {
previous: true,
@@ -65,7 +70,9 @@ export default function useStepList() {
},
{
title: intl.formatMessage({ id: 'resources.button.create' }),
content: '',
content: 'The cluster is created successfully.',
description:
'The cluster is now set up and you can add workers to it. You can also add workers later from the cluster list.',
showButtons: (provider?: ProviderType) => {
return {
previous: false,
@@ -82,6 +89,8 @@ export default function useStepList() {
{
title: intl.formatMessage({ id: 'clusters.button.register' }),
content: '',
description:
'Register an existing Kubernetes cluster to manage its workers. You can also register a cluster later from the cluster list.',
showButtons: (provider?: ProviderType) => {
return {
previous: false,
@@ -181,20 +181,15 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
<PageTools
marginBottom={22}
marginTop={0}
// left={
// <span className="flex-center gap-16">
// <Title>
// {intl.formatMessage({ id: 'clusters.workerpool.title' })}
// </Title>
// </span>
// }
left={
<span className="flex-center gap-16">
<Title>
{intl.formatMessage({ id: 'clusters.workerpool.title' })}
</Title>
</span>
}
right={
<Button
onClick={handleAddPool}
variant="filled"
color="default"
icon={<PlusOutlined />}
>
<Button onClick={handleAddPool} type="link" icon={<PlusOutlined />}>
{intl.formatMessage({ id: 'clusters.button.addNodePool' })}
</Button>
}
@@ -2,7 +2,6 @@ import { clusterSessionAtom } from '@/atoms/clusters';
import { hideModalTemporarilyAtom } from '@/atoms/settings';
import IconFont from '@/components/icon-font';
import ScrollerModal from '@/components/scroller-modal/index';
import { PageAction } from '@/config';
import useUserSettings from '@/hooks/use-user-settings';
import useClusterList from '@/pages/cluster-management/hooks/use-cluster-list';
import { useIntl, useNavigate } from '@umijs/max';
@@ -105,9 +104,7 @@ export default function useAddResource(options?: { onCreated?: () => void }) {
firstAddCluster: true
});
navigate(
`/cluster-management/clusters/create?action=${PageAction.CREATE}`
);
navigate(`/cluster-management/clusters/list`);
return;
}
@@ -1,6 +1,5 @@
import { clusterSessionAtom } from '@/atoms/clusters';
import IconFont from '@/components/icon-font';
import { PageAction } from '@/config';
import NoResult from '@/pages/_components/no-result';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
@@ -54,9 +53,7 @@ const useNoResourceResult = (props: {
firstAddCluster: true
});
navigate(
`/cluster-management/clusters/create?action=${PageAction.CREATE}`
);
navigate(`/cluster-management/clusters/list`);
return;
}