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 { Button, Form } from 'antd'; import { useAtom } from 'jotai'; import _ from 'lodash'; import React, { forwardRef, useEffect, useImperativeHandle, 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 NotFoundImageContent = () => { const intl = useIntl(); return ( {intl.formatMessage({ id: 'clusters.create.noImages' })} ); }; 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) { form.setFieldsValue({ ...currentData }); setInstanceSpec({ ...currentData.instance_spec }); } }, [currentData]); const imageLabelRender = (data: { label: React.ReactNode; value: string | number; }) => { if (action === PageAction.EDIT) { const vendor = _.split(currentData?.image_name || '', ' ')[0]; const iconType = _.get(vendorIconMap, vendor.toLowerCase()); return (
{iconType && } {currentData?.image_name || currentData?.os_image}
); } const selectImage = osImageList.find((item) => item.value === data.value); if (selectImage) { return ( ); } return data.value; }; const instanceLabelRender = (data: { label: React.ReactNode; value: string | number; }) => { const currentInstanceSpec = instanceTypeList.find((item) => item.value === data.value) || instanceSpec; return ( ); }; const handleOsImageChange = (value: string) => { form.setFieldsValue({ image_name: osImageList.find((item) => item.value === value)?.label || value }); }; const handleInstanceTypeChange = (value: string, option: any) => { setInstanceSpec({ ...option.specInfo, label: option.label, vendor: option.vendor, description: option.description }); form.setFieldsValue({ instance_spec: { ...option.specInfo, label: option.label, vendor: option.vendor, description: option.description } }); }; 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" > ) } >
name="name" rules={[ { required: true, message: getRuleMessage('input', 'common.table.name') } ]} > name="instance_type" rules={[ { required: true, message: getRuleMessage( 'input', 'clusters.workerpool.instanceType' ) } ]} > } label={intl.formatMessage({ id: 'clusters.workerpool.instanceType' })} required options={instanceTypeList} disabled={action === PageAction.EDIT} optionRender={RenderOption} onChange={handleInstanceTypeChange} > name="replicas" rules={[ { required: true, message: getRuleMessage('input', 'clusters.workerpool.replicas') } ]} > name="batch_size" rules={[ { required: true, message: getRuleMessage( 'input', 'clusters.workerpool.batchSize' ) } ]} > name="os_image" rules={[ { required: true, message: getRuleMessage('input', 'clusters.workerpool.osImage') } ]} > RenderOption({ ...option, styles: { header: { marginBlock: 5 } } }) } labelRender={imageLabelRender} placeholder={currentData?.image_name} options={osImageList} disabled={action === PageAction.EDIT} label={intl.formatMessage({ id: 'clusters.workerpool.osImage' })} required > name="labels" rules={[ ({ getFieldValue }) => ({ validator(rule, value) { if (_.keys(value).length > 0) { if (_.some(_.keys(value), (k: string) => !value[k])) { return Promise.reject( intl.formatMessage( { id: 'common.validate.value' }, { name: 'labels' } ) ); } } return Promise.resolve(); } }) ]} > name="image_name" hidden>
); }); export default PoolForm;