mirror of
https://github.com/didi/KnowStreaming.git
synced 2025-12-24 11:52:08 +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 * as React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import api from '../../api';
|
||||
import { regClusterName, regUsername } from '../../constants/reg';
|
||||
import api from '@src/api';
|
||||
import { regClusterName, regUsername } from '@src/constants/reg';
|
||||
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
|
||||
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
|
||||
|
||||
@@ -21,40 +21,28 @@ word=\\"xxxxxx\\";"
|
||||
`;
|
||||
|
||||
const AccessClusters = (props: any): JSX.Element => {
|
||||
const { afterSubmitSuccess, clusterInfo, visible } = props;
|
||||
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { afterSubmitSuccess, infoLoading, clusterInfo, visible } = props;
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [security, setSecurity] = React.useState(clusterInfo?.security || 'None');
|
||||
const [curClusterInfo, setCurClusterInfo] = React.useState<any>({});
|
||||
const [security, setSecurity] = React.useState(curClusterInfo?.security || 'None');
|
||||
const [extra, setExtra] = React.useState({
|
||||
versionExtra: '',
|
||||
zooKeeperExtra: '',
|
||||
bootstrapExtra: '',
|
||||
jmxExtra: '',
|
||||
});
|
||||
const [isLowVersion, setIsLowVersion] = React.useState<any>(false);
|
||||
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<any>(false);
|
||||
const [isLowVersion, setIsLowVersion] = React.useState<boolean>(false);
|
||||
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<boolean>(false);
|
||||
|
||||
const lastFormItemValue = React.useRef({
|
||||
bootstrap: clusterInfo?.bootstrapServers || '',
|
||||
zookeeper: clusterInfo?.zookeeper || '',
|
||||
clientProperties: clusterInfo?.clientProperties || {},
|
||||
bootstrap: curClusterInfo?.bootstrapServers || '',
|
||||
zookeeper: curClusterInfo?.zookeeper || '',
|
||||
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) => {
|
||||
Object.keys(value).forEach((key) => {
|
||||
switch (key) {
|
||||
@@ -128,10 +116,10 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
zookeeper: res.zookeeper || '',
|
||||
};
|
||||
setLoading(true);
|
||||
if (!isNaN(clusterInfo?.id)) {
|
||||
if (!isNaN(curClusterInfo?.id)) {
|
||||
Utils.put(api.phyCluster, {
|
||||
...params,
|
||||
id: clusterInfo?.id,
|
||||
id: curClusterInfo?.id,
|
||||
})
|
||||
.then(() => {
|
||||
message.success('编辑成功');
|
||||
@@ -219,7 +207,11 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
});
|
||||
|
||||
// 如果kafkaVersion小于最低版本则提示
|
||||
const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion);
|
||||
const showLowVersion = !(
|
||||
curClusterInfo?.zookeeper ||
|
||||
!curClusterInfo?.kafkaVersion ||
|
||||
curClusterInfo?.kafkaVersion >= lowKafkaVersion
|
||||
);
|
||||
setIsLowVersion(showLowVersion);
|
||||
setExtra({
|
||||
...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 (
|
||||
<>
|
||||
<Drawer
|
||||
@@ -256,16 +297,8 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
placement="right"
|
||||
width={480}
|
||||
>
|
||||
<Spin spinning={loading || !!infoLoading}>
|
||||
<Form
|
||||
form={form}
|
||||
initialValues={{
|
||||
security,
|
||||
...clusterInfo,
|
||||
}}
|
||||
layout="vertical"
|
||||
onValuesChange={onHandleValuesChange}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="集群名称"
|
||||
@@ -277,11 +310,9 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
if (!value) {
|
||||
return Promise.reject('集群名称不能为空');
|
||||
}
|
||||
|
||||
if (value === clusterInfo?.name) {
|
||||
if (value === curClusterInfo?.name) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (value?.length > 128) {
|
||||
return Promise.reject('集群名称长度限制在1~128字符');
|
||||
}
|
||||
@@ -307,13 +338,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
<Form.Item
|
||||
name="bootstrapServers"
|
||||
label="Bootstrap Servers"
|
||||
extra={
|
||||
extra.bootstrapExtra.includes('连接成功') ? (
|
||||
<span>{extra.bootstrapExtra}</span>
|
||||
) : (
|
||||
<span className="error-extra-info">{extra.bootstrapExtra}</span>
|
||||
)
|
||||
}
|
||||
extra={<span className={extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>}
|
||||
validateTrigger={'onBlur'}
|
||||
rules={[
|
||||
{
|
||||
@@ -349,13 +374,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
<Form.Item
|
||||
name="zookeeper"
|
||||
label="Zookeeper"
|
||||
extra={
|
||||
extra.zooKeeperExtra.includes('连接成功') ? (
|
||||
<span>{extra.zooKeeperExtra}</span>
|
||||
) : (
|
||||
<span className="error-extra-info">{extra.zooKeeperExtra}</span>
|
||||
)
|
||||
}
|
||||
extra={<span className={extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
|
||||
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
|
||||
validateTrigger={'onBlur'}
|
||||
rules={[
|
||||
@@ -458,7 +477,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
style={{ width: '58%' }}
|
||||
rules={[
|
||||
{
|
||||
required: security === 'Password' || clusterInfo?.security === 'Password',
|
||||
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
||||
validator: async (rule: any, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('用户名不能为空');
|
||||
@@ -483,7 +502,7 @@ const AccessClusters = (props: any): JSX.Element => {
|
||||
style={{ width: '38%', marginRight: 0 }}
|
||||
rules={[
|
||||
{
|
||||
required: security === 'Password' || clusterInfo?.security === 'Password',
|
||||
required: security === 'Password' || curClusterInfo?.security === 'Password',
|
||||
validator: async (rule: any, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('密码不能为空');
|
||||
|
||||
@@ -1,102 +1,108 @@
|
||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||
import { Checkbox } from 'knowdesign';
|
||||
import { CheckboxValueType } from 'knowdesign/es/basic/checkbox/Group';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const CheckboxGroup = Checkbox.Group;
|
||||
|
||||
interface IVersion {
|
||||
firstLine: string[];
|
||||
leftVersions: string[];
|
||||
}
|
||||
|
||||
const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: any }) => {
|
||||
const { kafkaVersions, onChangeCheckGroup } = props;
|
||||
const [checkedKafkaVersion, setCheckedKafkaVersion] = React.useState<IVersion>({
|
||||
firstLine: [],
|
||||
leftVersions: [],
|
||||
});
|
||||
const [allVersion, setAllVersion] = React.useState<IVersion>({
|
||||
firstLine: [],
|
||||
leftVersions: [],
|
||||
});
|
||||
|
||||
const { kafkaVersions: newVersions, onChangeCheckGroup } = props;
|
||||
const [versions, setVersions] = React.useState<string[]>([]);
|
||||
const [versionsState, setVersionsState] = React.useState<{
|
||||
[key: string]: boolean;
|
||||
}>({});
|
||||
const [indeterminate, setIndeterminate] = React.useState(false);
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleDocumentClick);
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDocumentClick = (e: Event) => {
|
||||
setShowMore(false);
|
||||
};
|
||||
|
||||
const setCheckAllStauts = (list: string[], otherList: string[]) => {
|
||||
onChangeCheckGroup([...list, ...otherList]);
|
||||
setIndeterminate(!!list.length && list.length + otherList.length < kafkaVersions.length);
|
||||
setCheckAll(list.length + otherList.length === kafkaVersions.length);
|
||||
};
|
||||
|
||||
const getTwoPanelVersion = () => {
|
||||
const updateGroupInfo = () => {
|
||||
const width = (document.getElementsByClassName('custom-check-group')[0] as any)?.offsetWidth;
|
||||
const checkgroupWidth = width - 100 - 86;
|
||||
const num = (checkgroupWidth / 108) | 0;
|
||||
const firstLine = Array.from(kafkaVersions).splice(0, num);
|
||||
setMoreGroupWidth(num * 108 + 88 + 66);
|
||||
const leftVersions = Array.from(kafkaVersions).splice(num);
|
||||
return { firstLine, leftVersions };
|
||||
setGroupInfo({
|
||||
width: num * 108 + 88 + 66,
|
||||
num,
|
||||
});
|
||||
};
|
||||
|
||||
const onFirstVersionChange = (list: []) => {
|
||||
setCheckedKafkaVersion({
|
||||
...checkedKafkaVersion,
|
||||
firstLine: list,
|
||||
});
|
||||
|
||||
setCheckAllStauts(list, checkedKafkaVersion.leftVersions);
|
||||
const getCheckedList = (
|
||||
versionState: {
|
||||
[key: string]: boolean;
|
||||
},
|
||||
filterFunc: (item: [string, boolean], i: number) => boolean
|
||||
) => {
|
||||
return Object.entries(versionState)
|
||||
.filter(filterFunc)
|
||||
.map(([key]) => key);
|
||||
};
|
||||
|
||||
const onLeftVersionChange = (list: []) => {
|
||||
setCheckedKafkaVersion({
|
||||
...checkedKafkaVersion,
|
||||
leftVersions: list,
|
||||
const onVersionsChange = (isFirstLine: boolean, list: CheckboxValueType[]) => {
|
||||
const newVersionsState = { ...versionsState };
|
||||
Object.keys(newVersionsState).forEach((key, i) => {
|
||||
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 versions = getTwoPanelVersion();
|
||||
|
||||
setCheckedKafkaVersion(
|
||||
e.target.checked
|
||||
? versions
|
||||
: {
|
||||
firstLine: [],
|
||||
leftVersions: [],
|
||||
}
|
||||
);
|
||||
onChangeCheckGroup(e.target.checked ? [...versions.firstLine, ...versions.leftVersions] : []);
|
||||
const checked = e.target.checked;
|
||||
const newVersionsState = { ...versionsState };
|
||||
Object.keys(newVersionsState).forEach((key) => (newVersionsState[key] = checked));
|
||||
|
||||
setVersionsState(newVersionsState);
|
||||
setIndeterminate(false);
|
||||
setCheckAll(e.target.checked);
|
||||
setCheckAll(checked);
|
||||
onChangeCheckGroup(e.target.checked ? versions : []);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleVersionLine = () => {
|
||||
const versions = getTwoPanelVersion();
|
||||
setAllVersion(versions);
|
||||
setCheckedKafkaVersion(versions);
|
||||
};
|
||||
handleVersionLine();
|
||||
useEffect(() => {
|
||||
const newVersionsState = { ...versionsState };
|
||||
Object.keys(newVersionsState).forEach((key) => {
|
||||
if (!newVersions.includes(key)) {
|
||||
delete newVersionsState[key];
|
||||
}
|
||||
});
|
||||
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); //监听窗口大小改变
|
||||
return () => window.removeEventListener('resize', debounce(handleVersionLine, 500));
|
||||
setVersions([...newVersions]);
|
||||
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 (
|
||||
@@ -107,17 +113,21 @@ const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup:
|
||||
全选
|
||||
</Checkbox>
|
||||
</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 ? (
|
||||
<CheckboxGroup
|
||||
style={{ width: moreGroupWidth }}
|
||||
style={{ width: groupInfo.width }}
|
||||
className="more-check-group"
|
||||
options={allVersion.leftVersions}
|
||||
value={checkedKafkaVersion.leftVersions}
|
||||
onChange={onLeftVersionChange}
|
||||
options={Array.from(versions).splice(groupInfo.num)}
|
||||
value={getCheckedList(versionsState, ([, state], i) => i >= groupInfo.num && state)}
|
||||
onChange={(list) => onVersionsChange(false, list)}
|
||||
/>
|
||||
) : null}
|
||||
{allVersion.leftVersions.length ? (
|
||||
{versions.length > groupInfo.num ? (
|
||||
<div className="more-btn" onClick={() => setShowMore(!showMore)}>
|
||||
<a>
|
||||
{!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 API from '../../api';
|
||||
import API from '@src/api';
|
||||
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
||||
import './index.less';
|
||||
import { healthSorceList, linesMetric, pointsMetric, sortFieldList, sortTypes, statusFilters } from './config';
|
||||
import { oneDayMillims } from '../../constants/common';
|
||||
import ListScroll from './List';
|
||||
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
|
||||
import ClusterList from './List';
|
||||
import AccessClusters from './AccessCluster';
|
||||
import CustomCheckGroup from './CustomCheckGroup';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
@@ -13,98 +12,85 @@ import { ClustersPermissionMap } from '../CommonConfig';
|
||||
const CheckboxGroup = Checkbox.Group;
|
||||
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 [run, setRun] = useState<boolean>(false);
|
||||
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 [existKafkaVersion, setExistKafkaVersion] = React.useState<string[]>([]);
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
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({
|
||||
const [stateInfo, setStateInfo] = React.useState<ClustersState>({
|
||||
downCount: 0,
|
||||
liveCount: 0,
|
||||
total: 0,
|
||||
});
|
||||
const [pagination, setPagination] = useState({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
|
||||
const [searchParams, setSearchParams] = React.useState<SearchParams>({
|
||||
keywords: '',
|
||||
checkedKafkaVersions: [],
|
||||
healthScoreRange: [0, 100],
|
||||
sortInfo: {
|
||||
sortField: 'HealthScore',
|
||||
sortType: 'asc',
|
||||
},
|
||||
clusterStatus: [0, 1],
|
||||
// 是否拉取当前所有数据
|
||||
isReloadAll: false,
|
||||
});
|
||||
|
||||
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 = () => {
|
||||
Utils.request(API.phyClusterState)
|
||||
.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(() => {
|
||||
getPhyClusterState();
|
||||
getSupportKafkaVersion();
|
||||
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 (
|
||||
<>
|
||||
<TourGuide guide={MultiPageSteps} run={run} />
|
||||
{pageLoading ? renderLoading() : stateInfo.total ? renderContent() : renderEmpty()}
|
||||
{pageLoading ? (
|
||||
<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
|
||||
visible={visible}
|
||||
setVisible={setVisible}
|
||||
clusterInfo={curClusterInfo}
|
||||
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 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 { Link, useHistory } from 'react-router-dom';
|
||||
import { timeFormat } from '../../constants/common';
|
||||
import { IMetricPoint, linesMetric } from './config';
|
||||
import { timeFormat, oneDayMillims } from '@src/constants/common';
|
||||
import { IMetricPoint, linesMetric, pointsMetric } from './config';
|
||||
import { useIntl } from 'react-intl';
|
||||
import api, { MetricType } from '../../api';
|
||||
import api, { MetricType } from '@src/api';
|
||||
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
import { getUnit, getDataNumberUnit } from '@src/constants/chartConfig';
|
||||
import SmallChart from '@src/components/SmallChart';
|
||||
import { SearchParams } from './HomePage';
|
||||
|
||||
const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getPhyClusterState: any }) => {
|
||||
const history = useHistory();
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
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 DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
const DeleteCluster = React.forwardRef((_, ref) => {
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [clusterInfo, setClusterInfo] = useState<any>({});
|
||||
const callback = useRef(() => {
|
||||
return;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setList(props.list || []);
|
||||
setPagination(props.pagination || {});
|
||||
}, [props.list, props.pagination]);
|
||||
const onFinish = () => {
|
||||
form.validateFields().then(() => {
|
||||
Utils.delete(api.phyCluster, {
|
||||
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(() => {
|
||||
if (visible) {
|
||||
@@ -40,19 +53,164 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
||||
}
|
||||
}, [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 () => {
|
||||
if (loading) {
|
||||
if (isLoadingMore) {
|
||||
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;
|
||||
setList(_data);
|
||||
setPagination(res.pagination);
|
||||
setLoading(false);
|
||||
setIsLoadingMore(false);
|
||||
};
|
||||
|
||||
// 重载列表
|
||||
useEffect(
|
||||
() => (searchParams.isReloadAll ? reloadClusterList(pagination.pageNo * pagination.pageSize) : reloadClusterList()),
|
||||
[searchParams]
|
||||
);
|
||||
|
||||
const RenderItem = (itemData: any) => {
|
||||
itemData = itemData || {};
|
||||
const metrics = linesMetric;
|
||||
@@ -160,7 +318,7 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
||||
title={
|
||||
<span>
|
||||
尚未开启 {name} 均衡策略,
|
||||
<Link to={`/cluster/${itemData.id}/cluster/balance`}>前往开启</Link>
|
||||
<Link to={`/cluster/${itemData.id}/operation/balance`}>前往开启</Link>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
@@ -225,11 +383,34 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) ? (
|
||||
{global.hasPermission ? (
|
||||
<div className="multi-cluster-list-item-btn">
|
||||
<div className="icon" onClick={(event) => onClickDeleteBtn(event, itemData)}>
|
||||
<IconFont type="icon-shanchu1" />
|
||||
</div>
|
||||
{global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_INFO) && (
|
||||
<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>
|
||||
) : (
|
||||
<></>
|
||||
@@ -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 (
|
||||
<>
|
||||
<Spin spinning={clusterLoading}>
|
||||
{useMemo(
|
||||
() => (
|
||||
<InfiniteScroll
|
||||
dataLength={list.length}
|
||||
next={loadMoreData}
|
||||
hasMore={list.length < pagination.total}
|
||||
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={loading} />}
|
||||
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={true} />}
|
||||
endMessage={
|
||||
!pagination.total ? (
|
||||
''
|
||||
) : (
|
||||
<Divider className="load-completed-tip" plain>
|
||||
加载完成 共{pagination.total}条
|
||||
加载完成 共 {pagination.total} 条
|
||||
</Divider>
|
||||
)
|
||||
}
|
||||
@@ -293,81 +450,11 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
|
||||
/>
|
||||
</InfiniteScroll>
|
||||
),
|
||||
[list, pagination, loading]
|
||||
[list, pagination, isLoadingMore]
|
||||
)}
|
||||
|
||||
<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" 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>
|
||||
</>
|
||||
<DeleteCluster ref={deleteModalRef} />
|
||||
</Spin>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListScroll;
|
||||
export default ClusterList;
|
||||
|
||||
@@ -364,8 +364,12 @@
|
||||
.multi-cluster-list-item-btn {
|
||||
opacity: 1;
|
||||
.icon {
|
||||
width: 24px;
|
||||
background: rgba(33, 37, 41, 0.04);
|
||||
border-radius: 12px;
|
||||
color: #74788d;
|
||||
font-size: 14px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
@@ -375,16 +379,14 @@
|
||||
}
|
||||
|
||||
.multi-cluster-list-item-btn {
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 8px;
|
||||
z-index: 10;
|
||||
text-align: right;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(33, 37, 41, 0.04);
|
||||
border-radius: 14px;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user