import {
regionInstanceTypeListAtom,
regionOSImageListAtom
} from '@/atoms/clusters';
import CollapsibleContainer, {
CollapsibleContainerProps
} from '@/components/collapse-container';
import IconFont from '@/components/icon-font';
import LabelSelector from '@/components/label-selector';
import AutoComplete from '@/components/seal-form/auto-complete';
import SealInputNumber from '@/components/seal-form/input-number';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
import { CardContainer } from '@/pages/llmodels/components/gpu-card';
import { DeleteOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Button, Form } from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash';
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useState
} from 'react';
import styled from 'styled-components';
import { ProviderType, instanceTypeFieldMap, vendorIconMap } from '../config';
import { NodePoolFormData as FormData } from '../config/types';
import VolumesConfig from './volumes-config';
const Container = styled.div`
pointer-events: auto;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 0 16px;
.ant-form-item:nth-child(1) {
grid-column: 1 / 3;
}
.ant-form-item:nth-child(2) {
grid-column: 1 / 3;
}
.ant-form-item:nth-child(5) {
grid-column: 1 / 3;
}
.ant-form-item:nth-child(6) {
grid-column: 1 / 3;
}
.ant-form-item:nth-child(7) {
grid-column: 1 / 3;
}
`;
const NoContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding-block: 12px;
`;
const OptionItem = styled.div.attrs({
className: 'option-item'
})`
display: flex;
gap: 8px;
align-items: center;
`;
const DescriptionWrapper = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 4px 8px;
font-weight: 400;
`;
const NotFoundContent = () => {
const intl = useIntl();
return (
{intl.formatMessage({ id: 'clusters.create.noInstanceTypes' })}
);
};
const RenderLabel = (data: {
label: React.ReactNode;
vendor: string;
style?: React.CSSProperties;
}) => {
const { label, vendor, style } = data;
return (
{label}
);
};
export const RenderOption = (option: any) => {
const { data = {}, styles } = option;
const entries = Object.entries(data?.specInfo || {});
return (
}
description={
entries.length > 0 && (
{Object.entries(data?.specInfo)
.filter(([key, value]) => value)
.map(([key, value]) => (
{_.get(instanceTypeFieldMap, key, key)}:
{value as string}
))}
)
}
/>
);
};
type AddModalProps = {
ref: any;
name?: string;
action: PageActionType;
provider: ProviderType; // 'kubernetes' | 'custom' | 'digitalocean';
currentData?: FormData | null;
onFinish: (values: FormData) => void;
onDelete?: () => void;
showDelete?: boolean;
collapseProps?: CollapsibleContainerProps;
};
const InstanceSpecData: React.FC<{ instanceSpec: Record }> = ({
instanceSpec
}) => {
return (
<>
{Object.entries(instanceSpec)
.filter(([key, value]) => value)
.map(([key, value]) => (
))}
>
);
};
const PoolForm: React.FC = forwardRef((props, ref) => {
const {
action,
name = 'workerPoolForm',
onFinish,
onDelete,
showDelete,
currentData,
collapseProps
} = props;
const [instanceTypeList] = useAtom(regionInstanceTypeListAtom);
const [osImageList] = useAtom(regionOSImageListAtom);
const { collapsible, onToggle, ...restCollapseProps } = collapseProps || {};
const [form] = Form.useForm();
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const labels = Form.useWatch('labels', form);
const title = Form.useWatch('name', form);
const [instanceSpec, setInstanceSpec] = useState>({});
useEffect(() => {
if (currentData) {
// when change the region, shoudle check the instance type and the os Image.
const selectOSImage = osImageList.find(
(item) => item.os_image === currentData.os_image
);
const selectInstanceType = instanceTypeList.find(
(item) => item.value === currentData.instance_type
);
form.setFieldsValue({
...currentData,
instance_type: selectInstanceType?.value || '',
os_image: selectOSImage?.os_image || '',
image_name: selectOSImage?.value || ''
});
setInstanceSpec(() => {
return selectInstanceType ? currentData.instance_spec : {};
});
}
}, [currentData, instanceTypeList, osImageList]);
const updateImageList = useMemoizedFn((instanceSpec: Record) => {
if (instanceSpec.count === 8) {
return osImageList.filter((item) => item.os_image === 'gpu-h100x8-base');
}
if (instanceSpec.count === 1 && instanceSpec.vendor === 'amd') {
return osImageList.filter((item) => item.os_image === 'gpu-amd-base');
}
if (instanceSpec.count === 1 && instanceSpec.vendor === 'nvidia') {
return osImageList.filter((item) => item.os_image === 'gpu-h100x1-base');
}
return osImageList;
});
const imageList = useMemo(() => {
return updateImageList(instanceSpec);
}, [osImageList, instanceSpec, updateImageList]);
const instanceLabelRender = (data: {
label: React.ReactNode;
value: string | number;
}) => {
const currentInstanceSpec =
instanceTypeList.find((item) => item.value === data.value) ||
instanceSpec;
if (!currentInstanceSpec || _.isEmpty(currentInstanceSpec)) {
return null;
}
return (
);
};
const handleOsImageChange = (value: string, option: any) => {
form.setFieldsValue({
os_image: option.os_image || value
});
};
const handleInstanceTypeChange = (value: string, option: any) => {
setInstanceSpec({
...option.specInfo,
label: option.label,
vendor: option.vendor,
description: option.description,
count: option.count
});
const newImageList = updateImageList({
...option.specInfo,
label: option.label,
vendor: option.vendor,
description: option.description,
count: option.count
});
form.setFieldsValue({
os_image: newImageList[0]?.os_image,
image_name: newImageList[0]?.value,
instance_spec: {
...option.specInfo,
label: option.label,
vendor: option.vendor,
description: option.description,
count: option.count
}
});
};
const filterInstanceOption = (inputValue: string, option: any) => {
return (
option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
option.description.toLowerCase().includes(inputValue.toLowerCase())
);
};
useImperativeHandle(ref, () => ({
resetFields: () => {
form.resetFields();
},
submit: () => {
form.submit();
},
setFieldsValue: (values: any) => {
form.setFieldsValue(values);
},
getFieldsValue: () => {
return form.getFieldsValue();
},
validateFields: async () => {
return await form.validateFields();
}
}));
return (
}
danger
type="text"
variant="filled"
color="danger"
size="small"
>
)
}
>
);
});
export default PoolForm;