This commit is contained in:
孙超
2022-12-15 18:48:41 +08:00
committed by EricZeng
parent 5bceed7105
commit 860d0b92e2
59 changed files with 25972 additions and 1497 deletions

View File

@@ -1,12 +1,13 @@
import { Button, Divider, Drawer, Form, Input, InputNumber, Radio, Select, Spin, Space, Utils } from 'knowdesign';
import { Button, Divider, Drawer, Form, Input, InputNumber, Radio, Select, Spin, Space, Utils, Tabs, Collapse, Empty } from 'knowdesign';
import message from '@src/components/Message';
import * as React from 'react';
import React, { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import api from '@src/api';
import { regClusterName, regUsername } from '@src/constants/reg';
import { regClusterName, regIpAndPort, regUsername } from '@src/constants/reg';
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
import { IconFont } from '@knowdesign/icons';
import notification from '@src/components/Notification';
const LOW_KAFKA_VERSION = '2.8.0';
const CLIENT_PROPERTIES_PLACEHOLDER = `用于创建Kafka客户端进行信息获取的相关配置
例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置
@@ -20,13 +21,12 @@ word=\\"xxxxxx\\";"
}
`;
const AccessClusters = (props: any): JSX.Element => {
const { afterSubmitSuccess, clusterInfo, visible } = props;
const { Panel } = Collapse;
const ClusterTabContent = forwardRef((props: any, ref): JSX.Element => {
const { form, clusterInfo, visible } = props;
const intl = useIntl();
const [form] = Form.useForm();
const [loading, setLoading] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [curClusterInfo, setCurClusterInfo] = React.useState<any>({});
const [extra, setExtra] = React.useState({
versionExtra: '',
@@ -66,6 +66,7 @@ const AccessClusters = (props: any): JSX.Element => {
const onCancel = () => {
form.resetFields();
setLoading(false);
setCurClusterInfo({});
setExtra({
versionExtra: '',
zooKeeperExtra: '',
@@ -73,60 +74,6 @@ const AccessClusters = (props: any): JSX.Element => {
jmxExtra: '',
});
lastFormItemValue.current = { bootstrapServers: '', zookeeper: '', clientProperties: {} };
props.setVisible && props.setVisible(false);
};
const onSubmit = () => {
form.validateFields().then((res) => {
setConfirmLoading(true);
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.openSSL || false,
token: res.token,
username: res.username,
},
kafkaVersion: res.kafkaVersion,
name: res.name,
zookeeper: res.zookeeper || '',
};
if (!isNaN(curClusterInfo?.id)) {
Utils.put(api.phyCluster, {
...params,
id: curClusterInfo?.id,
})
.then(() => {
message.success('编辑成功');
afterSubmitSuccess && afterSubmitSuccess();
onCancel();
})
.finally(() => {
setConfirmLoading(false);
});
} else {
Utils.post(api.phyCluster, params)
.then(() => {
message.success('集群接入成功。注意新接入集群数据稳定需要1-2分钟');
afterSubmitSuccess && afterSubmitSuccess();
onCancel();
})
.finally(() => {
setConfirmLoading(false);
});
}
});
};
const connectTest = () => {
@@ -213,39 +160,37 @@ const AccessClusters = (props: any): JSX.Element => {
// 获取集群详情数据
React.useEffect(() => {
if (visible) {
if (clusterInfo?.id) {
setLoading(true);
if (clusterInfo?.id && visible) {
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);
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);
}
return res;
};
} catch (err) {
console.error('jmxProperties not JSON: ', err);
}
return res;
};
Utils.request(api.getPhyClusterBasic(clusterInfo.id))
.then((res: any) => {
setCurClusterInfo(resolveJmxProperties(res));
})
.catch((err) => {
setCurClusterInfo(resolveJmxProperties(clusterInfo));
})
.finally(() => {
setLoading(false);
});
} else {
setCurClusterInfo({});
}
Utils.request(api.getPhyClusterBasic(clusterInfo.id))
.then((res: any) => {
setCurClusterInfo(resolveJmxProperties(res));
})
.catch((err) => {
setCurClusterInfo(resolveJmxProperties(clusterInfo));
})
.finally(() => {
setLoading(false);
});
} else {
setCurClusterInfo({});
}
}, [visible, clusterInfo]);
}, [clusterInfo, visible]);
const validators = {
name: async (_: any, value: string) => {
@@ -358,13 +303,510 @@ const AccessClusters = (props: any): JSX.Element => {
},
};
useImperativeHandle(ref, () => ({
onCancel,
}));
return (
<Spin spinning={loading}>
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
<Form.Item
name="name"
label="集群名称"
validateTrigger="onBlur"
rules={[
{
required: true,
validator: validators.name,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="bootstrapServers"
label="Bootstrap Servers"
extra={<span className={!extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>}
validateTrigger={'onBlur'}
rules={[
{
required: true,
validator: validators.bootstrapServers,
},
]}
>
<Input.TextArea rows={3} placeholder="请输入Bootstrap Servers地址例如192.168.1.1:9092,192.168.1.2:9092,192.168.1.3:9092" />
</Form.Item>
<Form.Item
name="zookeeper"
label="Zookeeper"
extra={<span className={!extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
validateTrigger={'onBlur'}
rules={[
{
validator: validators.zookeeper,
},
]}
>
<Input.TextArea rows={3} placeholder="请输入Zookeeper地址例如192.168.0.1:2181,192.168.0.2:2181,192.168.0.2:2181/ks-kafka" />
</Form.Item>
<Form.Item className="metrics-form-item" label="Metrics">
<div className="horizontal-form-container">
<div className="inline-items">
<Form.Item name="jmxPort" label="JMX Port :" extra={extra.jmxExtra}>
<InputNumber min={0} max={99999} style={{ width: 129 }} />
</Form.Item>
<Form.Item name="maxConn" label="Max Conn :">
<InputNumber addonAfter="个" min={0} max={99999} style={{ width: 124 }} />
</Form.Item>
</div>
<Form.Item name="openSSL" label="Security :">
<Radio.Group>
<Radio value={false}>None</Radio>
<Radio value={true}>Password Authentication</Radio>
</Radio.Group>
</Form.Item>
<Form.Item dependencies={['openSSL']} noStyle>
{({ getFieldValue }) => {
return getFieldValue('openSSL') ? (
<div className="user-info-form-items">
<Form.Item className="user-info-label" label="User Info :" required />
<div className="inline-items">
<Form.Item
name="username"
rules={[
{
validator: validators.securityUserName,
},
]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
<Form.Item
className="token-form-item"
name="token"
rules={[
{
validator: validators.securityToken,
},
]}
>
<Input placeholder="请输入密码" />
</Form.Item>
</div>
</div>
) : null;
}}
</Form.Item>
</div>
</Form.Item>
<Form.Item
name="kafkaVersion"
label="Version"
dependencies={['zookeeper']}
extra={<span className="error-extra-info">{extra.versionExtra}</span>}
rules={[
{
required: true,
validator: validators.kafkaVersion,
},
]}
>
<Select placeholder="请选择Kafka Version如无匹配则选择相近版本">
{(props.kafkaVersion || []).map((item: string) => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="clientProperties"
label="集群配置"
rules={[
{
validator: validators.clientProperties,
},
]}
>
<div>
<CodeMirrorFormItem
resize
defaultInput={form.getFieldValue('clientProperties')}
placeholder={CLIENT_PROPERTIES_PLACEHOLDER}
onBeforeChange={(clientProperties: string) => {
form.setFieldsValue({ clientProperties });
form.validateFields(['clientProperties']);
}}
onBlur={() => {
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(() => {
lastFormItemValue.current.clientProperties = clientProperties;
})
.catch(() => {
message.error('连接失败');
});
}
});
}}
/>
</div>
</Form.Item>
<Form.Item
name="description"
label="集群描述"
rules={[
{
validator: validators.description,
},
]}
>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</Spin>
);
});
const ConnectorForm = (props: {
initFieldsValue: any;
kafkaVersion: string[];
setSelectedTabKey: React.Dispatch<React.SetStateAction<string>>;
getConnectClustersList: any;
clusterInfo: any;
}) => {
const { initFieldsValue, kafkaVersion, setSelectedTabKey, getConnectClustersList, clusterInfo } = props;
const [form] = Form.useForm();
const validators = {
name: async (_: any, value: string) => {
if (!value) {
return Promise.reject('集群名称不能为空');
}
if (value === initFieldsValue?.name) {
return Promise.resolve();
}
if (!new RegExp(regClusterName).test(value)) {
return Promise.reject('集群名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~');
}
return Utils.request(api.getConnectClusterBasicExit(clusterInfo.id, value))
.then((res: any) => {
const data = res || {};
return data?.exist ? Promise.reject('集群名称重复') : Promise.resolve();
})
.catch(() => Promise.reject('连接超时! 请重试或检查服务'));
},
address: async (_: any, value: string) => {
if (!value) {
return Promise.reject('请输入集群地址');
}
if (!new RegExp(regIpAndPort).test(value)) {
return Promise.reject('格式错误正确示例http://1.1.1.1, http://1.1.1.1:65535, https://1.1.1.1, https://1.1.1.1:65535');
}
return Promise.resolve();
},
};
const onFinish = (values: any) => {
const params = {
...values,
id: initFieldsValue?.id,
};
Utils.put(api.batchConnectClusters, [params])
.then((res) => {
// setSelectedTabKey(undefined);
getConnectClustersList();
notification.success({
message: '修改Connect集群成功',
});
})
.catch((error) => {
notification.success({
message: '修改Connect集群失败',
});
});
};
const onCancel = () => {
setSelectedTabKey(undefined);
try {
const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {};
form.setFieldsValue({ ...initFieldsValue, jmxPort: jmxPortInfo.jmxPort });
} catch {
form.setFieldsValue({ ...initFieldsValue });
}
};
useLayoutEffect(() => {
try {
const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {};
form.setFieldsValue({ ...initFieldsValue, jmxPort: jmxPortInfo.jmxPort });
} catch {
form.setFieldsValue({ ...initFieldsValue });
}
}, []);
return (
<>
<Drawer
className="drawer-content drawer-access-cluster"
onClose={onCancel}
maskClosable={false}
extra={
<Form form={form} layout="vertical" onFinish={onFinish}>
<Form.Item name="name" label="集群名称" validateTrigger="onBlur" rules={[{ required: true, validator: validators.name }]}>
<Input placeholder="请输入集群名称" maxLength={64} />
</Form.Item>
<Form.Item name="groupName" label="ConsumerGroup Name">
<Input disabled={true} placeholder="请输入 ConsumerGroup Name" />
</Form.Item>
<Form.Item name="clusterUrl" label="集群地址">
<Input disabled placeholder="请输入集群地址" />
</Form.Item>
{/* <Form.Item
name="kafkaVersion"
label="版本号"
rules={[
{
required: true,
message: '请选择版本号',
},
]}
>
<Select placeholder="请选择版本,如无匹配可选择相邻版本">
{(kafkaVersion || []).map((item: string) => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="JMX Port" name="priority" rules={[{ required: true, message: 'Principle 不能为空' }]} initialValue="throughput">
<Radio.Group>
<Radio value="allBroker">应用于所有Broker</Radio>
<Radio value="givenBroker">应用于特定Broker</Radio>
</Radio.Group>
</Form.Item>
<Form.Item dependencies={['priority']} style={{ marginBottom: 0 }}>
{({ getFieldValue }) =>
getFieldValue('priority') === 'allBroker' ? (
<Form.Item name="jmxPort">
<InputNumber min={0} max={99999} style={{ width: 202 }} />
</Form.Item>
) : (
<Form.Item name="jmxPort">
<Input style={{ width: 202 }} />
</Form.Item>
)
}
</Form.Item> */}
<div className="inline-form-items" style={{ display: 'flex', justifyContent: 'space-between' }}>
<Form.Item
name="version"
label="版本号"
style={{ width: 202 }}
rules={[
{
required: true,
message: '请选择版本号',
},
]}
>
<Select placeholder="请选择版本,如无匹配可选择相邻版本">
{(kafkaVersion || []).map((item: string) => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item name="jmxPort" label="JMX Port" style={{ width: 202 }}>
<InputNumber min={0} max={99999} style={{ width: 202 }} />
</Form.Item>
</div>
<Form.Item style={{ marginBottom: 0 }}>
<Space>
<Button type="primary" htmlType="submit" size="small" style={{ width: 56 }}>
</Button>
<Button size="small" style={{ width: 56 }} onClick={onCancel}>
</Button>
</Space>
</Form.Item>
</Form>
</>
);
};
const ConnectTabContent = forwardRef((props: any, ref) => {
const { kafkaVersion, clusterInfo, visible } = props;
const [connectors, setConnectors] = useState<any[]>([]);
const [selectedTabKey, setSelectedTabKey] = useState<string>(undefined);
const [loading, setLoading] = useState(true);
const genExtra = (connector: any) => (
<IconFont
type="icon-shanchu1"
onClick={(e) => {
e.stopPropagation();
Utils.delete(api.deleteConnectClusters, {
params: {
connectClusterId: connector.id,
},
}).then((res) => {
// setSelectedTabKey(undefined);
getConnectClustersList();
notification.success({
message: '删除Connect集群成功',
});
});
}}
/>
);
const getConnectClustersList = () => {
setLoading(true);
Utils.request(api.getConnectClusters(clusterInfo.id))
.then((res: any) => {
setConnectors(res || []);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
visible && getConnectClustersList();
}, [visible]);
return (
<Spin spinning={loading}>
{connectors?.length ? (
<Collapse
accordion
bordered={false}
activeKey={selectedTabKey}
className="cluster-connect-custom-collapse"
expandIcon={({ isActive }) => <IconFont type="icon-jiantou_1" rotate={isActive ? 90 : 0} />}
onChange={(key: string) => {
setSelectedTabKey(key);
}}
>
{connectors.map((connector, i) => {
return (
<Panel header={connector.name} key={i} className="cluster-connect-custom-panel" extra={genExtra(connector)}>
<ConnectorForm
initFieldsValue={connector}
kafkaVersion={kafkaVersion}
setSelectedTabKey={setSelectedTabKey}
getConnectClustersList={getConnectClustersList}
clusterInfo={clusterInfo}
/>
</Panel>
);
})}
</Collapse>
) : (
<Empty description="暂无Connect集群" image={Empty.PRESENTED_IMAGE_CUSTOM} style={{ padding: '100px 0' }} />
)}
</Spin>
);
});
interface AccessClusterDrawerProps {
visible: boolean;
setVisible: (visible: boolean) => void;
clusterInfo: any;
afterSubmitSuccess: () => void;
kafkaVersion: string[];
title?: string;
}
const AccessClusterDrawer = (props: AccessClusterDrawerProps) => {
const { afterSubmitSuccess, clusterInfo, visible, setVisible, kafkaVersion } = props;
const intl = useIntl();
const [form] = Form.useForm();
const [confirmLoading, setConfirmLoading] = useState(false);
const clusterRef = useRef(null);
const [positionType, setPositionType] = useState<string>('cluster');
const onCancel = () => {
setPositionType('cluster');
form.resetFields();
clusterRef.current.onCancel();
setVisible && setVisible(false);
};
const callback = (key: any) => {
setPositionType(key);
};
const onSubmit = () => {
form.validateFields().then((res) => {
setConfirmLoading(true);
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.openSSL || false,
token: res.token,
username: res.username,
},
kafkaVersion: res.kafkaVersion,
name: res.name,
zookeeper: res.zookeeper || '',
};
if (!isNaN(clusterInfo?.id)) {
Utils.put(api.phyCluster, {
...params,
id: clusterInfo?.id,
})
.then(() => {
message.success('编辑成功');
afterSubmitSuccess && afterSubmitSuccess();
onCancel();
})
.finally(() => {
setConfirmLoading(false);
});
} else {
Utils.post(api.phyCluster, params)
.then(() => {
message.success('集群接入成功。注意新接入集群数据稳定需要1-2分钟');
afterSubmitSuccess && afterSubmitSuccess();
onCancel();
})
.finally(() => {
setConfirmLoading(false);
});
}
});
};
return (
<Drawer
className="drawer-content drawer-access-cluster"
onClose={onCancel}
maskClosable={false}
extra={
positionType === 'cluster' ? (
<div className="operate-wrap">
<Space>
<Button size="small" onClick={onCancel}>
@@ -376,189 +818,25 @@ const AccessClusters = (props: any): JSX.Element => {
<Divider type="vertical" />
</Space>
</div>
}
title={intl.formatMessage({ id: props.title || clusterInfo?.id ? 'edit.cluster' : 'access.cluster' })}
visible={props.visible}
placement="right"
width={480}
>
<Spin spinning={loading}>
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
<Form.Item
name="name"
label="集群名称"
validateTrigger="onBlur"
rules={[
{
required: true,
validator: validators.name,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="bootstrapServers"
label="Bootstrap Servers"
extra={<span className={!extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>}
validateTrigger={'onBlur'}
rules={[
{
required: true,
validator: validators.bootstrapServers,
},
]}
>
<Input.TextArea
rows={3}
placeholder="请输入Bootstrap Servers地址例如192.168.1.1:9092,192.168.1.2:9092,192.168.1.3:9092"
/>
</Form.Item>
<Form.Item
name="zookeeper"
label="Zookeeper"
extra={<span className={!extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
validateTrigger={'onBlur'}
rules={[
{
validator: validators.zookeeper,
},
]}
>
<Input.TextArea
rows={3}
placeholder="请输入Zookeeper地址例如192.168.0.1:2181,192.168.0.2:2181,192.168.0.2:2181/ks-kafka"
/>
</Form.Item>
<Form.Item className="metrics-form-item" label="Metrics">
<div className="horizontal-form-container">
<div className="inline-items">
<Form.Item name="jmxPort" label="JMX Port :" extra={extra.jmxExtra}>
<InputNumber min={0} max={99999} style={{ width: 129 }} />
</Form.Item>
<Form.Item name="maxConn" label="Max Conn :">
<InputNumber addonAfter="个" min={0} max={99999} style={{ width: 124 }} />
</Form.Item>
</div>
<Form.Item name="openSSL" label="Security :">
<Radio.Group>
<Radio value={false}>None</Radio>
<Radio value={true}>Password Authentication</Radio>
</Radio.Group>
</Form.Item>
<Form.Item dependencies={['openSSL']} noStyle>
{({ getFieldValue }) => {
return getFieldValue('openSSL') ? (
<div className="user-info-form-items">
<Form.Item className="user-info-label" label="User Info :" required />
<div className="inline-items">
<Form.Item
name="username"
rules={[
{
validator: validators.securityUserName,
},
]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
<Form.Item
className="token-form-item"
name="token"
rules={[
{
validator: validators.securityToken,
},
]}
>
<Input placeholder="请输入密码" />
</Form.Item>
</div>
</div>
) : null;
}}
</Form.Item>
</div>
</Form.Item>
<Form.Item
name="kafkaVersion"
label="Version"
dependencies={['zookeeper']}
extra={<span className="error-extra-info">{extra.versionExtra}</span>}
rules={[
{
required: true,
validator: validators.kafkaVersion,
},
]}
>
<Select placeholder="请选择Kafka Version如无匹配则选择相近版本">
{(props.kafkaVersion || []).map((item: string) => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="clientProperties"
label="集群配置"
rules={[
{
validator: validators.clientProperties,
},
]}
>
<div>
<CodeMirrorFormItem
resize
defaultInput={form.getFieldValue('clientProperties')}
placeholder={CLIENT_PROPERTIES_PLACEHOLDER}
onBeforeChange={(clientProperties: string) => {
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('连接失败');
});
}
});
}}
/>
</div>
</Form.Item>
<Form.Item
name="description"
label="集群描述"
rules={[
{
validator: validators.description,
},
]}
>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</Spin>
</Drawer>
</>
) : null
}
title={intl.formatMessage({ id: props.title || clusterInfo?.id ? 'edit.cluster' : 'access.cluster' })}
visible={visible}
placement="right"
width={480}
>
<Tabs onChange={callback} activeKey={positionType} defaultActiveKey="cluster">
<Tabs.TabPane tab="Cluster" key="cluster">
<ClusterTabContent ref={clusterRef} form={form} clusterInfo={clusterInfo} kafkaVersion={kafkaVersion} visible={visible} />
</Tabs.TabPane>
{clusterInfo?.id && (
<Tabs.TabPane tab="Connect" key="connect">
<ConnectTabContent kafkaVersion={kafkaVersion} clusterInfo={clusterInfo} visible={visible} />
</Tabs.TabPane>
)}
</Tabs>
</Drawer>
);
};
export default AccessClusters;
export default AccessClusterDrawer;

View File

@@ -466,6 +466,7 @@
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: @font-family-bold;
font-size: 18px;
color: #495057;
@@ -633,6 +634,12 @@
}
}
}
.drawer-access-cluster {
.dcloud-drawer-title {
height: 27px;
line-height: 27px;
}
}
.drawer-content {
.dcloud-form-item-extra {
@@ -674,6 +681,41 @@
}
}
}
.cluster-connect-custom-collapse {
background-color: transparent;
.cluster-connect-custom-panel,
.cluster-connect-custom-panel:last-child {
margin-bottom: 8px;
overflow: hidden;
background: #f8f9fa;
border: 0px;
border-radius: 8px;
.dcloud-collapse-header {
padding: 8px 12px;
font-size: 14px;
color: #495057;
.dcloud-collapse-extra {
opacity: 0;
transition: opacity 0.2s ease;
}
}
&:hover .dcloud-collapse-extra {
opacity: 1;
}
&:not(.dcloud-collapse-item-active) {
.dcloud-collapse-header:hover {
background: #f1f3ff;
}
}
.dcloud-collapse-content-box {
padding: 12px;
}
}
.dcloud-collapse-header .dcloud-collapse-arrow {
margin-right: 8px !important;
font-size: 16px;
}
}
}
.empty-page {