style: cluster creation steps

This commit is contained in:
jialin
2026-01-06 17:21:32 +08:00
parent 0ba8300959
commit 0eba1da6c4
21 changed files with 223 additions and 79 deletions
+10 -10
View File
@@ -199,16 +199,16 @@ export default [
hideInMenu: true,
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: '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',
path: '/cluster-management/credentials',
+3
View File
@@ -23,6 +23,8 @@ const Wrapper = styled.div<{ $token: any }>`
--ant-table-row-hover-bg: ${(props) => props.$token.rowHoverBg};
--ant-table-header-icon-color: ${(props) =>
props.$token.tableHeaderIconColor};
--ant-table-header-icon-hover-color: ${(props) =>
props.$token.tableHeaderIconHoverColor};
`;
const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
@@ -152,6 +154,7 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
$token={{
...token.Table,
tableHeaderIconColor: token.colorTextQuaternary,
tableHeaderIconHoverColor: token.colorTextSecondary,
colorBorderSecondary: token.colorBorderSecondary
}}
>
@@ -38,6 +38,16 @@
justify-content: center;
}
&:hover {
.sorter-header {
.sorter {
.anticon {
color: var(--ant-table-header-icon-hover-color);
}
}
}
}
.sorter-header {
display: flex;
flex: 1;
@@ -55,6 +65,7 @@
.anticon {
font-size: 12px;
color: var(--ant-table-header-icon-color);
transition: color var(--ant-motion-duration-slow);
}
.sorter-up {
+4 -1
View File
@@ -25,7 +25,10 @@ export default {
cellFontSize: 14,
rowSelectedHoverBg: '#1d1d1d',
rowHoverBg: '#1d1d1d',
rowSelectedBg: 'transparent'
rowSelectedBg: 'transparent',
headerSortActiveBg: 'transparent',
headerSortHoverBg: 'transparent',
headerBg: 'none'
},
Button: {
contentFontSizeLG: 14,
+4 -1
View File
@@ -25,7 +25,10 @@ export default {
cellFontSize: 14,
rowSelectedHoverBg: 'rgb(249 249 249)',
rowHoverBg: 'rgb(249 249 249)',
rowSelectedBg: 'transparent'
rowSelectedBg: 'transparent',
headerSortActiveBg: 'transparent',
headerSortHoverBg: 'transparent',
headerBg: 'none'
},
Button: {
contentFontSizeLG: 14,
+2 -2
View File
@@ -322,12 +322,12 @@ export default (props: any) => {
}}
modal={{
mask: {
blur: false
blur: true
}
}}
drawer={{
mask: {
blur: false
blur: true
}
}}
>
+3 -3
View File
@@ -211,8 +211,8 @@ export const yamlTemplate = `# ----------------------------------------
# - run_command: optional
# - entrypoint: optional
# - custom_framework:
# - optional
# - choose from: ${Object.values(GPUDriverMap).join(', ')}, CPU
# - required
# - choose from: ${Object.values(GPUDriverMap).join(', ')}, cpu
backend_name: vllm-custom
description: this is my custom vllm backend
@@ -231,5 +231,5 @@ version_configs:
image_name: lm/vllm:test
entrypoint:
run_command:
custom_framework:
custom_framework: rocm
`;
+33 -24
View File
@@ -44,7 +44,7 @@ const Content = styled.div`
`;
const StepsWrapper = styled.div`
width: 240px;
width: 220px;
padding-inline: 24px;
padding-block: 16px;
border-right: 1px solid var(--ant-color-split);
@@ -87,6 +87,8 @@ const ClusterCreate: React.FC<{
} as ClusterFormData);
const [formValues, setFormValues] = useState<Record<string, any>>({});
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
const [isAddWorkerStep, setIsAddWorkerStep] = useState<boolean>(false);
const [isComplete, setIsComplete] = useState<boolean>(false);
const formRefs: Record<string, any> = {
[moduleMap.BasicForm]: useRef<any>(null),
@@ -238,6 +240,12 @@ const ClusterCreate: React.FC<{
});
};
// add worker or cluster registration step
const handleAddWorker = () => {
setIsAddWorkerStep(true);
onNext();
};
/**
* this function is used to render the modules in the current step, they are not forms
* @returns
@@ -261,6 +269,8 @@ const ClusterCreate: React.FC<{
}
registrationInfo={registrationInfo}
provider={extraData.provider}
onSkip={onClose}
onAddWorker={handleAddWorker}
/>
) : null;
});
@@ -294,6 +304,7 @@ const ClusterCreate: React.FC<{
const res = await createCluster({ data });
const info = await queryClusterToken({ id: res.id });
setSubmitLoading(false);
setIsComplete(true);
setRegistrationInfo({
...info,
cluster_id: res.id
@@ -307,23 +318,16 @@ const ClusterCreate: React.FC<{
};
const handleCancel = () => {
// TODO: delete it if created by drawer
// if (clusterSession?.firstAddCluster) {
// setClusterSession({
// firstAddCluster: false,
// firstAddWorker: false
// });
// onClose?.();
// return;
// }
onClose?.();
};
return (
<MainWrapper>
<StepsWrapper>
<ClusterSteps steps={steps} currentStep={currentStep}></ClusterSteps>
</StepsWrapper>
{/* {!isAddWorkerStep && (
<StepsWrapper>
<ClusterSteps steps={steps} currentStep={currentStep}></ClusterSteps>
</StepsWrapper>
)} */}
<ColumnWrapper
styles={{
container: { paddingTop: 16, paddingBottom: 16 }
@@ -340,19 +344,24 @@ const ClusterCreate: React.FC<{
/>
}
>
<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 }}>
{/* {!isComplete && (
<div style={{ marginBottom: 24 }}>
<Title level={5} style={{ marginBottom: 8, fontSize: 14 }}>
{steps[currentStep]?.subTitle || steps[currentStep]?.title}
</Title>
</div>
)} */}
{!isAddWorkerStep && (
<div style={{ marginBottom: 32 }}>
<ClusterSteps
steps={steps}
currentStep={currentStep}
></ClusterSteps>
</div>
)}
<div style={{ flex: 1 }}>
{currentStep === startStep && (
<ProviderCatalog
groupIcons={{
'clusters.create.provider.self': 'icon-server02',
'clusters.create.provider.cloud': 'icon-cloud'
}}
cols={2}
dataList={providerList}
height={70}
@@ -4,17 +4,22 @@ import ClusterCreate from './cluster-create';
interface ClusterModalProps {
open: boolean;
title: string;
onClose: () => void;
}
const ClusterModal: React.FC<ClusterModalProps> = ({ open, onClose }) => {
const ClusterModal: React.FC<ClusterModalProps> = ({
open,
onClose,
title
}) => {
const handleCancel = () => {
onClose();
};
return (
<GSDrawer
title={'Create Cluster'}
title={title}
open={open}
onClose={handleCancel}
destroyOnHidden={true}
@@ -25,7 +30,7 @@ const ClusterModal: React.FC<ClusterModalProps> = ({ open, onClose }) => {
body: {
paddingBlock: 0
},
wrapper: { width: 900 }
wrapper: { width: 700 }
}}
footer={false}
>
@@ -411,6 +411,9 @@ const Clusters: React.FC = () => {
></AddPool>
<DeleteModal ref={modalRef}></DeleteModal>
<ClusterModal
title={intl.formatMessage({
id: 'menu.clusterManagement.clusterCreate'
})}
open={clusterModalStatus.open}
onClose={closeClusterModal}
></ClusterModal>
@@ -1,4 +1,5 @@
import { CheckCircleFilled, CheckCircleOutlined } from '@ant-design/icons';
import { Steps } from 'antd';
import React from 'react';
import styled from 'styled-components';
@@ -14,6 +15,12 @@ const Box = styled.div`
font-size: 14px;
margin-bottom: 24px;
}
.ant-steps {
--steps-title-font-size: 14px;
.ant-steps-item {
--steps-item-base-width: 20px;
}
}
`;
const StepItemWrapper = styled.div`
@@ -86,19 +93,30 @@ const ClusterSteps: React.FC<{
}> = (props) => {
const { steps, currentStep = 0, onChange } = props;
const visibleSteps = steps.filter((step) => !step.hideInSteps);
return (
<Box>
<div className="indicates">
STEP {currentStep + 1} OF {steps.length}
</div>
<Wrapper>
{steps.map((item, index) => (
{/* {visibleSteps.map((item, index) => (
<StepsItem
key={index}
active={currentStep === index}
item={item}
></StepsItem>
))}
))} */}
<Steps
current={currentStep}
items={visibleSteps}
type="panel"
styles={{
item: {
paddingBlock: 0
},
itemRail: {
borderColor: 'var(--ant-color-split)'
}
}}
></Steps>
</Wrapper>
</Box>
);
@@ -58,7 +58,7 @@ const FooterButtons: React.FC<FooterButtonsProps> = (props) => {
<ArrowRightOutlined />
</Button>
)}
{showButtons.skip && (
{showButtons.done && (
<Button type="primary" onClick={handleCancel}>
{intl.formatMessage({ id: 'common.button.done' })}
</Button>
@@ -70,8 +70,7 @@ const FooterButtons: React.FC<FooterButtonsProps> = (props) => {
style={{ minWidth: 88 }}
loading={loading}
>
{/* {intl.formatMessage({ id: 'common.button.save' })} */}
Create & Continue
{intl.formatMessage({ id: 'common.button.save' })}
</Button>
)}
</div>
@@ -0,0 +1,54 @@
import { CheckCircleFilled } from '@ant-design/icons';
import { Button, Flex, Result } from 'antd';
import React from 'react';
import { ProviderValueMap } from '../config';
const SuccessResult: React.FC<{
provider: string;
onSkip: () => void;
onAddWorker: () => void;
}> = ({ provider, onSkip, onAddWorker }) => {
const k8sTips =
'Next, register this Kubernetes cluster. You can also skip this step and register it later from the cluster list.';
const dockerTips = (
<div className="flex-column">
<span>Next, add worker to this cluster.</span>
<span>
You can also skip this step and add them later from the cluster list.
</span>
</div>
);
return (
<Result
styles={{
title: {
fontSize: 16,
fontWeight: 500
}
}}
icon={
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: 42 }}
/>
}
status="success"
title="Cluster Created Successfully!"
subTitle={provider === ProviderValueMap.Kubernetes ? k8sTips : dockerTips}
extra={[
<Flex gap={16} justify="center" key="buttons">
<Button key="buy" onClick={onSkip}>
Skip for now
</Button>
<Button type="primary" key="console" onClick={onAddWorker}>
{provider === ProviderValueMap.Kubernetes
? 'Register Cluster'
: 'Add Worker'}
</Button>
</Flex>
]}
/>
);
};
export default SuccessResult;
@@ -44,7 +44,6 @@ const Box = styled.div`
font-size: 10px;
font-weight: 400;
background-color: var(--ant-blue-1);
color: var(--ant-color-info);
}
}
`;
+6 -1
View File
@@ -1,4 +1,4 @@
import { fromClusterCreationAtom } from '@/atoms/clusters';
import { clusterSessionAtom, fromClusterCreationAtom } from '@/atoms/clusters';
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
@@ -62,6 +62,7 @@ const Credentials: React.FC = () => {
deleteAPI: deleteCredential,
contentForDelete: 'menu.clusterManagement.credentials'
});
const [, setClusterSession] = useAtom(clusterSessionAtom);
const [isFromCluster, setIsFromCluster] = useAtom(fromClusterCreationAtom);
const intl = useIntl();
const navigate = useNavigate();
@@ -109,6 +110,10 @@ const Credentials: React.FC = () => {
} else {
await createCredential({ data: params });
if (isFromCluster) {
setClusterSession({
firstAddWorker: false,
firstAddCluster: true
});
navigate(-1);
}
}
@@ -1,6 +1,7 @@
import React from 'react';
import AddWorkerStep from '../components/add-worker/add-worker-step';
import ProviderCatalog from '../components/provider-catalog';
import SuccessResult from '../components/success-result';
import BasicForm from './basic-form';
import WorkerPoolForm from './worker-pools-form';
@@ -8,12 +9,14 @@ export const moduleMap = {
BasicForm: 'BasicForm',
WorkerPoolForm: 'WorkerPoolForm',
ProviderCatalog: 'ProviderCatalog',
AddWorkerStep: 'AddWorkerStep'
AddWorkerStep: 'AddWorkerStep',
SuccessResult: 'SuccessResult'
};
export const moduleRegistry: Record<string, React.ComponentType<any>> = {
[moduleMap.BasicForm]: BasicForm,
[moduleMap.WorkerPoolForm]: WorkerPoolForm,
[moduleMap.ProviderCatalog]: ProviderCatalog,
[moduleMap.AddWorkerStep]: AddWorkerStep
[moduleMap.AddWorkerStep]: AddWorkerStep,
[moduleMap.SuccessResult]: SuccessResult
};
@@ -15,9 +15,9 @@ export default function useStepList() {
return useMemo(
() => [
{
title: intl.formatMessage({ id: 'clusters.table.provider' }),
content: 'Choose where your cluster will run.',
subTitle: 'Select Provider',
title: intl.formatMessage({ id: 'clusters.create.selectProvider' }),
content: '',
subTitle: '',
description:
'Choose the environment where your cluster will be deployed.',
showButtons: (provider?: ProviderType) => {
@@ -25,7 +25,8 @@ export default function useStepList() {
previous: false,
next: true,
save: false,
skip: false
skip: false,
done: false
};
},
defaultShow: true,
@@ -35,7 +36,7 @@ export default function useStepList() {
},
{
title: intl.formatMessage({ id: 'common.title.config' }),
content: 'Configure your cluster settings.',
content: '',
description:
'Set the cluster name, description, or other essential parameters in advanced settings.',
showButtons: (provider?: ProviderType) => {
@@ -43,7 +44,8 @@ export default function useStepList() {
previous: true,
next: provider === ProviderValueMap.DigitalOcean,
save: provider !== ProviderValueMap.DigitalOcean,
skip: false
skip: false,
done: false
};
},
defaultShow: true,
@@ -53,13 +55,14 @@ export default function useStepList() {
},
{
title: intl.formatMessage({ id: 'clusters.button.addNodePool' }),
content: 'Add worker pools to your cluster.',
content: '',
showButtons: (provider?: ProviderType) => {
return {
previous: true,
next: false,
save: true,
skip: false
skip: false,
done: false
};
},
defaultShow: false,
@@ -69,37 +72,57 @@ export default function useStepList() {
beforeNext: handleBack
},
{
title: intl.formatMessage({ id: 'resources.button.create' }),
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.',
title: 'Completed',
content: '',
description: '',
showButtons: (provider?: ProviderType) => {
return {
previous: false,
next: false,
save: false,
skip: true
skip: true,
done: false
};
},
defaultShow: false,
showForms: [],
showModules: [moduleMap.SuccessResult],
providers: [ProviderValueMap.Docker, ProviderValueMap.Kubernetes]
},
{
title: intl.formatMessage({ id: 'resources.button.create' }),
content: '',
description: '',
showButtons: (provider?: ProviderType) => {
return {
previous: false,
next: false,
save: false,
skip: false,
done: true
};
},
defaultShow: false,
hideInSteps: true,
showForms: [],
showModules: [moduleMap.AddWorkerStep],
providers: [ProviderValueMap.Docker]
},
{
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.',
description: '',
showButtons: (provider?: ProviderType) => {
return {
previous: false,
next: false,
save: false,
skip: true
skip: false,
done: true
};
},
defaultShow: false,
hideInSteps: true,
showForms: [],
showModules: [moduleMap.AddWorkerStep],
providers: [ProviderValueMap.Kubernetes]
@@ -179,7 +179,7 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
return (
<div>
<PageTools
marginBottom={22}
marginBottom={12}
marginTop={0}
// left={
// <span className="flex-center gap-16">
@@ -189,7 +189,12 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
// </span>
// }
left={
<Button onClick={handleAddPool} type="link" icon={<PlusOutlined />}>
<Button
onClick={handleAddPool}
type="link"
icon={<PlusOutlined />}
style={{ paddingInline: 0 }}
>
{intl.formatMessage({ id: 'clusters.button.addNodePool' })}
</Button>
}
+3 -2
View File
@@ -414,7 +414,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
}
// TODO
// fetchContextLength({ ...params, source });
fetchContextLength({ ...params, source });
}, [isGGUF, source, localPath, modelScopeModelId, huggingfaceRepoId]);
return (
@@ -480,7 +480,8 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
initialValues?.speculative_config?.ngram_max_match_length || 10
},
...initialValues,
backend_version: initialValues?.backend_version || null
backend_version: initialValues?.backend_version || null,
max_context_len: initialValues?.max_context_len || 2048
}}
>
<BasicForm
@@ -261,7 +261,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef((props, ref) => {
placement: 'right'
}}
minWidth={60}
maxWidth={180}
maxWidth={220}
>
{data.label}
</AutoTooltip>
@@ -275,7 +275,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef((props, ref) => {
placement: 'right'
}}
minWidth={60}
maxWidth={180}
maxWidth={220}
>
{data.label}
</AutoTooltip>
+1 -1
View File
@@ -16,7 +16,7 @@
width: 100%;
.title {
max-width: min(100%, 180px);
max-width: min(100%, 230px);
min-width: min(120px, 32%);
}
}