mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-03 19:38:20 +08:00
feat: 多集群列表支持编辑 & 代码结构优化
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign';
|
import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import api from '../../api';
|
import api from '@src/api';
|
||||||
import { regClusterName, regUsername } from '../../constants/reg';
|
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';
|
||||||
|
|
||||||
@@ -21,40 +21,28 @@ word=\\"xxxxxx\\";"
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const AccessClusters = (props: any): JSX.Element => {
|
const AccessClusters = (props: any): JSX.Element => {
|
||||||
|
const { afterSubmitSuccess, clusterInfo, visible } = props;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const { afterSubmitSuccess, infoLoading, clusterInfo, visible } = props;
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [security, setSecurity] = React.useState(clusterInfo?.security || 'None');
|
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<any>(false);
|
const [isLowVersion, setIsLowVersion] = React.useState<boolean>(false);
|
||||||
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<any>(false);
|
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const lastFormItemValue = React.useRef({
|
const lastFormItemValue = React.useRef({
|
||||||
bootstrap: clusterInfo?.bootstrapServers || '',
|
bootstrap: curClusterInfo?.bootstrapServers || '',
|
||||||
zookeeper: clusterInfo?.zookeeper || '',
|
zookeeper: curClusterInfo?.zookeeper || '',
|
||||||
clientProperties: clusterInfo?.clientProperties || {},
|
clientProperties: curClusterInfo?.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) => {
|
const onHandleValuesChange = (value: any, allValues: any) => {
|
||||||
Object.keys(value).forEach((key) => {
|
Object.keys(value).forEach((key) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -128,10 +116,10 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
zookeeper: res.zookeeper || '',
|
zookeeper: res.zookeeper || '',
|
||||||
};
|
};
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!isNaN(clusterInfo?.id)) {
|
if (!isNaN(curClusterInfo?.id)) {
|
||||||
Utils.put(api.phyCluster, {
|
Utils.put(api.phyCluster, {
|
||||||
...params,
|
...params,
|
||||||
id: clusterInfo?.id,
|
id: curClusterInfo?.id,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
message.success('编辑成功');
|
message.success('编辑成功');
|
||||||
@@ -219,7 +207,11 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 如果kafkaVersion小于最低版本则提示
|
// 如果kafkaVersion小于最低版本则提示
|
||||||
const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion);
|
const showLowVersion = !(
|
||||||
|
curClusterInfo?.zookeeper ||
|
||||||
|
!curClusterInfo?.kafkaVersion ||
|
||||||
|
curClusterInfo?.kafkaVersion >= lowKafkaVersion
|
||||||
|
);
|
||||||
setIsLowVersion(showLowVersion);
|
setIsLowVersion(showLowVersion);
|
||||||
setExtra({
|
setExtra({
|
||||||
...extraMsg,
|
...extraMsg,
|
||||||
@@ -232,6 +224,55 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const showLowVersion = !(curClusterInfo?.zookeeper || !curClusterInfo?.kafkaVersion || curClusterInfo?.kafkaVersion >= lowKafkaVersion);
|
||||||
|
lastFormItemValue.current = {
|
||||||
|
bootstrap: curClusterInfo?.bootstrapServers || '',
|
||||||
|
zookeeper: curClusterInfo?.zookeeper || '',
|
||||||
|
clientProperties: curClusterInfo?.clientProperties || {},
|
||||||
|
};
|
||||||
|
setIsLowVersion(showLowVersion);
|
||||||
|
setExtra({
|
||||||
|
...extra,
|
||||||
|
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
|
||||||
|
});
|
||||||
|
form.setFieldsValue({ ...curClusterInfo });
|
||||||
|
}, [curClusterInfo]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
if (clusterInfo?.id) {
|
||||||
|
setLoading(true);
|
||||||
|
Utils.request(api.getPhyClusterBasic(clusterInfo.id))
|
||||||
|
.then((res: any) => {
|
||||||
|
let jmxProperties = null;
|
||||||
|
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) => {
|
||||||
|
setCurClusterInfo(clusterInfo);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setCurClusterInfo(clusterInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [visible, clusterInfo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -256,16 +297,8 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
placement="right"
|
placement="right"
|
||||||
width={480}
|
width={480}
|
||||||
>
|
>
|
||||||
<Spin spinning={loading || !!infoLoading}>
|
<Spin spinning={loading}>
|
||||||
<Form
|
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
|
||||||
form={form}
|
|
||||||
initialValues={{
|
|
||||||
security,
|
|
||||||
...clusterInfo,
|
|
||||||
}}
|
|
||||||
layout="vertical"
|
|
||||||
onValuesChange={onHandleValuesChange}
|
|
||||||
>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
label="集群名称"
|
label="集群名称"
|
||||||
@@ -277,11 +310,9 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('集群名称不能为空');
|
return Promise.reject('集群名称不能为空');
|
||||||
}
|
}
|
||||||
|
if (value === curClusterInfo?.name) {
|
||||||
if (value === clusterInfo?.name) {
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value?.length > 128) {
|
if (value?.length > 128) {
|
||||||
return Promise.reject('集群名称长度限制在1~128字符');
|
return Promise.reject('集群名称长度限制在1~128字符');
|
||||||
}
|
}
|
||||||
@@ -307,13 +338,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="bootstrapServers"
|
name="bootstrapServers"
|
||||||
label="Bootstrap Servers"
|
label="Bootstrap Servers"
|
||||||
extra={
|
extra={<span className={extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>}
|
||||||
extra.bootstrapExtra.includes('连接成功') ? (
|
|
||||||
<span>{extra.bootstrapExtra}</span>
|
|
||||||
) : (
|
|
||||||
<span className="error-extra-info">{extra.bootstrapExtra}</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
validateTrigger={'onBlur'}
|
validateTrigger={'onBlur'}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@@ -349,13 +374,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="zookeeper"
|
name="zookeeper"
|
||||||
label="Zookeeper"
|
label="Zookeeper"
|
||||||
extra={
|
extra={<span className={extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
|
||||||
extra.zooKeeperExtra.includes('连接成功') ? (
|
|
||||||
<span>{extra.zooKeeperExtra}</span>
|
|
||||||
) : (
|
|
||||||
<span className="error-extra-info">{extra.zooKeeperExtra}</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
|
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
|
||||||
validateTrigger={'onBlur'}
|
validateTrigger={'onBlur'}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -458,7 +477,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
style={{ width: '58%' }}
|
style={{ width: '58%' }}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: security === 'Password' || clusterInfo?.security === 'Password',
|
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
||||||
validator: async (rule: any, value: string) => {
|
validator: async (rule: any, value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('用户名不能为空');
|
return Promise.reject('用户名不能为空');
|
||||||
@@ -483,7 +502,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
|||||||
style={{ width: '38%', marginRight: 0 }}
|
style={{ width: '38%', marginRight: 0 }}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: security === 'Password' || clusterInfo?.security === 'Password',
|
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
||||||
validator: async (rule: any, value: string) => {
|
validator: async (rule: any, value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('密码不能为空');
|
return Promise.reject('密码不能为空');
|
||||||
|
|||||||
@@ -1,102 +1,108 @@
|
|||||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||||
import { Checkbox } from 'knowdesign';
|
import { Checkbox } from 'knowdesign';
|
||||||
|
import { CheckboxValueType } from 'knowdesign/es/basic/checkbox/Group';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const CheckboxGroup = Checkbox.Group;
|
const CheckboxGroup = Checkbox.Group;
|
||||||
|
|
||||||
interface IVersion {
|
|
||||||
firstLine: string[];
|
|
||||||
leftVersions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: any }) => {
|
const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: any }) => {
|
||||||
const { kafkaVersions, onChangeCheckGroup } = props;
|
const { kafkaVersions: newVersions, onChangeCheckGroup } = props;
|
||||||
const [checkedKafkaVersion, setCheckedKafkaVersion] = React.useState<IVersion>({
|
const [versions, setVersions] = React.useState<string[]>([]);
|
||||||
firstLine: [],
|
const [versionsState, setVersionsState] = React.useState<{
|
||||||
leftVersions: [],
|
[key: string]: boolean;
|
||||||
});
|
}>({});
|
||||||
const [allVersion, setAllVersion] = React.useState<IVersion>({
|
|
||||||
firstLine: [],
|
|
||||||
leftVersions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [indeterminate, setIndeterminate] = React.useState(false);
|
const [indeterminate, setIndeterminate] = React.useState(false);
|
||||||
const [checkAll, setCheckAll] = React.useState(true);
|
const [checkAll, setCheckAll] = React.useState(true);
|
||||||
const [moreGroupWidth, setMoreGroupWidth] = React.useState(400);
|
const [groupInfo, setGroupInfo] = useState({
|
||||||
|
width: 400,
|
||||||
|
num: 0,
|
||||||
|
});
|
||||||
const [showMore, setShowMore] = React.useState(false);
|
const [showMore, setShowMore] = React.useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('click', handleDocumentClick);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('click', handleDocumentClick);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDocumentClick = (e: Event) => {
|
const handleDocumentClick = (e: Event) => {
|
||||||
setShowMore(false);
|
setShowMore(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCheckAllStauts = (list: string[], otherList: string[]) => {
|
const updateGroupInfo = () => {
|
||||||
onChangeCheckGroup([...list, ...otherList]);
|
|
||||||
setIndeterminate(!!list.length && list.length + otherList.length < kafkaVersions.length);
|
|
||||||
setCheckAll(list.length + otherList.length === kafkaVersions.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTwoPanelVersion = () => {
|
|
||||||
const width = (document.getElementsByClassName('custom-check-group')[0] as any)?.offsetWidth;
|
const width = (document.getElementsByClassName('custom-check-group')[0] as any)?.offsetWidth;
|
||||||
const checkgroupWidth = width - 100 - 86;
|
const checkgroupWidth = width - 100 - 86;
|
||||||
const num = (checkgroupWidth / 108) | 0;
|
const num = (checkgroupWidth / 108) | 0;
|
||||||
const firstLine = Array.from(kafkaVersions).splice(0, num);
|
setGroupInfo({
|
||||||
setMoreGroupWidth(num * 108 + 88 + 66);
|
width: num * 108 + 88 + 66,
|
||||||
const leftVersions = Array.from(kafkaVersions).splice(num);
|
num,
|
||||||
return { firstLine, leftVersions };
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFirstVersionChange = (list: []) => {
|
const getCheckedList = (
|
||||||
setCheckedKafkaVersion({
|
versionState: {
|
||||||
...checkedKafkaVersion,
|
[key: string]: boolean;
|
||||||
firstLine: list,
|
},
|
||||||
});
|
filterFunc: (item: [string, boolean], i: number) => boolean
|
||||||
|
) => {
|
||||||
setCheckAllStauts(list, checkedKafkaVersion.leftVersions);
|
return Object.entries(versionState)
|
||||||
|
.filter(filterFunc)
|
||||||
|
.map(([key]) => key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLeftVersionChange = (list: []) => {
|
const onVersionsChange = (isFirstLine: boolean, list: CheckboxValueType[]) => {
|
||||||
setCheckedKafkaVersion({
|
const newVersionsState = { ...versionsState };
|
||||||
...checkedKafkaVersion,
|
Object.keys(newVersionsState).forEach((key, i) => {
|
||||||
leftVersions: list,
|
if (isFirstLine && i < groupInfo.num) {
|
||||||
|
newVersionsState[key] = list.includes(key);
|
||||||
|
} else if (!isFirstLine && i >= groupInfo.num) {
|
||||||
|
newVersionsState[key] = list.includes(key);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setCheckAllStauts(list, checkedKafkaVersion.firstLine);
|
const checkedLen = Object.values(newVersionsState).filter((v) => v).length;
|
||||||
|
|
||||||
|
setVersionsState(newVersionsState);
|
||||||
|
setIndeterminate(checkedLen && checkedLen < newVersions.length);
|
||||||
|
setCheckAll(checkedLen === newVersions.length);
|
||||||
|
onChangeCheckGroup(getCheckedList(newVersionsState, ([, state]) => state));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCheckAllChange = (e: any) => {
|
const onCheckAllChange = (e: any) => {
|
||||||
const versions = getTwoPanelVersion();
|
const checked = e.target.checked;
|
||||||
|
const newVersionsState = { ...versionsState };
|
||||||
setCheckedKafkaVersion(
|
Object.keys(newVersionsState).forEach((key) => (newVersionsState[key] = checked));
|
||||||
e.target.checked
|
|
||||||
? versions
|
|
||||||
: {
|
|
||||||
firstLine: [],
|
|
||||||
leftVersions: [],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
onChangeCheckGroup(e.target.checked ? [...versions.firstLine, ...versions.leftVersions] : []);
|
|
||||||
|
|
||||||
|
setVersionsState(newVersionsState);
|
||||||
setIndeterminate(false);
|
setIndeterminate(false);
|
||||||
setCheckAll(e.target.checked);
|
setCheckAll(checked);
|
||||||
|
onChangeCheckGroup(e.target.checked ? versions : []);
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const handleVersionLine = () => {
|
const newVersionsState = { ...versionsState };
|
||||||
const versions = getTwoPanelVersion();
|
Object.keys(newVersionsState).forEach((key) => {
|
||||||
setAllVersion(versions);
|
if (!newVersions.includes(key)) {
|
||||||
setCheckedKafkaVersion(versions);
|
delete newVersionsState[key];
|
||||||
};
|
}
|
||||||
handleVersionLine();
|
});
|
||||||
|
newVersions.forEach((version) => {
|
||||||
|
if (!Object.keys(newVersionsState).includes(version)) {
|
||||||
|
newVersionsState[version] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const checkedLen = Object.values(newVersionsState).filter((v) => v).length;
|
||||||
|
|
||||||
window.addEventListener('resize', handleVersionLine); //监听窗口大小改变
|
setVersions([...newVersions]);
|
||||||
return () => window.removeEventListener('resize', debounce(handleVersionLine, 500));
|
setVersionsState(newVersionsState);
|
||||||
|
setIndeterminate(checkedLen && checkedLen < newVersions.length);
|
||||||
|
setCheckAll(checkedLen === newVersions.length);
|
||||||
|
onChangeCheckGroup(getCheckedList(newVersionsState, ([, state]) => state));
|
||||||
|
}, [newVersions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateGroupInfo();
|
||||||
|
const listen = debounce(updateGroupInfo, 500);
|
||||||
|
window.addEventListener('resize', listen); //监听窗口大小改变
|
||||||
|
document.addEventListener('click', handleDocumentClick);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', listen);
|
||||||
|
document.removeEventListener('click', handleDocumentClick);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -107,17 +113,21 @@ const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup:
|
|||||||
全选
|
全选
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<CheckboxGroup options={allVersion.firstLine} value={checkedKafkaVersion.firstLine} onChange={onFirstVersionChange} />
|
<CheckboxGroup
|
||||||
|
options={Array.from(versions).splice(0, groupInfo.num)}
|
||||||
|
value={getCheckedList(versionsState, ([, state], i) => i < groupInfo.num && state)}
|
||||||
|
onChange={(list) => onVersionsChange(true, list)}
|
||||||
|
/>
|
||||||
{showMore ? (
|
{showMore ? (
|
||||||
<CheckboxGroup
|
<CheckboxGroup
|
||||||
style={{ width: moreGroupWidth }}
|
style={{ width: groupInfo.width }}
|
||||||
className="more-check-group"
|
className="more-check-group"
|
||||||
options={allVersion.leftVersions}
|
options={Array.from(versions).splice(groupInfo.num)}
|
||||||
value={checkedKafkaVersion.leftVersions}
|
value={getCheckedList(versionsState, ([, state], i) => i >= groupInfo.num && state)}
|
||||||
onChange={onLeftVersionChange}
|
onChange={(list) => onVersionsChange(false, list)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{allVersion.leftVersions.length ? (
|
{versions.length > groupInfo.num ? (
|
||||||
<div className="more-btn" onClick={() => setShowMore(!showMore)}>
|
<div className="more-btn" onClick={() => setShowMore(!showMore)}>
|
||||||
<a>
|
<a>
|
||||||
{!showMore ? '展开更多' : '收起更多'} <DoubleRightOutlined style={{ transform: `rotate(${showMore ? '270' : '90'}deg)` }} />
|
{!showMore ? '展开更多' : '收起更多'} <DoubleRightOutlined style={{ transform: `rotate(${showMore ? '270' : '90'}deg)` }} />
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState, useReducer } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, IconFont, AppContainer } from 'knowdesign';
|
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, IconFont, AppContainer } from 'knowdesign';
|
||||||
import API from '../../api';
|
import API from '@src/api';
|
||||||
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { healthSorceList, linesMetric, pointsMetric, sortFieldList, sortTypes, statusFilters } from './config';
|
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
|
||||||
import { oneDayMillims } from '../../constants/common';
|
import ClusterList from './List';
|
||||||
import ListScroll from './List';
|
|
||||||
import AccessClusters from './AccessCluster';
|
import AccessClusters from './AccessCluster';
|
||||||
import CustomCheckGroup from './CustomCheckGroup';
|
import CustomCheckGroup from './CustomCheckGroup';
|
||||||
import { ClustersPermissionMap } from '../CommonConfig';
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
@@ -13,98 +12,85 @@ import { ClustersPermissionMap } from '../CommonConfig';
|
|||||||
const CheckboxGroup = Checkbox.Group;
|
const CheckboxGroup = Checkbox.Group;
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
|
interface ClustersState {
|
||||||
|
liveCount: number;
|
||||||
|
downCount: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchParams {
|
||||||
|
healthScoreRange?: [number, number];
|
||||||
|
checkedKafkaVersions?: string[];
|
||||||
|
sortInfo?: {
|
||||||
|
sortField: string;
|
||||||
|
sortType: string;
|
||||||
|
};
|
||||||
|
keywords?: string;
|
||||||
|
clusterStatus?: number[];
|
||||||
|
isReloadAll?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未接入集群默认页
|
||||||
|
const DefaultPage = (props: { setVisible: (visible: boolean) => void }) => {
|
||||||
|
return (
|
||||||
|
<div className="empty-page">
|
||||||
|
<div className="title">Kafka 多集群管理</div>
|
||||||
|
<div className="img">
|
||||||
|
<div className="img-card-1" />
|
||||||
|
<div className="img-card-2" />
|
||||||
|
<div className="img-card-3" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button className="header-filter-top-button" type="primary" onClick={() => props.setVisible(true)}>
|
||||||
|
<span>
|
||||||
|
<IconFont type="icon-jiahao" />
|
||||||
|
<span className="text">接入集群</span>
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const LoadingState = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<Spin spinning={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const MultiClusterPage = () => {
|
const MultiClusterPage = () => {
|
||||||
const [run, setRun] = useState<boolean>(false);
|
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const [global] = AppContainer.useGlobalValue();
|
||||||
const [statusList, setStatusList] = React.useState([1, 0]);
|
const [pageLoading, setPageLoading] = useState(true);
|
||||||
|
const [accessClusterVisible, setAccessClusterVisible] = React.useState(false);
|
||||||
|
const [curClusterInfo, setCurClusterInfo] = useState<any>({});
|
||||||
const [kafkaVersions, setKafkaVersions] = React.useState<string[]>([]);
|
const [kafkaVersions, setKafkaVersions] = React.useState<string[]>([]);
|
||||||
const [existKafkaVersion, setExistKafkaVersion] = React.useState<string[]>([]);
|
const [existKafkaVersion, setExistKafkaVersion] = React.useState<string[]>([]);
|
||||||
const [visible, setVisible] = React.useState(false);
|
const [stateInfo, setStateInfo] = React.useState<ClustersState>({
|
||||||
const [list, setList] = useState<[]>([]);
|
|
||||||
const [healthScoreRange, setHealthScoreRange] = React.useState([0, 100]);
|
|
||||||
const [checkedKafkaVersions, setCheckedKafkaVersions] = React.useState<string[]>([]);
|
|
||||||
const [sortInfo, setSortInfo] = React.useState({
|
|
||||||
sortField: 'HealthScore',
|
|
||||||
sortType: 'asc',
|
|
||||||
});
|
|
||||||
const [clusterLoading, setClusterLoading] = useState(true);
|
|
||||||
const [pageLoading, setPageLoading] = useState(true);
|
|
||||||
const [isReload, setIsReload] = useState(false);
|
|
||||||
const [versionLoading, setVersionLoading] = useState(true);
|
|
||||||
const [searchKeywords, setSearchKeywords] = useState('');
|
|
||||||
const [stateInfo, setStateInfo] = React.useState({
|
|
||||||
downCount: 0,
|
downCount: 0,
|
||||||
liveCount: 0,
|
liveCount: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const [pagination, setPagination] = useState({
|
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
|
||||||
pageNo: 1,
|
const [searchParams, setSearchParams] = React.useState<SearchParams>({
|
||||||
pageSize: 10,
|
keywords: '',
|
||||||
total: 0,
|
checkedKafkaVersions: [],
|
||||||
|
healthScoreRange: [0, 100],
|
||||||
|
sortInfo: {
|
||||||
|
sortField: 'HealthScore',
|
||||||
|
sortType: 'asc',
|
||||||
|
},
|
||||||
|
clusterStatus: [0, 1],
|
||||||
|
// 是否拉取当前所有数据
|
||||||
|
isReloadAll: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchKeyword = useRef('');
|
const searchKeyword = useRef('');
|
||||||
|
const isReload = useRef(false);
|
||||||
|
|
||||||
const getPhyClustersDashbord = (pageNo: number, pageSize: number) => {
|
// 获取集群状态
|
||||||
const endTime = new Date().getTime();
|
|
||||||
const startTime = endTime - oneDayMillims;
|
|
||||||
const params = {
|
|
||||||
metricLines: {
|
|
||||||
endTime,
|
|
||||||
metricsNames: linesMetric,
|
|
||||||
startTime,
|
|
||||||
},
|
|
||||||
latestMetricNames: pointsMetric,
|
|
||||||
pageNo: pageNo || 1,
|
|
||||||
pageSize: pageSize || 10,
|
|
||||||
preciseFilterDTOList: [
|
|
||||||
{
|
|
||||||
fieldName: 'kafkaVersion',
|
|
||||||
fieldValueList: checkedKafkaVersions as (string | number)[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rangeFilterDTOList: [
|
|
||||||
{
|
|
||||||
fieldMaxValue: healthScoreRange[1],
|
|
||||||
fieldMinValue: healthScoreRange[0],
|
|
||||||
fieldName: 'HealthScore',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
searchKeywords,
|
|
||||||
...sortInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (statusList.length === 1) {
|
|
||||||
params.preciseFilterDTOList.push({
|
|
||||||
fieldName: 'Alive',
|
|
||||||
fieldValueList: statusList,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Utils.post(API.phyClustersDashbord, params);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSupportKafkaVersion = () => {
|
|
||||||
Utils.request(API.supportKafkaVersion).then((res) => {
|
|
||||||
setKafkaVersions(Object.keys(res || {}));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getExistKafkaVersion = () => {
|
|
||||||
setVersionLoading(true);
|
|
||||||
Utils.request(API.getClustersVersion)
|
|
||||||
.then((versions: string[]) => {
|
|
||||||
if (!Array.isArray(versions)) {
|
|
||||||
versions = [];
|
|
||||||
}
|
|
||||||
setExistKafkaVersion(versions.sort().reverse() || []);
|
|
||||||
setVersionLoading(false);
|
|
||||||
setCheckedKafkaVersions(versions || []);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setVersionLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPhyClusterState = () => {
|
const getPhyClusterState = () => {
|
||||||
Utils.request(API.phyClusterState)
|
Utils.request(API.phyClusterState)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
@@ -115,213 +101,224 @@ const MultiClusterPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取 kafka 全部版本
|
||||||
|
const getSupportKafkaVersion = () => {
|
||||||
|
Utils.request(API.supportKafkaVersion).then((res) => {
|
||||||
|
setKafkaVersions(Object.keys(res || {}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSearchParams = (params: SearchParams) => {
|
||||||
|
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchParamsChangeFunc = {
|
||||||
|
// 健康分改变
|
||||||
|
onSilderChange: (value: [number, number]) =>
|
||||||
|
updateSearchParams({
|
||||||
|
healthScoreRange: value,
|
||||||
|
}),
|
||||||
|
// 排序信息改变
|
||||||
|
onSortInfoChange: (type: string, value: string) =>
|
||||||
|
updateSearchParams({
|
||||||
|
sortInfo: {
|
||||||
|
...searchParams.sortInfo,
|
||||||
|
[type]: value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Live / Down 筛选
|
||||||
|
onClusterStatusChange: (list: number[]) =>
|
||||||
|
updateSearchParams({
|
||||||
|
clusterStatus: list,
|
||||||
|
}),
|
||||||
|
// 集群名称搜索项改变
|
||||||
|
onInputChange: () =>
|
||||||
|
updateSearchParams({
|
||||||
|
keywords: searchKeyword.current,
|
||||||
|
}),
|
||||||
|
// 集群版本筛选
|
||||||
|
onChangeCheckGroup: (list: string[]) => {
|
||||||
|
updateSearchParams({
|
||||||
|
checkedKafkaVersions: list,
|
||||||
|
isReloadAll: isReload.current,
|
||||||
|
});
|
||||||
|
isReload.current = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前接入集群的 kafka 版本
|
||||||
|
const getExistKafkaVersion = (isReloadAll = false) => {
|
||||||
|
isReload.current = isReloadAll;
|
||||||
|
Utils.request(API.getClustersVersion).then((versions: string[]) => {
|
||||||
|
if (!Array.isArray(versions)) {
|
||||||
|
versions = [];
|
||||||
|
}
|
||||||
|
setExistKafkaVersion(versions.sort().reverse() || []);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 接入/编辑集群
|
||||||
|
const showAccessCluster = (clusterInfo: any = {}) => {
|
||||||
|
setCurClusterInfo(clusterInfo);
|
||||||
|
setAccessClusterVisible(true);
|
||||||
|
};
|
||||||
|
// 接入/编辑集群回调
|
||||||
|
const afterAccessCluster = () => {
|
||||||
|
getPhyClusterState();
|
||||||
|
getExistKafkaVersion(true);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPhyClusterState();
|
getPhyClusterState();
|
||||||
getSupportKafkaVersion();
|
getSupportKafkaVersion();
|
||||||
getExistKafkaVersion();
|
getExistKafkaVersion();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!pageLoading && stateInfo.total) {
|
|
||||||
setRun(true);
|
|
||||||
}
|
|
||||||
}, [pageLoading, stateInfo]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (versionLoading) return;
|
|
||||||
setClusterLoading(true);
|
|
||||||
getPhyClustersDashbord(pagination.pageNo, pagination.pageSize)
|
|
||||||
.then((res: any) => {
|
|
||||||
setPagination(res.pagination);
|
|
||||||
setList(res?.bizData || []);
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setClusterLoading(false);
|
|
||||||
});
|
|
||||||
}, [sortInfo, checkedKafkaVersions, healthScoreRange, statusList, searchKeywords, isReload]);
|
|
||||||
|
|
||||||
const onSilderChange = (value: number[]) => {
|
|
||||||
setHealthScoreRange(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelectChange = (type: string, value: string) => {
|
|
||||||
setSortInfo({
|
|
||||||
...sortInfo,
|
|
||||||
[type]: value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onStatusChange = (list: []) => {
|
|
||||||
setStatusList(list);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInputChange = (e: any) => {
|
|
||||||
const { value } = e.target;
|
|
||||||
setSearchKeywords(value.trim());
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeCheckGroup = (list: []) => {
|
|
||||||
setCheckedKafkaVersions(list);
|
|
||||||
};
|
|
||||||
|
|
||||||
const afterSubmitSuccessAccessClusters = () => {
|
|
||||||
getPhyClusterState();
|
|
||||||
setIsReload(!isReload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderEmpty = () => {
|
|
||||||
return (
|
|
||||||
<div className="empty-page">
|
|
||||||
<div className="title">Kafka 多集群管理</div>
|
|
||||||
<div className="img">
|
|
||||||
<div className="img-card-1" />
|
|
||||||
<div className="img-card-2" />
|
|
||||||
<div className="img-card-3" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button className="header-filter-top-button" type="primary" onClick={() => setVisible(true)}>
|
|
||||||
<span>
|
|
||||||
<IconFont type="icon-jiahao" />
|
|
||||||
<span className="text">接入集群</span>
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderLoading = () => {
|
|
||||||
return (
|
|
||||||
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
||||||
<Spin spinning={true} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderContent = () => {
|
|
||||||
return (
|
|
||||||
<div className="multi-cluster-page" id="scrollableDiv">
|
|
||||||
<div className="multi-cluster-page-fixed">
|
|
||||||
<div className="content-container">
|
|
||||||
<div className="multi-cluster-header">
|
|
||||||
<div className="cluster-header-card">
|
|
||||||
<div className="cluster-header-card-bg-left"></div>
|
|
||||||
<div className="cluster-header-card-bg-right"></div>
|
|
||||||
<h5 className="header-card-title">
|
|
||||||
Clusters<span className="chinese-text"> 总数</span>
|
|
||||||
</h5>
|
|
||||||
<div className="header-card-total">{stateInfo.total}</div>
|
|
||||||
<div className="header-card-info">
|
|
||||||
<div className="card-info-item card-info-item-live">
|
|
||||||
<div>
|
|
||||||
live
|
|
||||||
<span className="info-item-value">
|
|
||||||
<em>{stateInfo.liveCount}</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card-info-item card-info-item-down">
|
|
||||||
<div>
|
|
||||||
down
|
|
||||||
<span className="info-item-value">
|
|
||||||
<em>{stateInfo.downCount}</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="cluster-header-filter">
|
|
||||||
<div className="header-filter-top">
|
|
||||||
<div className="header-filter-top-input">
|
|
||||||
<Input
|
|
||||||
onPressEnter={onInputChange}
|
|
||||||
onChange={(e) => (searchKeyword.current = e.target.value)}
|
|
||||||
allowClear
|
|
||||||
bordered={false}
|
|
||||||
placeholder="请输入ClusterName进行搜索"
|
|
||||||
suffix={<IconFont className="icon" type="icon-fangdajing" onClick={() => setSearchKeywords(searchKeyword.current)} />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_ADD) ? (
|
|
||||||
<>
|
|
||||||
<div className="header-filter-top-divider"></div>
|
|
||||||
<Button className="header-filter-top-button" type="primary" onClick={() => setVisible(true)}>
|
|
||||||
<IconFont type="icon-jiahao" />
|
|
||||||
<span className="text">接入集群</span>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="header-filter-bottom">
|
|
||||||
<div className="header-filter-bottom-item header-filter-bottom-item-checkbox">
|
|
||||||
<h3 className="header-filter-bottom-item-title">版本分布</h3>
|
|
||||||
<div className="header-filter-bottom-item-content flex">
|
|
||||||
{existKafkaVersion.length ? (
|
|
||||||
<CustomCheckGroup kafkaVersions={existKafkaVersion} onChangeCheckGroup={onChangeCheckGroup} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
|
||||||
<h3 className="header-filter-bottom-item-title title-right">健康分</h3>
|
|
||||||
<div className="header-filter-bottom-item-content">
|
|
||||||
<Slider range step={20} defaultValue={[0, 100]} marks={healthSorceList} onAfterChange={onSilderChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="multi-cluster-filter">
|
|
||||||
<div className="multi-cluster-filter-select">
|
|
||||||
<Select
|
|
||||||
onChange={(value) => onSelectChange('sortField', value)}
|
|
||||||
defaultValue="HealthScore"
|
|
||||||
style={{ width: 170, marginRight: 12 }}
|
|
||||||
>
|
|
||||||
{sortFieldList.map((item) => (
|
|
||||||
<Option key={item.value} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
<Select onChange={(value) => onSelectChange('sortType', value)} defaultValue="asc" style={{ width: 170 }}>
|
|
||||||
{sortTypes.map((item) => (
|
|
||||||
<Option key={item.value} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="multi-cluster-filter-checkbox">
|
|
||||||
<CheckboxGroup options={statusFilters} value={statusList} onChange={onStatusChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="test-modal-23"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="multi-cluster-page-dashboard">
|
|
||||||
<Spin spinning={clusterLoading}>{renderList}</Spin>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderList = useMemo(() => {
|
|
||||||
return <ListScroll list={list} pagination={pagination} loadMoreData={getPhyClustersDashbord} getPhyClusterState={getPhyClusterState} />;
|
|
||||||
}, [list, pagination]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TourGuide guide={MultiPageSteps} run={run} />
|
{pageLoading ? (
|
||||||
{pageLoading ? renderLoading() : stateInfo.total ? renderContent() : renderEmpty()}
|
<LoadingState />
|
||||||
|
) : !stateInfo?.total ? (
|
||||||
|
<DefaultPage setVisible={setAccessClusterVisible} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="multi-cluster-page" id="scrollableDiv">
|
||||||
|
<div className="multi-cluster-page-fixed">
|
||||||
|
<div className="content-container">
|
||||||
|
<div className="multi-cluster-header">
|
||||||
|
<div className="cluster-header-card">
|
||||||
|
<div className="cluster-header-card-bg-left"></div>
|
||||||
|
<div className="cluster-header-card-bg-right"></div>
|
||||||
|
<h5 className="header-card-title">
|
||||||
|
Clusters<span className="chinese-text"> 总数</span>
|
||||||
|
</h5>
|
||||||
|
<div className="header-card-total">{stateInfo.total}</div>
|
||||||
|
<div className="header-card-info">
|
||||||
|
<div className="card-info-item card-info-item-live">
|
||||||
|
<div>
|
||||||
|
live
|
||||||
|
<span className="info-item-value">
|
||||||
|
<em>{stateInfo.liveCount}</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card-info-item card-info-item-down">
|
||||||
|
<div>
|
||||||
|
down
|
||||||
|
<span className="info-item-value">
|
||||||
|
<em>{stateInfo.downCount}</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="cluster-header-filter">
|
||||||
|
<div className="header-filter-top">
|
||||||
|
<div className="header-filter-top-input">
|
||||||
|
<Input
|
||||||
|
onPressEnter={searchParamsChangeFunc.onInputChange}
|
||||||
|
onChange={(e) => (searchKeyword.current = e.target.value)}
|
||||||
|
allowClear
|
||||||
|
bordered={false}
|
||||||
|
placeholder="请输入ClusterName进行搜索"
|
||||||
|
suffix={<IconFont className="icon" type="icon-fangdajing" onClick={searchParamsChangeFunc.onInputChange} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_ADD) ? (
|
||||||
|
<>
|
||||||
|
<div className="header-filter-top-divider"></div>
|
||||||
|
<Button className="header-filter-top-button" type="primary" onClick={() => showAccessCluster()}>
|
||||||
|
<IconFont type="icon-jiahao" />
|
||||||
|
<span className="text">接入集群</span>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="header-filter-bottom">
|
||||||
|
<div className="header-filter-bottom-item header-filter-bottom-item-checkbox">
|
||||||
|
<h3 className="header-filter-bottom-item-title">版本分布</h3>
|
||||||
|
<div className="header-filter-bottom-item-content flex">
|
||||||
|
{existKafkaVersion.length ? (
|
||||||
|
<CustomCheckGroup
|
||||||
|
kafkaVersions={existKafkaVersion}
|
||||||
|
onChangeCheckGroup={searchParamsChangeFunc.onChangeCheckGroup}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
||||||
|
<h3 className="header-filter-bottom-item-title title-right">健康分</h3>
|
||||||
|
<div className="header-filter-bottom-item-content">
|
||||||
|
<Slider
|
||||||
|
range
|
||||||
|
step={20}
|
||||||
|
defaultValue={[0, 100]}
|
||||||
|
marks={healthSorceList}
|
||||||
|
onAfterChange={searchParamsChangeFunc.onSilderChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="multi-cluster-filter">
|
||||||
|
<div className="multi-cluster-filter-select">
|
||||||
|
<Select
|
||||||
|
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
|
||||||
|
defaultValue="HealthScore"
|
||||||
|
style={{ width: 170, marginRight: 12 }}
|
||||||
|
>
|
||||||
|
{sortFieldList.map((item) => (
|
||||||
|
<Option key={item.value} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortType', value)}
|
||||||
|
defaultValue="asc"
|
||||||
|
style={{ width: 170 }}
|
||||||
|
>
|
||||||
|
{sortTypes.map((item) => (
|
||||||
|
<Option key={item.value} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="multi-cluster-filter-checkbox">
|
||||||
|
<CheckboxGroup
|
||||||
|
options={statusFilters}
|
||||||
|
value={searchParams.clusterStatus}
|
||||||
|
onChange={searchParamsChangeFunc.onClusterStatusChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="multi-cluster-page-dashboard">
|
||||||
|
<ClusterList
|
||||||
|
searchParams={searchParams}
|
||||||
|
showAccessCluster={showAccessCluster}
|
||||||
|
getPhyClusterState={getPhyClusterState}
|
||||||
|
getExistKafkaVersion={getExistKafkaVersion}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 引导页 */}
|
||||||
|
<TourGuide guide={MultiPageSteps} run={true} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<AccessClusters
|
<AccessClusters
|
||||||
visible={visible}
|
clusterInfo={curClusterInfo}
|
||||||
setVisible={setVisible}
|
|
||||||
kafkaVersion={kafkaVersions}
|
kafkaVersion={kafkaVersions}
|
||||||
afterSubmitSuccess={afterSubmitSuccessAccessClusters}
|
visible={accessClusterVisible}
|
||||||
|
setVisible={setAccessClusterVisible}
|
||||||
|
afterSubmitSuccess={afterAccessCluster}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,38 +1,51 @@
|
|||||||
import { AppContainer, Divider, Form, IconFont, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
|
import { AppContainer, Divider, Form, IconFont, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useEffect, useMemo, useState, useReducer } from 'react';
|
import API from '@src/api';
|
||||||
|
import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import { timeFormat } from '../../constants/common';
|
import { timeFormat, oneDayMillims } from '@src/constants/common';
|
||||||
import { IMetricPoint, linesMetric } from './config';
|
import { IMetricPoint, linesMetric, pointsMetric } from './config';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import api, { MetricType } from '../../api';
|
import api, { MetricType } from '@src/api';
|
||||||
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
||||||
import { ClustersPermissionMap } from '../CommonConfig';
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
import { getUnit, getDataNumberUnit } from '@src/constants/chartConfig';
|
import { getUnit, getDataNumberUnit } from '@src/constants/chartConfig';
|
||||||
import SmallChart from '@src/components/SmallChart';
|
import SmallChart from '@src/components/SmallChart';
|
||||||
|
import { SearchParams } from './HomePage';
|
||||||
|
|
||||||
const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getPhyClusterState: any }) => {
|
const DEFAULT_PAGE_SIZE = 10;
|
||||||
const history = useHistory();
|
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const DeleteCluster = React.forwardRef((_, ref) => {
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [list, setList] = useState<[]>(props.list || []);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [clusterInfo, setClusterInfo] = useState({} as any);
|
|
||||||
const [pagination, setPagination] = useState(
|
|
||||||
props.pagination || {
|
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [visible, setVisible] = useState<boolean>(false);
|
||||||
|
const [clusterInfo, setClusterInfo] = useState<any>({});
|
||||||
|
const callback = useRef(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const onFinish = () => {
|
||||||
setList(props.list || []);
|
form.validateFields().then(() => {
|
||||||
setPagination(props.pagination || {});
|
Utils.delete(api.phyCluster, {
|
||||||
}, [props.list, props.pagination]);
|
params: {
|
||||||
|
clusterPhyId: clusterInfo.id,
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
message.success('删除成功');
|
||||||
|
callback.current();
|
||||||
|
setVisible(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
onOpen: (clusterInfo: any, cbk: () => void) => {
|
||||||
|
setClusterInfo(clusterInfo);
|
||||||
|
callback.current = cbk;
|
||||||
|
setVisible(true);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
@@ -40,19 +53,164 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width={570}
|
||||||
|
destroyOnClose={true}
|
||||||
|
centered={true}
|
||||||
|
className="custom-modal"
|
||||||
|
wrapClassName="del-topic-modal delete-modal"
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.title',
|
||||||
|
})}
|
||||||
|
visible={visible}
|
||||||
|
onOk={onFinish}
|
||||||
|
okText={intl.formatMessage({
|
||||||
|
id: 'btn.delete',
|
||||||
|
})}
|
||||||
|
cancelText={intl.formatMessage({
|
||||||
|
id: 'btn.cancel',
|
||||||
|
})}
|
||||||
|
onCancel={() => setVisible(false)}
|
||||||
|
okButtonProps={{
|
||||||
|
style: {
|
||||||
|
width: 56,
|
||||||
|
},
|
||||||
|
danger: true,
|
||||||
|
size: 'small',
|
||||||
|
}}
|
||||||
|
cancelButtonProps={{
|
||||||
|
style: {
|
||||||
|
width: 56,
|
||||||
|
},
|
||||||
|
size: 'small',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="tip-info">
|
||||||
|
<IconFont type="icon-warning-circle"></IconFont>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.tip',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Form form={form} className="form" labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} autoComplete="off">
|
||||||
|
<Form.Item label="集群名称" name="name">
|
||||||
|
<span>{clusterInfo.name}</span>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="集群名称"
|
||||||
|
name="clusterName"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.cluster',
|
||||||
|
}),
|
||||||
|
validator: (rule: any, value: string) => {
|
||||||
|
value = value || '';
|
||||||
|
if (!value.trim() || value.trim() !== clusterInfo.name)
|
||||||
|
return Promise.reject(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'delete.cluster.confirm.cluster',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any; getPhyClusterState: any; getExistKafkaVersion: any }) => {
|
||||||
|
const { searchParams, showAccessCluster, getPhyClusterState, getExistKafkaVersion } = props;
|
||||||
|
const history = useHistory();
|
||||||
|
const [global] = AppContainer.useGlobalValue();
|
||||||
|
const [isReload, setIsReload] = useState<boolean>(false);
|
||||||
|
const [list, setList] = useState<[]>([]);
|
||||||
|
const [clusterLoading, setClusterLoading] = useState<boolean>(true);
|
||||||
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const deleteModalRef = useRef(null);
|
||||||
|
|
||||||
|
const getClusterList = (pageNo: number, pageSize: number) => {
|
||||||
|
const endTime = new Date().getTime();
|
||||||
|
const startTime = endTime - oneDayMillims;
|
||||||
|
const params = {
|
||||||
|
metricLines: {
|
||||||
|
endTime,
|
||||||
|
metricsNames: linesMetric,
|
||||||
|
startTime,
|
||||||
|
},
|
||||||
|
latestMetricNames: pointsMetric,
|
||||||
|
pageNo: pageNo,
|
||||||
|
pageSize: pageSize,
|
||||||
|
preciseFilterDTOList: [
|
||||||
|
{
|
||||||
|
fieldName: 'kafkaVersion',
|
||||||
|
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rangeFilterDTOList: [
|
||||||
|
{
|
||||||
|
fieldMaxValue: searchParams.healthScoreRange[1],
|
||||||
|
fieldMinValue: searchParams.healthScoreRange[0],
|
||||||
|
fieldName: 'HealthScore',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
searchKeywords: searchParams.keywords,
|
||||||
|
...searchParams.sortInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (searchParams.clusterStatus.length === 1) {
|
||||||
|
params.preciseFilterDTOList.push({
|
||||||
|
fieldName: 'Alive',
|
||||||
|
fieldValueList: searchParams.clusterStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Utils.post(API.phyClustersDashbord, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置集群列表
|
||||||
|
const reloadClusterList = (pageSize = DEFAULT_PAGE_SIZE) => {
|
||||||
|
setClusterLoading(true);
|
||||||
|
getClusterList(1, pageSize)
|
||||||
|
.then((res: any) => {
|
||||||
|
setList(res?.bizData || []);
|
||||||
|
setPagination(res.pagination);
|
||||||
|
})
|
||||||
|
.finally(() => setClusterLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载更多列表
|
||||||
const loadMoreData = async () => {
|
const loadMoreData = async () => {
|
||||||
if (loading) {
|
if (isLoadingMore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setIsLoadingMore(true);
|
||||||
|
|
||||||
const res = await props.loadMoreData(pagination.pageNo + 1, pagination.pageSize);
|
const res: any = await getClusterList(pagination.pageNo + 1, pagination.pageSize);
|
||||||
const _data = list.concat(res.bizData || []) as any;
|
const _data = list.concat(res.bizData || []) as any;
|
||||||
setList(_data);
|
setList(_data);
|
||||||
setPagination(res.pagination);
|
setPagination(res.pagination);
|
||||||
setLoading(false);
|
setIsLoadingMore(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 重载列表
|
||||||
|
useEffect(
|
||||||
|
() => (searchParams.isReloadAll ? reloadClusterList(pagination.pageNo * pagination.pageSize) : reloadClusterList()),
|
||||||
|
[searchParams]
|
||||||
|
);
|
||||||
|
|
||||||
const RenderItem = (itemData: any) => {
|
const RenderItem = (itemData: any) => {
|
||||||
itemData = itemData || {};
|
itemData = itemData || {};
|
||||||
const metrics = linesMetric;
|
const metrics = linesMetric;
|
||||||
@@ -160,7 +318,7 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
title={
|
title={
|
||||||
<span>
|
<span>
|
||||||
尚未开启 {name} 均衡策略,
|
尚未开启 {name} 均衡策略,
|
||||||
<Link to={`/cluster/${itemData.id}/cluster/balance`}>前往开启</Link>
|
<Link to={`/cluster/${itemData.id}/operation/balance`}>前往开启</Link>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -225,11 +383,34 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) ? (
|
{global.hasPermission ? (
|
||||||
<div className="multi-cluster-list-item-btn">
|
<div className="multi-cluster-list-item-btn">
|
||||||
<div className="icon" onClick={(event) => onClickDeleteBtn(event, itemData)}>
|
{global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_INFO) && (
|
||||||
<IconFont type="icon-shanchu1" />
|
<div
|
||||||
</div>
|
className="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showAccessCluster(itemData);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFont type="icon-duojiqunbianji" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) && (
|
||||||
|
<div
|
||||||
|
className="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteModalRef.current.onOpen(itemData, () => {
|
||||||
|
getPhyClusterState();
|
||||||
|
getExistKafkaVersion(true);
|
||||||
|
reloadClusterList(pagination.pageNo * pagination.pageSize);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconFont type="icon-duojiqunshanchu" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@@ -239,45 +420,21 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFinish = () => {
|
|
||||||
form.validateFields().then((formData) => {
|
|
||||||
Utils.delete(api.phyCluster, {
|
|
||||||
params: {
|
|
||||||
clusterPhyId: clusterInfo.id,
|
|
||||||
},
|
|
||||||
}).then((res) => {
|
|
||||||
message.success('删除成功');
|
|
||||||
setVisible(false);
|
|
||||||
props?.getPhyClusterState();
|
|
||||||
const fliterList: any = list.filter((item: any) => {
|
|
||||||
return item?.id !== clusterInfo.id;
|
|
||||||
});
|
|
||||||
setList(fliterList || []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickDeleteBtn = (event: any, clusterInfo: any) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
setClusterInfo(clusterInfo);
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Spin spinning={clusterLoading}>
|
||||||
{useMemo(
|
{useMemo(
|
||||||
() => (
|
() => (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
dataLength={list.length}
|
dataLength={list.length}
|
||||||
next={loadMoreData}
|
next={loadMoreData}
|
||||||
hasMore={list.length < pagination.total}
|
hasMore={list.length < pagination.total}
|
||||||
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={loading} />}
|
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={true} />}
|
||||||
endMessage={
|
endMessage={
|
||||||
!pagination.total ? (
|
!pagination.total ? (
|
||||||
''
|
''
|
||||||
) : (
|
) : (
|
||||||
<Divider className="load-completed-tip" plain>
|
<Divider className="load-completed-tip" plain>
|
||||||
加载完成 共{pagination.total}条
|
加载完成 共 {pagination.total} 条
|
||||||
</Divider>
|
</Divider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -293,81 +450,11 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
|||||||
/>
|
/>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
),
|
),
|
||||||
[list, pagination, loading]
|
[list, pagination, isLoadingMore]
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Modal
|
<DeleteCluster ref={deleteModalRef} />
|
||||||
width={570}
|
</Spin>
|
||||||
destroyOnClose={true}
|
|
||||||
centered={true}
|
|
||||||
className="custom-modal"
|
|
||||||
wrapClassName="del-topic-modal delete-modal"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.title',
|
|
||||||
})}
|
|
||||||
visible={visible}
|
|
||||||
onOk={onFinish}
|
|
||||||
okText={intl.formatMessage({
|
|
||||||
id: 'btn.delete',
|
|
||||||
})}
|
|
||||||
cancelText={intl.formatMessage({
|
|
||||||
id: 'btn.cancel',
|
|
||||||
})}
|
|
||||||
onCancel={() => setVisible(false)}
|
|
||||||
okButtonProps={{
|
|
||||||
style: {
|
|
||||||
width: 56,
|
|
||||||
},
|
|
||||||
danger: true,
|
|
||||||
size: 'small',
|
|
||||||
}}
|
|
||||||
cancelButtonProps={{
|
|
||||||
style: {
|
|
||||||
width: 56,
|
|
||||||
},
|
|
||||||
size: 'small',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="tip-info">
|
|
||||||
<IconFont type="icon-warning-circle"></IconFont>
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.tip',
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Form form={form} className="form" labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} autoComplete="off">
|
|
||||||
<Form.Item label="集群名称" name="name" rules={[{ required: false, message: '' }]}>
|
|
||||||
<span>{clusterInfo.name}</span>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="集群名称"
|
|
||||||
name="clusterName"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.cluster',
|
|
||||||
}),
|
|
||||||
validator: (rule: any, value: string) => {
|
|
||||||
value = value || '';
|
|
||||||
if (!value.trim() || value.trim() !== clusterInfo.name)
|
|
||||||
return Promise.reject(
|
|
||||||
intl.formatMessage({
|
|
||||||
id: 'delete.cluster.confirm.cluster',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export default ClusterList;
|
||||||
export default ListScroll;
|
|
||||||
|
|||||||
@@ -364,8 +364,12 @@
|
|||||||
.multi-cluster-list-item-btn {
|
.multi-cluster-list-item-btn {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
.icon {
|
.icon {
|
||||||
|
width: 24px;
|
||||||
|
background: rgba(33, 37, 41, 0.04);
|
||||||
|
border-radius: 12px;
|
||||||
color: #74788d;
|
color: #74788d;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon:hover {
|
.icon:hover {
|
||||||
@@ -375,16 +379,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.multi-cluster-list-item-btn {
|
.multi-cluster-list-item-btn {
|
||||||
|
display: flex;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: rgba(33, 37, 41, 0.04);
|
|
||||||
border-radius: 14px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user