mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-03 19:38:20 +08:00
refactor: 接入/编辑集群优化
This commit is contained in:
@@ -6,9 +6,8 @@ import { regClusterName, regUsername } from '@src/constants/reg';
|
|||||||
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
|
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
|
||||||
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
|
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
|
||||||
|
|
||||||
const rows = 4;
|
const LOW_KAFKA_VERSION = '2.8.0';
|
||||||
const lowKafkaVersion = '2.8.0';
|
const CLIENT_PROPERTIES_PLACEHOLDER = `用于创建Kafka客户端进行信息获取的相关配置,
|
||||||
const clientPropertiesPlaceholder = `用于创建Kafka客户端进行信息获取的相关配置,
|
|
||||||
例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置,
|
例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置,
|
||||||
未开启安全管控可不进行任何输入:
|
未开启安全管控可不进行任何输入:
|
||||||
{
|
{
|
||||||
@@ -26,37 +25,25 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const [confirmLoading, setConfirmLoading] = React.useState(false);
|
||||||
const [curClusterInfo, setCurClusterInfo] = React.useState<any>({});
|
const [curClusterInfo, setCurClusterInfo] = React.useState<any>({});
|
||||||
const [security, setSecurity] = React.useState(curClusterInfo?.security || 'None');
|
|
||||||
const [extra, setExtra] = React.useState({
|
const [extra, setExtra] = React.useState({
|
||||||
versionExtra: '',
|
versionExtra: '',
|
||||||
zooKeeperExtra: '',
|
zooKeeperExtra: '',
|
||||||
bootstrapExtra: '',
|
bootstrapExtra: '',
|
||||||
jmxExtra: '',
|
jmxExtra: '',
|
||||||
});
|
});
|
||||||
const [isLowVersion, setIsLowVersion] = React.useState<boolean>(false);
|
|
||||||
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<boolean>(false);
|
|
||||||
|
|
||||||
const lastFormItemValue = React.useRef({
|
const lastFormItemValue = React.useRef({
|
||||||
bootstrap: curClusterInfo?.bootstrapServers || '',
|
bootstrapServers: curClusterInfo?.bootstrapServers || '',
|
||||||
zookeeper: curClusterInfo?.zookeeper || '',
|
zookeeper: curClusterInfo?.zookeeper || '',
|
||||||
clientProperties: curClusterInfo?.clientProperties || {},
|
clientProperties: curClusterInfo?.clientProperties || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onHandleValuesChange = (value: any, allValues: any) => {
|
const onHandleValuesChange = (changedValue: string[]) => {
|
||||||
Object.keys(value).forEach((key) => {
|
Object.keys(changedValue).forEach((key) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'security':
|
|
||||||
setSecurity(value.security);
|
|
||||||
break;
|
|
||||||
case 'zookeeper':
|
case 'zookeeper':
|
||||||
setExtra({
|
|
||||||
...extra,
|
|
||||||
zooKeeperExtra: '',
|
|
||||||
bootstrapExtra: '',
|
|
||||||
jmxExtra: '',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'bootstrapServers':
|
case 'bootstrapServers':
|
||||||
setExtra({
|
setExtra({
|
||||||
...extra,
|
...extra,
|
||||||
@@ -78,21 +65,19 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setZookeeperErrorStatus(false);
|
|
||||||
setIsLowVersion(false);
|
|
||||||
setSecurity('None');
|
|
||||||
setExtra({
|
setExtra({
|
||||||
versionExtra: '',
|
versionExtra: '',
|
||||||
zooKeeperExtra: '',
|
zooKeeperExtra: '',
|
||||||
bootstrapExtra: '',
|
bootstrapExtra: '',
|
||||||
jmxExtra: '',
|
jmxExtra: '',
|
||||||
});
|
});
|
||||||
lastFormItemValue.current = { bootstrap: '', zookeeper: '', clientProperties: {} };
|
lastFormItemValue.current = { bootstrapServers: '', zookeeper: '', clientProperties: {} };
|
||||||
props.setVisible && props.setVisible(false);
|
props.setVisible && props.setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
form.validateFields().then((res) => {
|
form.validateFields().then((res) => {
|
||||||
|
setConfirmLoading(true);
|
||||||
let clientProperties = null;
|
let clientProperties = null;
|
||||||
try {
|
try {
|
||||||
clientProperties = res.clientProperties && JSON.parse(res.clientProperties);
|
clientProperties = res.clientProperties && JSON.parse(res.clientProperties);
|
||||||
@@ -107,7 +92,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
jmxProperties: {
|
jmxProperties: {
|
||||||
jmxPort: res.jmxPort,
|
jmxPort: res.jmxPort,
|
||||||
maxConn: res.maxConn,
|
maxConn: res.maxConn,
|
||||||
openSSL: res.security === 'Password',
|
openSSL: res.openSSL || false,
|
||||||
token: res.token,
|
token: res.token,
|
||||||
username: res.username,
|
username: res.username,
|
||||||
},
|
},
|
||||||
@@ -115,7 +100,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
name: res.name,
|
name: res.name,
|
||||||
zookeeper: res.zookeeper || '',
|
zookeeper: res.zookeeper || '',
|
||||||
};
|
};
|
||||||
setLoading(true);
|
|
||||||
if (!isNaN(curClusterInfo?.id)) {
|
if (!isNaN(curClusterInfo?.id)) {
|
||||||
Utils.put(api.phyCluster, {
|
Utils.put(api.phyCluster, {
|
||||||
...params,
|
...params,
|
||||||
@@ -127,7 +112,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
onCancel();
|
onCancel();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setConfirmLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Utils.post(api.phyCluster, params)
|
Utils.post(api.phyCluster, params)
|
||||||
@@ -137,7 +122,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
onCancel();
|
onCancel();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setConfirmLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -154,125 +139,224 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setIsLowVersion(false);
|
|
||||||
setZookeeperErrorStatus(false);
|
|
||||||
|
|
||||||
return Utils.post(api.kafkaValidator, {
|
return Utils.post(api.kafkaValidator, {
|
||||||
bootstrapServers: bootstrapServers || '',
|
bootstrapServers: bootstrapServers || '',
|
||||||
zookeeper: zookeeper || '',
|
zookeeper: zookeeper || '',
|
||||||
clientProperties,
|
clientProperties,
|
||||||
})
|
})
|
||||||
.then((res: any) => {
|
.then(
|
||||||
form.setFieldsValue({
|
(res: {
|
||||||
jmxPort: res.jmxPort,
|
errList: { code: number; message: string; data: any }[];
|
||||||
});
|
jmxPort: number | null;
|
||||||
|
kafkaVersion: string | null;
|
||||||
|
zookeeper: string | null;
|
||||||
|
}) => {
|
||||||
|
const changedValue: { jmxPort?: number; kafkaVersion?: string; zookeeper: string } = {
|
||||||
|
zookeeper: zookeeper || res.zookeeper,
|
||||||
|
};
|
||||||
|
if (res.kafkaVersion && props.kafkaVersion.includes(res.kafkaVersion)) {
|
||||||
|
changedValue.kafkaVersion = res.kafkaVersion;
|
||||||
|
}
|
||||||
|
if (res.jmxPort) {
|
||||||
|
changedValue.jmxPort = res.jmxPort;
|
||||||
|
}
|
||||||
|
form.setFieldsValue(changedValue);
|
||||||
|
|
||||||
if (props.kafkaVersion.indexOf(res.kafkaVersion) > -1) {
|
const extraMsg = {
|
||||||
form.setFieldsValue({
|
...extra,
|
||||||
kafkaVersion: res.kafkaVersion,
|
// 重置默认信息为连接成功
|
||||||
});
|
bootstrapExtra: bootstrapServers ? '连接成功' : '',
|
||||||
} else {
|
zooKeeperExtra: zookeeper ? '连接成功' : '',
|
||||||
form.setFieldsValue({
|
};
|
||||||
kafkaVersion: undefined,
|
|
||||||
|
const errList = res.errList || [];
|
||||||
|
// 处理错误信息
|
||||||
|
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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setExtra(extraMsg);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
)
|
||||||
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 = !(
|
|
||||||
curClusterInfo?.zookeeper ||
|
|
||||||
!curClusterInfo?.kafkaVersion ||
|
|
||||||
curClusterInfo?.kafkaVersion >= lowKafkaVersion
|
|
||||||
);
|
|
||||||
setIsLowVersion(showLowVersion);
|
|
||||||
setExtra({
|
|
||||||
...extraMsg,
|
|
||||||
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 更新表单状态
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const showLowVersion = !(curClusterInfo?.zookeeper || !curClusterInfo?.kafkaVersion || curClusterInfo?.kafkaVersion >= lowKafkaVersion);
|
|
||||||
lastFormItemValue.current = {
|
lastFormItemValue.current = {
|
||||||
bootstrap: curClusterInfo?.bootstrapServers || '',
|
bootstrapServers: curClusterInfo?.bootstrapServers || '',
|
||||||
zookeeper: curClusterInfo?.zookeeper || '',
|
zookeeper: curClusterInfo?.zookeeper || '',
|
||||||
clientProperties: curClusterInfo?.clientProperties || {},
|
clientProperties: curClusterInfo?.clientProperties || {},
|
||||||
};
|
};
|
||||||
setIsLowVersion(showLowVersion);
|
|
||||||
setExtra({
|
|
||||||
...extra,
|
|
||||||
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
|
|
||||||
});
|
|
||||||
form.setFieldsValue({ ...curClusterInfo });
|
form.setFieldsValue({ ...curClusterInfo });
|
||||||
|
if (curClusterInfo?.kafkaVersion) {
|
||||||
|
form.validateFields(['kafkaVersion']);
|
||||||
|
}
|
||||||
}, [curClusterInfo]);
|
}, [curClusterInfo]);
|
||||||
|
|
||||||
|
// 获取集群详情数据
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
if (clusterInfo?.id) {
|
if (clusterInfo?.id) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const resolveJmxProperties = (obj: any) => {
|
||||||
|
const res = { ...obj };
|
||||||
|
try {
|
||||||
|
const originValue = obj?.jmxProperties;
|
||||||
|
if (originValue) {
|
||||||
|
const jmxProperties = JSON.parse(originValue);
|
||||||
|
typeof jmxProperties === 'object' && jmxProperties !== null && Object.assign(res, jmxProperties);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('jmxProperties not JSON: ', err);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
Utils.request(api.getPhyClusterBasic(clusterInfo.id))
|
Utils.request(api.getPhyClusterBasic(clusterInfo.id))
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
let jmxProperties = null;
|
setCurClusterInfo(resolveJmxProperties(res));
|
||||||
try {
|
|
||||||
jmxProperties = JSON.parse(res?.jmxProperties);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转化值对应成表单值
|
|
||||||
if (jmxProperties?.openSSL) {
|
|
||||||
jmxProperties.security = 'Password';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jmxProperties) {
|
|
||||||
res = Object.assign({}, res || {}, jmxProperties);
|
|
||||||
}
|
|
||||||
setCurClusterInfo(res);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setCurClusterInfo(clusterInfo);
|
setCurClusterInfo(resolveJmxProperties(clusterInfo));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setCurClusterInfo(clusterInfo);
|
setCurClusterInfo({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [visible, clusterInfo]);
|
}, [visible, clusterInfo]);
|
||||||
|
|
||||||
|
const validators = {
|
||||||
|
name: async (_: any, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('集群名称不能为空');
|
||||||
|
}
|
||||||
|
if (value === curClusterInfo?.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 || {};
|
||||||
|
return data?.exist ? Promise.reject('集群名称重复') : Promise.resolve();
|
||||||
|
})
|
||||||
|
.catch(() => Promise.reject('连接超时! 请重试或检查服务'));
|
||||||
|
},
|
||||||
|
bootstrapServers: async (_: 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.bootstrapServers) {
|
||||||
|
lastFormItemValue.current.bootstrapServers = value;
|
||||||
|
return connectTest().catch(() => (lastFormItemValue.current.bootstrapServers = ''));
|
||||||
|
}
|
||||||
|
return Promise.resolve('');
|
||||||
|
},
|
||||||
|
zookeeper: async (_: any, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length > 2000) {
|
||||||
|
return Promise.reject('Zookeeper长度限制在2000字符');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && value !== lastFormItemValue.current.zookeeper) {
|
||||||
|
lastFormItemValue.current.zookeeper = value;
|
||||||
|
return connectTest().catch(() => (lastFormItemValue.current.zookeeper = ''));
|
||||||
|
}
|
||||||
|
return Promise.resolve('');
|
||||||
|
},
|
||||||
|
securityUserName: async (_: any, value: string) => {
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
securityToken: async (_: any, value: string) => {
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
kafkaVersion: async (_: any, value: any) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('版本号不能为空');
|
||||||
|
}
|
||||||
|
// 检测版本号小于2.8.0,如果没有填zookeeper信息,才会提示
|
||||||
|
const zookeeper = form.getFieldValue('zookeeper');
|
||||||
|
let versionExtra = '';
|
||||||
|
if (value < LOW_KAFKA_VERSION && !zookeeper) {
|
||||||
|
versionExtra = intl.formatMessage({ id: 'access.cluster.low.version.tip' });
|
||||||
|
}
|
||||||
|
setExtra({
|
||||||
|
...extra,
|
||||||
|
versionExtra,
|
||||||
|
});
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
clientProperties: async (_: any, value: string) => {
|
||||||
|
try {
|
||||||
|
if (value) {
|
||||||
|
JSON.parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(new Error('输入内容必须为 JSON'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: async (_: any, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve('');
|
||||||
|
}
|
||||||
|
if (value && value.length > 200) {
|
||||||
|
return Promise.reject('集群描述长度限制在200字符');
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -285,14 +369,14 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
<Button size="small" onClick={onCancel}>
|
<Button size="small" onClick={onCancel}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" type="primary" onClick={onSubmit}>
|
<Button size="small" type="primary" loading={confirmLoading} onClick={onSubmit}>
|
||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
title={intl.formatMessage({ id: props.title || 'access.cluster' })}
|
title={intl.formatMessage({ id: props.title || clusterInfo?.id ? 'edit.cluster' : 'access.cluster' })}
|
||||||
visible={props.visible}
|
visible={props.visible}
|
||||||
placement="right"
|
placement="right"
|
||||||
width={480}
|
width={480}
|
||||||
@@ -306,30 +390,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
validator: async (rule: any, value: string) => {
|
validator: validators.name,
|
||||||
if (!value) {
|
|
||||||
return Promise.reject('集群名称不能为空');
|
|
||||||
}
|
|
||||||
if (value === curClusterInfo?.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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -343,26 +404,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
validator: async (rule: any, value: string) => {
|
validator: validators.bootstrapServers,
|
||||||
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('');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -375,35 +417,10 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
name="zookeeper"
|
name="zookeeper"
|
||||||
label="Zookeeper"
|
label="Zookeeper"
|
||||||
extra={<span className={!extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
|
extra={<span className={!extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
|
||||||
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
|
|
||||||
validateTrigger={'onBlur'}
|
validateTrigger={'onBlur'}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
validator: validators.zookeeper,
|
||||||
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('');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -412,142 +429,65 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
placeholder="请输入Zookeeper地址,例如:192.168.0.1:2181,192.168.0.2:2181,192.168.0.2:2181/ks-kafka"
|
placeholder="请输入Zookeeper地址,例如:192.168.0.1:2181,192.168.0.2:2181,192.168.0.2:2181/ks-kafka"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item className="metrics-form-item" label="Metrics">
|
||||||
className="no-item-control"
|
<div className="horizontal-form-container">
|
||||||
name="Metrics"
|
<div className="inline-items">
|
||||||
label="Metrics"
|
<Form.Item name="jmxPort" label="JMX Port :" extra={extra.jmxExtra}>
|
||||||
rules={[
|
<InputNumber min={0} max={99999} style={{ width: 129 }} />
|
||||||
{
|
</Form.Item>
|
||||||
required: false,
|
<Form.Item name="maxConn" label="Max Conn :">
|
||||||
message: '',
|
<InputNumber addonAfter="个" min={0} max={99999} style={{ width: 124 }} />
|
||||||
},
|
</Form.Item>
|
||||||
]}
|
</div>
|
||||||
>
|
<Form.Item name="openSSL" label="Security :">
|
||||||
<></>
|
<Radio.Group>
|
||||||
</Form.Item>
|
<Radio value={false}>None</Radio>
|
||||||
<Form.Item
|
<Radio value={true}>Password Authentication</Radio>
|
||||||
name="jmxPort"
|
</Radio.Group>
|
||||||
label="JMX Port"
|
|
||||||
className="inline-item adjust-height-style"
|
|
||||||
extra={extra.jmxExtra}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: false,
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber style={{ width: 134 }} min={0} max={99999} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="maxConn"
|
|
||||||
label="MaxConn"
|
|
||||||
className="inline-item adjust-height-style"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: false,
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber style={{ width: 134 }} min={0} max={99999} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="security"
|
|
||||||
label="Security"
|
|
||||||
className="inline-item adjust-height-style"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: false,
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Radio.Group>
|
|
||||||
<Radio value="None">None</Radio>
|
|
||||||
<Radio value="Password">Password Authentication</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
{security === 'Password' ? (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
className="inline-item max-width-66"
|
|
||||||
name="username"
|
|
||||||
label="User Info"
|
|
||||||
style={{ width: '58%' }}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
|
||||||
validator: async (rule: any, value: string) => {
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input placeholder="请输入用户名" />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item dependencies={['openSSL']} noStyle>
|
||||||
className="inline-item"
|
{({ getFieldValue }) => {
|
||||||
name="token"
|
return getFieldValue('openSSL') ? (
|
||||||
label=""
|
<div className="user-info-form-items">
|
||||||
style={{ width: '38%', marginRight: 0 }}
|
<Form.Item className="user-info-label" label="User Info :" required />
|
||||||
rules={[
|
<div className="inline-items">
|
||||||
{
|
<Form.Item
|
||||||
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
name="username"
|
||||||
validator: async (rule: any, value: string) => {
|
rules={[
|
||||||
if (!value) {
|
{
|
||||||
return Promise.reject('密码不能为空');
|
validator: validators.securityUserName,
|
||||||
}
|
},
|
||||||
if (!new RegExp(regUsername).test(value)) {
|
]}
|
||||||
return Promise.reject('密码只能由大小写、下划线、短划线(-)组成');
|
>
|
||||||
}
|
<Input placeholder="请输入用户名" />
|
||||||
if (value.length < 6 || value.length > 32) {
|
</Form.Item>
|
||||||
return Promise.reject('密码长度限制在6~32字符');
|
<Form.Item
|
||||||
}
|
className="token-form-item"
|
||||||
return Promise.resolve();
|
name="token"
|
||||||
},
|
rules={[
|
||||||
},
|
{
|
||||||
]}
|
validator: validators.securityToken,
|
||||||
>
|
},
|
||||||
<Input placeholder="请输入密码" />
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入密码" />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</div>
|
||||||
) : null}
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="kafkaVersion"
|
name="kafkaVersion"
|
||||||
label="Version"
|
label="Version"
|
||||||
|
dependencies={['zookeeper']}
|
||||||
extra={<span className="error-extra-info">{extra.versionExtra}</span>}
|
extra={<span className="error-extra-info">{extra.versionExtra}</span>}
|
||||||
validateStatus={isLowVersion ? 'error' : 'success'}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
validator: async (rule: any, value: any) => {
|
validator: validators.kafkaVersion,
|
||||||
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();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -565,29 +505,15 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
label="集群配置"
|
label="集群配置"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
validator: validators.clientProperties,
|
||||||
message: '请输入集群配置',
|
|
||||||
},
|
},
|
||||||
() => ({
|
|
||||||
validator(_, value) {
|
|
||||||
try {
|
|
||||||
if (value) {
|
|
||||||
JSON.parse(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(new Error('输入内容必须为 JSON'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<CodeMirrorFormItem
|
<CodeMirrorFormItem
|
||||||
resize
|
resize
|
||||||
defaultInput={form.getFieldValue('clientProperties')}
|
defaultInput={form.getFieldValue('clientProperties')}
|
||||||
placeholder={clientPropertiesPlaceholder}
|
placeholder={CLIENT_PROPERTIES_PLACEHOLDER}
|
||||||
onBeforeChange={(clientProperties: string) => {
|
onBeforeChange={(clientProperties: string) => {
|
||||||
form.setFieldsValue({ clientProperties });
|
form.setFieldsValue({ clientProperties });
|
||||||
form.validateFields(['clientProperties']);
|
form.validateFields(['clientProperties']);
|
||||||
@@ -621,20 +547,11 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
label="集群描述"
|
label="集群描述"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
validator: validators.description,
|
||||||
validator: async (rule: any, value: string) => {
|
|
||||||
if (!value) {
|
|
||||||
return Promise.resolve('');
|
|
||||||
}
|
|
||||||
if (value && value.length > 200) {
|
|
||||||
return Promise.reject('集群描述长度限制在200字符');
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input.TextArea rows={rows} />
|
<Input.TextArea rows={4} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
@@ -656,43 +656,37 @@
|
|||||||
color: @error-color;
|
color: @error-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.inline-item.dcloud-form-item {
|
.horizontal-form-container {
|
||||||
display: -webkit-inline-box;
|
padding-left: 16px;
|
||||||
margin-right: 16px;
|
.inline-items {
|
||||||
|
display: flex;
|
||||||
&.adjust-height-style {
|
justify-content: space-between;
|
||||||
.dcloud-form-item-label {
|
|
||||||
padding: 0;
|
|
||||||
label {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dcloud-form-item-control {
|
|
||||||
&-input {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.dcloud-form-item {
|
||||||
&.max-width-66 {
|
flex-direction: row;
|
||||||
.dcloud-form-item-control {
|
align-items: center;
|
||||||
max-width: 66%;
|
&-label {
|
||||||
}
|
padding: 0 12px 0 0;
|
||||||
}
|
font-size: 13px;
|
||||||
|
|
||||||
.dcloud-form-item-label {
|
|
||||||
margin-right: 12px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-family: @font-family;
|
font-family: @font-family;
|
||||||
|
color: #74788d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.metrics-form-item {
|
||||||
|
margin-top: 8px;
|
||||||
.no-item-control {
|
}
|
||||||
margin-bottom: 8px !important;
|
.user-info-form-items {
|
||||||
.dcloud-form-item-control {
|
display: flex;
|
||||||
display: none;
|
align-items: flex-start;
|
||||||
|
.user-info-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
.inline-items {
|
||||||
|
flex: 0 0 80%;
|
||||||
|
.token-form-item {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user