refactor: create cluster by steps

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent 6e5a7035c8
commit 8de272239e
48 changed files with 1392 additions and 227 deletions
@@ -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;