import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign'; import * as React from 'react'; import { useIntl } from 'react-intl'; import api from '../../api'; import { regClusterName, regUsername } from '../../constants/reg'; import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config'; import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem'; const rows = 4; const lowKafkaVersion = '2.8.0'; const clientPropertiesPlaceholder = `用于创建Kafka客户端进行信息获取的相关配置, 例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置, 未开启安全管控可不进行任何输入: { "security.protocol": "SASL_PLAINTEXT", "sasl.mechanism": "SCRAM-SHA-256", "sasl.jaas.config": "org.apache.kafka.common.security. scram.ScramLoginModule required username=\\"xxxxxx\\" pass word=\\"xxxxxx\\";" } `; const AccessClusters = (props: any): JSX.Element => { const intl = useIntl(); const [form] = Form.useForm(); const { afterSubmitSuccess, infoLoading, clusterInfo, visible } = props; const [loading, setLoading] = React.useState(false); const [security, setSecurity] = React.useState(clusterInfo?.security || 'None'); const [extra, setExtra] = React.useState({ versionExtra: '', zooKeeperExtra: '', bootstrapExtra: '', jmxExtra: '', }); const [isLowVersion, setIsLowVersion] = React.useState(false); const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState(false); const lastFormItemValue = React.useRef({ bootstrap: clusterInfo?.bootstrapServers || '', zookeeper: clusterInfo?.zookeeper || '', clientProperties: clusterInfo?.clientProperties || {}, }); React.useEffect(() => { const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion); lastFormItemValue.current.bootstrap = clusterInfo?.bootstrapServers || ''; lastFormItemValue.current.zookeeper = clusterInfo?.zookeeper || ''; lastFormItemValue.current.clientProperties = clusterInfo?.clientProperties || {}; setIsLowVersion(showLowVersion); setExtra({ ...extra, versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '', }); form.setFieldsValue({ ...clusterInfo }); }, [clusterInfo]); const onHandleValuesChange = (value: any, allValues: any) => { Object.keys(value).forEach((key) => { switch (key) { case 'security': setSecurity(value.security); break; case 'zookeeper': setExtra({ ...extra, zooKeeperExtra: '', bootstrapExtra: '', jmxExtra: '', }); break; case 'bootstrapServers': setExtra({ ...extra, zooKeeperExtra: '', bootstrapExtra: '', jmxExtra: '', }); break; case 'kafkaVersion': setExtra({ ...extra, versionExtra: '', }); break; } }); }; const onCancel = () => { form.resetFields(); setLoading(false); setZookeeperErrorStatus(false); setIsLowVersion(false); setSecurity('None'); setExtra({ versionExtra: '', zooKeeperExtra: '', bootstrapExtra: '', jmxExtra: '', }); lastFormItemValue.current = { bootstrap: '', zookeeper: '', clientProperties: {} }; props.setVisible && props.setVisible(false); }; const onSubmit = () => { form.validateFields().then((res) => { let clientProperties = null; try { clientProperties = res.clientProperties && JSON.parse(res.clientProperties); } catch (err) { console.error(err); } const params = { bootstrapServers: res.bootstrapServers, clientProperties: clientProperties || {}, description: res.description || '', jmxProperties: { jmxPort: res.jmxPort, maxConn: res.maxConn, openSSL: res.security === 'Password', token: res.token, username: res.username, }, kafkaVersion: res.kafkaVersion, name: res.name, zookeeper: res.zookeeper || '', }; setLoading(true); if (!isNaN(clusterInfo?.id)) { Utils.put(api.phyCluster, { ...params, id: clusterInfo?.id, }) .then(() => { message.success('编辑成功'); afterSubmitSuccess && afterSubmitSuccess(); onCancel(); }) .finally(() => { setLoading(false); }); } else { Utils.post(api.phyCluster, params) .then(() => { message.success('集群接入成功。注意:新接入集群数据稳定需要1-2分钟'); afterSubmitSuccess && afterSubmitSuccess(); onCancel(); }) .finally(() => { setLoading(false); }); } }); }; const connectTest = () => { const bootstrapServers = form.getFieldValue('bootstrapServers'); const zookeeper = form.getFieldValue('zookeeper'); let clientProperties = {}; try { clientProperties = form.getFieldValue('clientProperties') && JSON.parse(form.getFieldValue('clientProperties')); } catch (err) { console.error(`JSON.parse(form.getFieldValue('clientProperties')) ERROR: ${err}`); } setLoading(true); setIsLowVersion(false); setZookeeperErrorStatus(false); return Utils.post(api.kafkaValidator, { bootstrapServers: bootstrapServers || '', zookeeper: zookeeper || '', clientProperties, }) .then((res: any) => { form.setFieldsValue({ jmxPort: res.jmxPort, }); if (props.kafkaVersion.indexOf(res.kafkaVersion) > -1) { form.setFieldsValue({ kafkaVersion: res.kafkaVersion, }); } else { form.setFieldsValue({ kafkaVersion: undefined, }); } form.setFieldsValue({ zookeeper: zookeeper || res.zookeeper, }); const errList = res.errList || []; const extraMsg = extra; // 初始化信息为连接成功 extraMsg.bootstrapExtra = bootstrapServers ? '连接成功' : ''; extraMsg.zooKeeperExtra = zookeeper ? '连接成功' : ''; // 处理错误信息 errList.forEach((item: any) => { const { code, message } = item; let modifyKey: 'bootstrapExtra' | 'zooKeeperExtra' | 'jmxExtra' | undefined; if (bootstrapServersErrCodes.includes(code)) { modifyKey = 'bootstrapExtra'; } else if (zkErrCodes.includes(code)) { modifyKey = 'zooKeeperExtra'; } else if (jmxErrCodes.includes(code)) { modifyKey = 'jmxExtra'; } if (modifyKey) { extraMsg[modifyKey] = `连接失败。${message}`; } }); // 如果kafkaVersion小于最低版本则提示 const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion); setIsLowVersion(showLowVersion); setExtra({ ...extraMsg, versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '', }); return res; }) .finally(() => { setLoading(false); }); }; return ( <> } title={intl.formatMessage({ id: props.title || 'access.cluster' })} visible={props.visible} placement="right" width={480} >
{ if (!value) { return Promise.reject('集群名称不能为空'); } if (value === clusterInfo?.name) { return Promise.resolve(); } if (value?.length > 128) { return Promise.reject('集群名称长度限制在1~128字符'); } if (!new RegExp(regClusterName).test(value)) { return Promise.reject( '集群名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~' ); } return Utils.request(api.getClusterBasicExit(value)).then((res: any) => { const data = res || {}; if (data?.exist) { return Promise.reject('集群名称重复'); } else { return Promise.resolve(); } }); }, }, ]} > {extra.bootstrapExtra} ) : ( {extra.bootstrapExtra} ) } validateTrigger={'onBlur'} rules={[ { required: true, validator: async (rule: any, value: string) => { if (!value) { return Promise.reject('Bootstrap Servers不能为空'); } if (value.length > 2000) { return Promise.reject('Bootstrap Servers长度限制在2000字符'); } if (value && value !== lastFormItemValue.current.bootstrap) { return connectTest() .then((res: any) => { lastFormItemValue.current.bootstrap = value; return Promise.resolve(''); }) .catch((err) => { return Promise.reject('连接失败'); }); } return Promise.resolve(''); }, }, ]} > {extra.zooKeeperExtra} ) : ( {extra.zooKeeperExtra} ) } validateStatus={zookeeperErrorStatus ? 'error' : 'success'} validateTrigger={'onBlur'} rules={[ { required: false, validator: async (rule: any, value: string) => { if (!value) { setZookeeperErrorStatus(false); return Promise.resolve(''); } if (value.length > 2000) { return Promise.reject('Zookeeper长度限制在2000字符'); } if (value && value !== lastFormItemValue.current.zookeeper) { return connectTest() .then((res: any) => { lastFormItemValue.current.zookeeper = value; setZookeeperErrorStatus(false); return Promise.resolve(''); }) .catch((err) => { setZookeeperErrorStatus(true); return Promise.reject('连接失败'); }); } return Promise.resolve(''); }, }, ]} > <> None Password Authentication {security === 'Password' ? ( <> { if (!value) { return Promise.reject('用户名不能为空'); } if (!new RegExp(regUsername).test(value)) { return Promise.reject('仅支持大小写、下划线、短划线(-)'); } if (value.length > 128) { return Promise.reject('用户名长度限制在1~128字符'); } return Promise.resolve(); }, }, ]} > { if (!value) { return Promise.reject('密码不能为空'); } if (!new RegExp(regUsername).test(value)) { return Promise.reject('密码只能由大小写、下划线、短划线(-)组成'); } if (value.length < 6 || value.length > 32) { return Promise.reject('密码长度限制在6~32字符'); } return Promise.resolve(); }, }, ]} > ) : null} {extra.versionExtra}} validateStatus={isLowVersion ? 'error' : 'success'} rules={[ { required: true, validator: async (rule: any, value: any) => { if (!value) { setIsLowVersion(true); return Promise.reject('版本号不能为空'); } // 检测版本号小于2.8.0,如果没有填zookeeper信息,才会提示 const zookeeper = form.getFieldValue('zookeeper'); if (value < lowKafkaVersion && !zookeeper) { setIsLowVersion(true); setExtra({ ...extra, versionExtra: intl.formatMessage({ id: 'access.cluster.low.version.tip' }), }); return Promise.resolve(); } setIsLowVersion(false); return Promise.resolve(); }, }, ]} > ({ validator(_, value) { try { if (value) { JSON.parse(value); } return Promise.resolve(); } catch (e) { return Promise.reject(new Error('输入内容必须为 JSON')); } }, }), ]} >
{ form.setFieldsValue({ clientProperties }); form.validateFields(['clientProperties']); }} onBlur={(value: any) => { form.validateFields(['clientProperties']).then(() => { const bootstrapServers = form.getFieldValue('bootstrapServers'); const zookeeper = form.getFieldValue('zookeeper'); const clientProperties = form.getFieldValue('clientProperties'); if ( clientProperties && clientProperties !== lastFormItemValue.current.clientProperties && (!!bootstrapServers || !!zookeeper) ) { connectTest() .then((res: any) => { lastFormItemValue.current.clientProperties = clientProperties; }) .catch((err) => { message.error('连接失败'); }); } }); }} />
{ if (!value) { return Promise.resolve(''); } if (value && value.length > 200) { return Promise.reject('集群描述长度限制在200字符'); } return Promise.resolve(); }, }, ]} >
); }; export default AccessClusters;