feat: 健康状态展示优化
@@ -24,6 +24,7 @@ const api = {
|
|||||||
logout: `${securityPrefix}/account/logout`,
|
logout: `${securityPrefix}/account/logout`,
|
||||||
|
|
||||||
// 全局信息
|
// 全局信息
|
||||||
|
getVersionInfo: () => getApi('/self/version'),
|
||||||
getUserInfo: (userId: number) => `${securityPrefix}/user/${userId}`,
|
getUserInfo: (userId: number) => `${securityPrefix}/user/${userId}`,
|
||||||
getPermissionTree: `${securityPrefix}/permission/tree`,
|
getPermissionTree: `${securityPrefix}/permission/tree`,
|
||||||
getKafkaVersionItems: () => getApi('/kafka-versions-items'),
|
getKafkaVersionItems: () => getApi('/kafka-versions-items'),
|
||||||
@@ -60,6 +61,7 @@ const api = {
|
|||||||
phyClustersDashbord: getApi(`/physical-clusters/dashboard`),
|
phyClustersDashbord: getApi(`/physical-clusters/dashboard`),
|
||||||
supportKafkaVersion: getApi(`/support-kafka-versions`),
|
supportKafkaVersion: getApi(`/support-kafka-versions`),
|
||||||
phyClusterState: getApi(`/physical-clusters/state`),
|
phyClusterState: getApi(`/physical-clusters/state`),
|
||||||
|
phyClusterHealthState: getApi(`/physical-clusters/health-state`),
|
||||||
|
|
||||||
getOperatingStateList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/groups-overview`),
|
getOperatingStateList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/groups-overview`),
|
||||||
getGroupTopicList: (clusterPhyId: number, groupName: string) => getApi(`/clusters/${clusterPhyId}/groups/${groupName}/topics-overview`),
|
getGroupTopicList: (clusterPhyId: number, groupName: string) => getApi(`/clusters/${clusterPhyId}/groups/${groupName}/topics-overview`),
|
||||||
@@ -201,6 +203,14 @@ const api = {
|
|||||||
getJobsTaskData: (clusterPhyId: string, jobId: string | number) => getApi(`/clusters/${clusterPhyId}/jobs/${jobId}/modify-detail`),
|
getJobsTaskData: (clusterPhyId: string, jobId: string | number) => getApi(`/clusters/${clusterPhyId}/jobs/${jobId}/modify-detail`),
|
||||||
//编辑任务
|
//编辑任务
|
||||||
putJobsTaskData: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/jobs`),
|
putJobsTaskData: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/jobs`),
|
||||||
|
|
||||||
|
// Zookeeper 接口
|
||||||
|
getZookeeperState: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeepers-state`),
|
||||||
|
getZookeeperList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeepers-overview`),
|
||||||
|
getZookeeperNodeChildren: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-children`),
|
||||||
|
getZookeeperNodeData: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-data`),
|
||||||
|
getZookeeperMetricsInfo: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeeper-latest-metrics`),
|
||||||
|
getZookeeperMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeeper-metrics`),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -1,11 +1,12 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import CardBar from '@src/components/CardBar';
|
import CardBar from '@src/components/CardBar';
|
||||||
import { healthDataProps } from '.';
|
import { healthDataProps } from '.';
|
||||||
import { Tag, Utils } from 'knowdesign';
|
import { Utils } from 'knowdesign';
|
||||||
import Api from '@src/api';
|
import Api from '@src/api';
|
||||||
import { hashDataParse } from '@src/constants/common';
|
import { hashDataParse } from '@src/constants/common';
|
||||||
|
import { HealthStateEnum } from '../HealthState';
|
||||||
|
|
||||||
export default (props: { record: any }) => {
|
export default (props: { record: any }) => {
|
||||||
const { record } = props;
|
const { record } = props;
|
||||||
@@ -14,22 +15,20 @@ export default (props: { record: any }) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||||
score: 0,
|
state: HealthStateEnum.UNKNOWN,
|
||||||
passed: 0,
|
passed: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
alive: 0,
|
|
||||||
});
|
});
|
||||||
const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'live'];
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Utils.post(Api.getBrokerDetailMetricPoints(hashDataParse(urlLocation.hash)?.brokerId, urlParams?.clusterId), [
|
Utils.post(Api.getBrokerDetailMetricPoints(hashDataParse(urlLocation.hash)?.brokerId, urlParams?.clusterId), [
|
||||||
'Partitions',
|
'Partitions',
|
||||||
'Leaders',
|
'Leaders',
|
||||||
'PartitionURP',
|
'PartitionURP',
|
||||||
'HealthScore',
|
|
||||||
'HealthCheckPassed',
|
'HealthCheckPassed',
|
||||||
'HealthCheckTotal',
|
'HealthCheckTotal',
|
||||||
'Alive',
|
'HealthState',
|
||||||
]).then((data: any) => {
|
]).then((data: any) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
const rightData = JSON.parse(JSON.stringify(data.metrics));
|
const rightData = JSON.parse(JSON.stringify(data.metrics));
|
||||||
@@ -47,14 +46,12 @@ export default (props: { record: any }) => {
|
|||||||
value: rightData['PartitionURP'] || '-',
|
value: rightData['PartitionURP'] || '-',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const healthResData: any = {};
|
|
||||||
healthResData.score = data?.metrics?.['HealthScore'] || 0;
|
|
||||||
healthResData.passed = data?.metrics?.['HealthCheckPassed'] || 0;
|
|
||||||
healthResData.total = data?.metrics?.['HealthCheckTotal'] || 0;
|
|
||||||
healthResData.alive = data?.metrics?.['Alive'] || 0;
|
|
||||||
setCardData(cordRightMap);
|
setCardData(cordRightMap);
|
||||||
setHealthData(healthResData);
|
setHealthData({
|
||||||
// setCardData(data.metrics)
|
state: data?.metrics?.['HealthState'],
|
||||||
|
passed: data?.metrics?.['HealthCheckPassed'] || 0,
|
||||||
|
total: data?.metrics?.['HealthCheckTotal'] || 0,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { healthDataProps } from '.';
|
|||||||
import { Tag, Tooltip, Utils } from 'knowdesign';
|
import { Tag, Tooltip, Utils } from 'knowdesign';
|
||||||
import api from '@src/api';
|
import api from '@src/api';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { HealthStateEnum } from '../HealthState';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const routeParams = useParams<{
|
const routeParams = useParams<{
|
||||||
@@ -14,26 +15,21 @@ export default () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||||
score: 0,
|
state: HealthStateEnum.UNKNOWN,
|
||||||
passed: 0,
|
passed: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
alive: 0,
|
|
||||||
});
|
});
|
||||||
const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize'];
|
const healthItems = ['HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'HealthState'];
|
||||||
const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'Alive'];
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// 获取左侧健康度
|
// 获取左侧健康度
|
||||||
const brokerMetric = Utils.post(api.getBrokerMetricPoints(Number(routeParams.clusterId)), healthItems).then((data: any) => {
|
const brokerMetric = Utils.post(api.getBrokerMetricPoints(Number(routeParams.clusterId)), healthItems).then((data: any) => {
|
||||||
const healthResData: any = {};
|
setHealthData({
|
||||||
// healthResData.score = data?.find((item:any) => item.metricName === 'HealthScore_Brokers')?.value || 0;
|
state: data?.metrics?.['HealthState'],
|
||||||
// healthResData.passed = data?.find((item:any) => item.metricName === 'HealthCheckPassed_Brokers')?.value || 0;
|
passed: data?.metrics?.['HealthCheckPassed_Brokers'] || 0,
|
||||||
// healthResData.total = data?.find((item:any) => item.metricName === 'HealthCheckTotal_Brokers')?.value || 0;
|
total: data?.metrics?.['HealthCheckTotal_Brokers'] || 0,
|
||||||
healthResData.score = data?.metrics?.['HealthScore_Brokers'] || 0;
|
});
|
||||||
healthResData.passed = data?.metrics?.['HealthCheckPassed_Brokers'] || 0;
|
|
||||||
healthResData.total = data?.metrics?.['HealthCheckTotal_Brokers'] || 0;
|
|
||||||
healthResData.alive = data?.metrics?.['Alive'] || 0;
|
|
||||||
setHealthData(healthResData);
|
|
||||||
});
|
});
|
||||||
// 获取右侧状态
|
// 获取右侧状态
|
||||||
const brokersState = Utils.request(api.getBrokersState(routeParams?.clusterId)).then((data) => {
|
const brokersState = Utils.request(api.getBrokersState(routeParams?.clusterId)).then((data) => {
|
||||||
@@ -115,6 +111,6 @@ export default () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [routeParams.clusterId]);
|
}, [routeParams.clusterId]);
|
||||||
// console.log('cardData', cardData, healthData);
|
|
||||||
return <CardBar scene="broker" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
return <CardBar scene="broker" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import CardBar from '@src/components/CardBar';
|
|||||||
import { healthDataProps } from '.';
|
import { healthDataProps } from '.';
|
||||||
import { Utils } from 'knowdesign';
|
import { Utils } from 'knowdesign';
|
||||||
import api from '@src/api';
|
import api from '@src/api';
|
||||||
|
import { HealthStateEnum } from '../HealthState';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const routeParams = useParams<{
|
const routeParams = useParams<{
|
||||||
@@ -12,22 +13,17 @@ export default () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||||
score: 0,
|
state: HealthStateEnum.UNKNOWN,
|
||||||
passed: 0,
|
passed: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
alive: 0,
|
|
||||||
});
|
});
|
||||||
const [healthDetail, setHealthDetail] = useState([]);
|
|
||||||
const cardItems = ['Groups', 'GroupActives', 'GroupEmptys', 'GroupRebalances', 'GroupDeads'];
|
const cardItems = ['Groups', 'GroupActives', 'GroupEmptys', 'GroupRebalances', 'GroupDeads'];
|
||||||
const healthItems = ['HealthScore_Groups', 'HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'Alive'];
|
const healthItems = ['HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'HealthState'];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
|
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
// setCardData(data
|
|
||||||
// .filter((item: any) => cardItems.indexOf(item.metricName) >= 0)
|
|
||||||
// .map((item: any) => ({ title: item.metricName, value: item.value }))
|
|
||||||
// )
|
|
||||||
setCardData(
|
setCardData(
|
||||||
cardItems.map((item) => {
|
cardItems.map((item) => {
|
||||||
if (item === 'GroupDeads') {
|
if (item === 'GroupDeads') {
|
||||||
@@ -36,12 +32,11 @@ export default () => {
|
|||||||
return { title: item, value: data.metrics[item] };
|
return { title: item, value: data.metrics[item] };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const healthResData: any = {};
|
setHealthData({
|
||||||
healthResData.score = data.metrics['HealthScore_Groups'] || 0;
|
state: data?.metrics?.['HealthState'],
|
||||||
healthResData.passed = data.metrics['HealthCheckPassed_Groups'] || 0;
|
passed: data?.metrics?.['HealthCheckPassed_Groups'] || 0,
|
||||||
healthResData.total = data.metrics['HealthCheckTotal_Groups'] || 0;
|
total: data?.metrics?.['HealthCheckTotal_Groups'] || 0,
|
||||||
healthResData.alive = data.metrics['Alive'] || 0;
|
});
|
||||||
setHealthData(healthResData);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
return <CardBar scene="group" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
return <CardBar scene="group" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import CardBar from '@src/components/CardBar';
|
import CardBar from '@src/components/CardBar';
|
||||||
import { healthDataProps } from '.';
|
import { Utils } from 'knowdesign';
|
||||||
import { Tag, Utils } from 'knowdesign';
|
|
||||||
import Api from '@src/api';
|
import Api from '@src/api';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
@@ -12,14 +11,7 @@ export default () => {
|
|||||||
}>();
|
}>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
|
||||||
score: 0,
|
|
||||||
passed: 0,
|
|
||||||
total: 0,
|
|
||||||
alive: 0,
|
|
||||||
});
|
|
||||||
const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize'];
|
|
||||||
const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'alive'];
|
|
||||||
const getCordRightMap = (data: any) => {
|
const getCordRightMap = (data: any) => {
|
||||||
const cordRightMap = [
|
const cordRightMap = [
|
||||||
{
|
{
|
||||||
@@ -49,6 +41,7 @@ export default () => {
|
|||||||
];
|
];
|
||||||
return cordRightMap;
|
return cordRightMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// 获取状态
|
// 获取状态
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const LoadRebalanceCardBar = (props: any) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [normsVisible, setNormsVisible] = useState(null);
|
const [normsVisible, setNormsVisible] = useState(null);
|
||||||
const cardItems = ['AclEnable', 'Acls', 'AclUsers', 'AclTopics', 'AclGroups'];
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setNormsVisible(false);
|
setNormsVisible(false);
|
||||||
};
|
};
|
||||||
@@ -45,11 +44,9 @@ const LoadRebalanceCardBar = (props: any) => {
|
|||||||
// 获取右侧状态
|
// 获取右侧状态
|
||||||
getCartInfo()
|
getCartInfo()
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
// const { AclEnable, Acls, AclUsers, AclTopics, AclGroups } = res.metrics;
|
|
||||||
const { next, sub, status } = res;
|
const { next, sub, status } = res;
|
||||||
const { cpu, disk, bytesIn, bytesOut } = sub;
|
const { cpu, disk, bytesIn, bytesOut } = sub;
|
||||||
const newNextDate: any = transUnitTimePro(moment(next).valueOf() - moment().valueOf());
|
const newNextDate: any = transUnitTimePro(moment(next).valueOf() - moment().valueOf());
|
||||||
// const newNextDate = parseInt(`${transUnitTimePro(moment(next).valueOf() - moment().valueOf())}`);
|
|
||||||
const cardMap = [
|
const cardMap = [
|
||||||
{
|
{
|
||||||
title() {
|
title() {
|
||||||
@@ -80,20 +77,15 @@ const LoadRebalanceCardBar = (props: any) => {
|
|||||||
>
|
>
|
||||||
{!status ? '已均衡' : '未均衡'}
|
{!status ? '已均衡' : '未均衡'}
|
||||||
</Tag>
|
</Tag>
|
||||||
{/* <Tag style={{ padding: '2px 4px', backgroundColor: 'rgba(85,110,230,0.10)', color: '#556EE6' }}>已均衡</Tag> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span>
|
<span>
|
||||||
周期均衡 <IconFont className="cutomIcon" type={`${!status ? 'icon-zhengchang' : 'icon-warning'}`} />
|
周期均衡 <IconFont className="cutomIcon" type={`${!status ? 'icon-zhengchang' : 'icon-warning'}`} />
|
||||||
</span>
|
</span>
|
||||||
{/* <span>
|
|
||||||
周期均衡 <IconFont className="cutomIcon" type="icon-zhengchang" />
|
|
||||||
</span> */}
|
|
||||||
<span>
|
<span>
|
||||||
距下次均衡还剩{newNextDate?.value || 0}
|
距下次均衡还剩{newNextDate?.value || 0}
|
||||||
{newNextDate?.unit || '分钟'}
|
{newNextDate?.unit || '分钟'}
|
||||||
</span>
|
</span>
|
||||||
{/* {<span>距下次均衡还剩{1}小时</span>} */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -106,73 +98,6 @@ const LoadRebalanceCardBar = (props: any) => {
|
|||||||
padding: '12px 12px 8px 12px',
|
padding: '12px 12px 8px 12px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// // title: 'CPU avg',
|
|
||||||
// title() {
|
|
||||||
// return (
|
|
||||||
// <div>
|
|
||||||
// <span style={{ display: 'inline-block', marginRight: '8px' }}>CPU AVG</span>
|
|
||||||
// {!cpu?.interval && cpu?.interval !== 0 && (
|
|
||||||
// <Tooltip overlayClassName="rebalance-tooltip" title="未设置均衡策略">
|
|
||||||
// <QuestionCircleOutlined />
|
|
||||||
// </Tooltip>
|
|
||||||
// )}
|
|
||||||
// {/* <IconFont className="cutomIcon" onClick={() => setNormsVisible(true)} type="icon-shezhi"></IconFont> */}
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// value(visibleType: boolean) {
|
|
||||||
// return (
|
|
||||||
// <div id="CPU" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
|
|
||||||
// <div style={{ display: 'inline-block' }}>
|
|
||||||
// <div style={{ margin: '5px 0', fontFamily: 'DIDIFD-Medium' }}>
|
|
||||||
// <span style={{ fontSize: '24px' }}>{cpu?.avg || 0}</span>
|
|
||||||
// <span style={{ fontSize: '14px', display: 'inline-block', marginLeft: '4px' }}>%</span>
|
|
||||||
// </div>
|
|
||||||
// <div style={{ marginTop: '-4px', display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
// <span>均衡区间: ±{cpu?.interval || 0}%</span>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// <Popover
|
|
||||||
// // visible={visibleType} // 修改为hover柱状图
|
|
||||||
// overlayClassName="custom-popover"
|
|
||||||
// content={
|
|
||||||
// <div style={{ color: '#495057' }}>
|
|
||||||
// <div>
|
|
||||||
// <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
|
|
||||||
// 超过均衡区间的有: {cpu?.bigNu || 0}
|
|
||||||
// </div>
|
|
||||||
// <div style={{ margin: '6px 0' }}>
|
|
||||||
// <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
|
|
||||||
// 在均衡区间内的有: {cpu?.betweenNu || 0}
|
|
||||||
// </div>
|
|
||||||
// <div>
|
|
||||||
// <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
|
|
||||||
// 低于均衡区间的有: {cpu?.smallNu || 0}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// }
|
|
||||||
// getPopupContainer={(triggerNode: any) => {
|
|
||||||
// return triggerNode;
|
|
||||||
// }}
|
|
||||||
// color="#ffffff"
|
|
||||||
// >
|
|
||||||
// <div style={{ width: '44px', height: '30px' }}>
|
|
||||||
// <StateChart
|
|
||||||
// data={[
|
|
||||||
// { name: 'bigNu', value: cpu?.bigNu || 0 },
|
|
||||||
// { name: 'betweenNu', value: cpu?.betweenNu || 0 },
|
|
||||||
// { name: 'smallNu', value: cpu?.smallNu || 0 },
|
|
||||||
// ]}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// </Popover>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// className: 'custom-card-bar',
|
|
||||||
// valueClassName: 'custom-card-bar-value',
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title() {
|
title() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { healthDataProps } from '.';
|
|||||||
import { Utils } from 'knowdesign';
|
import { Utils } from 'knowdesign';
|
||||||
import { IconFont } from '@knowdesign/icons';
|
import { IconFont } from '@knowdesign/icons';
|
||||||
import api from '@src/api';
|
import api from '@src/api';
|
||||||
import { healthScoreCondition } from './const';
|
|
||||||
import { hashDataParse } from '@src/constants/common';
|
import { hashDataParse } from '@src/constants/common';
|
||||||
|
import { HealthStateEnum } from '../HealthState';
|
||||||
|
|
||||||
|
const healthItems = ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState'];
|
||||||
|
|
||||||
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
|
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
|
||||||
return typeof v === 'function' ? v(visibleType) : v;
|
return typeof v === 'function' ? v(visibleType) : v;
|
||||||
@@ -19,14 +21,12 @@ export default (props: { record: any }) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||||
score: 0,
|
state: HealthStateEnum.UNKNOWN,
|
||||||
passed: 0,
|
passed: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
alive: 0,
|
|
||||||
});
|
});
|
||||||
const [healthDetail, setHealthDetail] = useState([]);
|
|
||||||
const [clusterAlive, setClusterAlive] = useState(0);
|
const [clusterAlive, setClusterAlive] = useState(0);
|
||||||
const healthItems = ['HealthScore', 'HealthCheckPassed', 'HealthCheckTotal', 'alive'];
|
|
||||||
const getNumAndSubTitles = (cardColumnsItemData: any) => {
|
const getNumAndSubTitles = (cardColumnsItemData: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', display: 'flex', alignItems: 'end' }}>
|
<div style={{ width: '100%', display: 'flex', alignItems: 'end' }}>
|
||||||
@@ -40,21 +40,21 @@ export default (props: { record: any }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const topicName = hashDataParse(location.hash)['topicName'];
|
const topicName = hashDataParse(location.hash)['topicName'];
|
||||||
let detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then(
|
const detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
let healthResData: any = {};
|
setHealthData({
|
||||||
healthResData.score = data.metrics['HealthScore'] || 0;
|
state: data.metrics['HealthState'],
|
||||||
healthResData.passed = data.metrics['HealthCheckPassed'] || 0;
|
passed: data.metrics['HealthCheckPassed'] || 0,
|
||||||
healthResData.total = data.metrics['HealthCheckTotal'] || 0;
|
total: data.metrics['HealthCheckTotal'] || 0,
|
||||||
// healthResData.alive = data.metrics['alive'] || 0
|
});
|
||||||
setHealthData(healthResData);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => {
|
const detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => {
|
||||||
setCardData([
|
setCardData([
|
||||||
{
|
{
|
||||||
title: 'Partitions',
|
title: 'Partitions',
|
||||||
@@ -87,13 +87,12 @@ export default (props: { record: any }) => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
// 获取集群维度的指标信息
|
// 获取集群维度的指标信息
|
||||||
let clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then(
|
const clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then(
|
||||||
(clusterHealthState: any) => {
|
(clusterHealthState: any) => {
|
||||||
let clusterAlive = clusterHealthState?.metrics?.Alive || 0;
|
setClusterAlive(clusterHealthState?.metrics?.Alive || 0);
|
||||||
setClusterAlive(clusterAlive);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then((res) => {
|
Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@@ -101,7 +100,7 @@ export default (props: { record: any }) => {
|
|||||||
<CardBar
|
<CardBar
|
||||||
record={record}
|
record={record}
|
||||||
scene="topic"
|
scene="topic"
|
||||||
healthData={{ ...healthData, alive: clusterAlive }}
|
healthData={{ ...healthData, state: clusterAlive ? healthData.state : HealthStateEnum.DOWN }}
|
||||||
cardColumns={cardData}
|
cardColumns={cardData}
|
||||||
showCardBg={false}
|
showCardBg={false}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import CardBar from '@src/components/CardBar';
|
|||||||
import { healthDataProps } from '.';
|
import { healthDataProps } from '.';
|
||||||
import { Utils } from 'knowdesign';
|
import { Utils } from 'knowdesign';
|
||||||
import api from '@src/api';
|
import api from '@src/api';
|
||||||
|
import { HealthStateEnum } from '../HealthState';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const routeParams = useParams<{
|
const routeParams = useParams<{
|
||||||
@@ -12,14 +13,12 @@ export default () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [cardData, setCardData] = useState([]);
|
const [cardData, setCardData] = useState([]);
|
||||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||||
score: 0,
|
state: HealthStateEnum.UNKNOWN,
|
||||||
passed: 0,
|
passed: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
alive: 0,
|
|
||||||
});
|
});
|
||||||
const [healthDetail, setHealthDetail] = useState([]);
|
|
||||||
const cardItems = ['Topics', 'Partitions', 'PartitionNoLeader', 'PartitionMinISR_S', 'PartitionMinISR_E', 'PartitionURP'];
|
const cardItems = ['Topics', 'Partitions', 'PartitionNoLeader', 'PartitionMinISR_S', 'PartitionMinISR_E', 'PartitionURP'];
|
||||||
const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'Alive'];
|
const healthItems = ['HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'HealthState'];
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
|
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
|
||||||
@@ -42,12 +41,6 @@ export default () => {
|
|||||||
PartitionURP: 'URP',
|
PartitionURP: 'URP',
|
||||||
PartitionNoLeader: 'No Leader',
|
PartitionNoLeader: 'No Leader',
|
||||||
};
|
};
|
||||||
// setCardData(data
|
|
||||||
// .filter(item => cardItems.indexOf(item.name) >= 0)
|
|
||||||
// .map(item => {
|
|
||||||
// return { title: metricElmMap[item.name] || item.name, value: item.value }
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
setCardData(
|
setCardData(
|
||||||
cardItems.map((item) => {
|
cardItems.map((item) => {
|
||||||
let title = item;
|
let title = item;
|
||||||
@@ -66,12 +59,11 @@ export default () => {
|
|||||||
return { title, value: data.metrics[item] };
|
return { title, value: data.metrics[item] };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const healthResData: any = {};
|
setHealthData({
|
||||||
healthResData.score = data.metrics['HealthScore_Topics'] || 0;
|
state: data.metrics['HealthState'],
|
||||||
healthResData.passed = data.metrics['HealthCheckPassed_Topics'] || 0;
|
passed: data.metrics['HealthCheckPassed_Topics'] || 0,
|
||||||
healthResData.total = data.metrics['HealthCheckTotal_Topics'] || 0;
|
total: data.metrics['HealthCheckTotal_Topics'] || 0,
|
||||||
healthResData.alive = data.metrics['Alive'] || 0;
|
});
|
||||||
setHealthData(healthResData);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
return <CardBar scene="topic" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
return <CardBar scene="topic" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import CardBar, { healthDataProps } from './index';
|
||||||
|
import { Utils } from 'knowdesign';
|
||||||
|
import api from '@src/api';
|
||||||
|
import { HealthStateEnum } from '../HealthState';
|
||||||
|
|
||||||
|
interface ZookeeperState {
|
||||||
|
aliveFollowerCount: number;
|
||||||
|
aliveObserverCount: number;
|
||||||
|
aliveServerCount: number;
|
||||||
|
healthCheckPassed: number;
|
||||||
|
healthCheckTotal: number;
|
||||||
|
healthState: number;
|
||||||
|
leaderNode: string;
|
||||||
|
totalFollowerCount: number;
|
||||||
|
totalObserverCount: number;
|
||||||
|
totalServerCount: number;
|
||||||
|
watchCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVal = (val: string | number | undefined | null) => {
|
||||||
|
return val === undefined || val === null || val === '' ? '-' : val;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZookeeperCard = () => {
|
||||||
|
const { clusterId } = useParams<{
|
||||||
|
clusterId: string;
|
||||||
|
}>();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [cardData, setCardData] = useState([]);
|
||||||
|
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||||
|
state: HealthStateEnum.UNKNOWN,
|
||||||
|
passed: 0,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getHealthData = () => {
|
||||||
|
return Utils.post(api.getZookeeperMetricsInfo(Number(clusterId)), ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState']).then(
|
||||||
|
(data: any) => {
|
||||||
|
setHealthData({
|
||||||
|
state: data?.metrics?.['HealthState'],
|
||||||
|
passed: data?.metrics?.['HealthCheckPassed'] || 0,
|
||||||
|
total: data?.metrics?.['HealthCheckTotal'] || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCardInfo = () => {
|
||||||
|
return Utils.request(api.getZookeeperState(clusterId)).then((res: ZookeeperState) => {
|
||||||
|
const {
|
||||||
|
aliveFollowerCount,
|
||||||
|
aliveObserverCount,
|
||||||
|
aliveServerCount,
|
||||||
|
totalFollowerCount,
|
||||||
|
totalObserverCount,
|
||||||
|
totalServerCount,
|
||||||
|
watchCount,
|
||||||
|
leaderNode,
|
||||||
|
} = res || {};
|
||||||
|
const cardMap = [
|
||||||
|
{
|
||||||
|
title: 'Node Count',
|
||||||
|
value() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{aliveServerCount || '-'}/{totalServerCount || '-'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
customStyle: {
|
||||||
|
// 自定义cardbar样式
|
||||||
|
marginLeft: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Watch Count',
|
||||||
|
value: getVal(watchCount),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Leader',
|
||||||
|
value() {
|
||||||
|
return <span style={{ fontSize: 24 }}>{leaderNode || '-'}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Follower',
|
||||||
|
value() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{getVal(aliveFollowerCount)}/{getVal(totalFollowerCount)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Observer',
|
||||||
|
value() {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{getVal(aliveObserverCount)}/{getVal(totalObserverCount)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
setCardData(cardMap);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
Promise.all([getHealthData(), getCardInfo()]).finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, [clusterId]);
|
||||||
|
return <CardBar scene="zookeeper" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ZookeeperCard;
|
||||||
@@ -10,48 +10,15 @@
|
|||||||
height: 88px;
|
height: 88px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
// justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.card-bar-health {
|
.card-bar-health {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
// justify-content: space-between;
|
|
||||||
.card-bar-health-process {
|
.card-bar-health-process {
|
||||||
height: 100%;
|
padding-top: 30px;
|
||||||
margin-right: 24px;
|
margin-right: 20px;
|
||||||
.dcloud-progress-inner {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.dcloud-progress-status-normal {
|
|
||||||
.dcloud-progress-inner {
|
|
||||||
background: rgba(85, 110, 230, 0.03);
|
|
||||||
}
|
|
||||||
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
|
|
||||||
stroke: rgb(85, 110, 230);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dcloud-progress-status-success {
|
|
||||||
.dcloud-progress-inner {
|
|
||||||
background: rgba(0, 192, 162, 0.03);
|
|
||||||
}
|
|
||||||
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
|
|
||||||
stroke: rgb(0, 192, 162);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dcloud-progress-status-exception {
|
|
||||||
.dcloud-progress-inner {
|
|
||||||
background: rgba(255, 112, 102, 0.03);
|
|
||||||
}
|
|
||||||
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
|
|
||||||
stroke: rgb(255, 112, 102);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dcloud-progress-inner {
|
|
||||||
font-family: DIDIFD-Regular;
|
|
||||||
font-size: 40px !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.state {
|
.state {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -61,20 +28,6 @@
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.health-status-image {
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
.health-status-image-success {
|
|
||||||
background-image: url('../../assets/health-status-success.png');
|
|
||||||
}
|
|
||||||
.health-status-image-exception {
|
|
||||||
background-image: url('../../assets/health-status-exception.png');
|
|
||||||
}
|
|
||||||
.health-status-image-normal {
|
|
||||||
background-image: url('../../assets/health-status-normal.png');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.value-bar {
|
.value-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Drawer, Select, Spin, Table } from 'knowdesign';
|
import { Drawer, Spin, Table, Utils } from 'knowdesign';
|
||||||
import { IconFont } from '@knowdesign/icons';
|
import { IconFont } from '@knowdesign/icons';
|
||||||
import { Utils, Progress } from 'knowdesign';
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import api from '@src/api';
|
import api from '@src/api';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import TagsWithHide from '../TagsWithHide/index';
|
import TagsWithHide from '../TagsWithHide/index';
|
||||||
import { getHealthProcessColor } from '@src/pages/SingleClusterDetail/config';
|
import HealthState, { getHealthStateDesc, getHealthStateEmoji, HealthStateEnum } from '../HealthState';
|
||||||
|
import { getConfigItemDetailDesc } from '@src/pages/SingleClusterDetail/config';
|
||||||
|
|
||||||
export interface healthDataProps {
|
export interface healthDataProps {
|
||||||
score: number;
|
state: HealthStateEnum;
|
||||||
passed: number;
|
passed: number;
|
||||||
total: number;
|
total: number;
|
||||||
alive: number;
|
|
||||||
}
|
}
|
||||||
export interface CardBarProps {
|
export interface CardBarProps {
|
||||||
cardColumns?: any[];
|
cardColumns?: any[];
|
||||||
healthData?: healthDataProps;
|
healthData?: healthDataProps;
|
||||||
showCardBg?: boolean;
|
showCardBg?: boolean;
|
||||||
scene: 'topic' | 'broker' | 'group';
|
scene: 'topic' | 'broker' | 'group' | 'zookeeper';
|
||||||
record?: any;
|
record?: any;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
needProgress?: boolean;
|
needProgress?: boolean;
|
||||||
@@ -27,36 +26,27 @@ export interface CardBarProps {
|
|||||||
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
|
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
|
||||||
return typeof v === 'function' ? v(visibleType) : v;
|
return typeof v === 'function' ? v(visibleType) : v;
|
||||||
};
|
};
|
||||||
const statusTxtEmojiMap = {
|
|
||||||
success: {
|
|
||||||
emoji: '👍',
|
|
||||||
txt: '优异',
|
|
||||||
},
|
|
||||||
normal: {
|
|
||||||
emoji: '😊',
|
|
||||||
txt: '正常',
|
|
||||||
},
|
|
||||||
exception: {
|
|
||||||
emoji: '👻',
|
|
||||||
txt: '异常',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const sceneCodeMap = {
|
const sceneCodeMap = {
|
||||||
topic: {
|
|
||||||
code: 2,
|
|
||||||
fieldName: 'topicName',
|
|
||||||
alias: 'Topics',
|
|
||||||
},
|
|
||||||
broker: {
|
broker: {
|
||||||
code: 1,
|
code: 1,
|
||||||
fieldName: 'brokerId',
|
fieldName: 'brokerId',
|
||||||
alias: 'Brokers',
|
alias: 'Brokers',
|
||||||
},
|
},
|
||||||
|
topic: {
|
||||||
|
code: 2,
|
||||||
|
fieldName: 'topicName',
|
||||||
|
alias: 'Topics',
|
||||||
|
},
|
||||||
group: {
|
group: {
|
||||||
code: 3,
|
code: 3,
|
||||||
fieldName: 'groupName',
|
fieldName: 'groupName',
|
||||||
alias: 'Consumers',
|
alias: 'Consumers',
|
||||||
},
|
},
|
||||||
|
zookeeper: {
|
||||||
|
code: 4,
|
||||||
|
fieldName: 'zookeeperId',
|
||||||
|
alias: 'Zookeeper',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const CardColumnsItem: any = (cardItem: any) => {
|
const CardColumnsItem: any = (cardItem: any) => {
|
||||||
const { cardColumnsItemData, showCardBg } = cardItem;
|
const { cardColumnsItemData, showCardBg } = cardItem;
|
||||||
@@ -92,16 +82,7 @@ const CardBar = (props: CardBarProps) => {
|
|||||||
}>();
|
}>();
|
||||||
const { healthData, cardColumns, showCardBg = true, scene, record, loading, needProgress = true } = props;
|
const { healthData, cardColumns, showCardBg = true, scene, record, loading, needProgress = true } = props;
|
||||||
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
||||||
const [progressStatus, setProgressStatus] = useState<'success' | 'exception' | 'normal'>('success');
|
|
||||||
const [healthCheckDetailList, setHealthCheckDetailList] = useState([]);
|
const [healthCheckDetailList, setHealthCheckDetailList] = useState([]);
|
||||||
const [isAlive, setIsAlive] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (healthData) {
|
|
||||||
setProgressStatus(!isAlive ? 'exception' : healthData.score >= 90 ? 'success' : 'normal');
|
|
||||||
setIsAlive(healthData.alive === 1);
|
|
||||||
}
|
|
||||||
}, [healthData, isAlive]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sceneObj = sceneCodeMap[scene];
|
const sceneObj = sceneCodeMap[scene];
|
||||||
@@ -120,23 +101,24 @@ const CardBar = (props: CardBarProps) => {
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '检查项',
|
title: '检查项',
|
||||||
dataIndex: 'configDesc',
|
dataIndex: 'checkConfig',
|
||||||
key: 'configDesc',
|
render(config: any, record: any) {
|
||||||
},
|
let valueGroup = {};
|
||||||
{
|
try {
|
||||||
title: '权重',
|
valueGroup = JSON.parse(config.value);
|
||||||
dataIndex: 'weightPercent',
|
} catch (e) {
|
||||||
key: 'weightPercent',
|
//
|
||||||
},
|
}
|
||||||
{
|
return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-';
|
||||||
title: '得分',
|
},
|
||||||
dataIndex: 'score',
|
|
||||||
key: 'score',
|
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// title: '得分',
|
||||||
|
// dataIndex: 'score',
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
title: '检查时间',
|
title: '检查时间',
|
||||||
dataIndex: 'updateTime',
|
dataIndex: 'updateTime',
|
||||||
key: 'updateTime',
|
|
||||||
render: (value: number) => {
|
render: (value: number) => {
|
||||||
return moment(value).format('YYYY-MM-DD HH:mm:ss');
|
return moment(value).format('YYYY-MM-DD HH:mm:ss');
|
||||||
},
|
},
|
||||||
@@ -144,8 +126,6 @@ const CardBar = (props: CardBarProps) => {
|
|||||||
{
|
{
|
||||||
title: '检查结果',
|
title: '检查结果',
|
||||||
dataIndex: 'passed',
|
dataIndex: 'passed',
|
||||||
key: 'passed',
|
|
||||||
width: 280,
|
|
||||||
render(value: boolean, record: any) {
|
render(value: boolean, record: any) {
|
||||||
const icon = value ? <IconFont type="icon-zhengchang"></IconFont> : <IconFont type="icon-yichang"></IconFont>;
|
const icon = value ? <IconFont type="icon-zhengchang"></IconFont> : <IconFont type="icon-yichang"></IconFont>;
|
||||||
const txt = value ? '已通过' : '未通过';
|
const txt = value ? '已通过' : '未通过';
|
||||||
@@ -168,40 +148,13 @@ const CardBar = (props: CardBarProps) => {
|
|||||||
{!loading && healthData && needProgress && (
|
{!loading && healthData && needProgress && (
|
||||||
<div className="card-bar-health">
|
<div className="card-bar-health">
|
||||||
<div className="card-bar-health-process">
|
<div className="card-bar-health-process">
|
||||||
<Progress
|
<HealthState state={healthData?.state} width={74} height={74} />
|
||||||
width={70}
|
|
||||||
type="circle"
|
|
||||||
percent={!isAlive ? 100 : healthData.score}
|
|
||||||
status={progressStatus}
|
|
||||||
format={(percent, successPercent) => {
|
|
||||||
return !isAlive ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontFamily: 'HelveticaNeue-Medium',
|
|
||||||
fontSize: 22,
|
|
||||||
color: getHealthProcessColor(healthData.score, healthData.alive),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Down
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
textIndent: Math.round(percent) >= 100 ? '-4px' : '',
|
|
||||||
color: getHealthProcessColor(healthData.score, healthData.alive),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Math.round(percent)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
strokeWidth={3}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="state">
|
<div className="state">
|
||||||
<div className={`health-status-image health-status-image-${progressStatus}`}></div>
|
{getHealthStateEmoji(healthData?.state)}
|
||||||
{sceneCodeMap[scene].alias}状态{statusTxtEmojiMap[progressStatus].txt}
|
{sceneCodeMap[scene].alias}
|
||||||
|
{getHealthStateDesc(healthData?.state)}
|
||||||
</div>
|
</div>
|
||||||
<div className="value-bar">
|
<div className="value-bar">
|
||||||
<div className="value">{`${healthData?.passed}/${healthData?.total}`}</div>
|
<div className="value">{`${healthData?.passed}/${healthData?.total}`}</div>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.health-state {
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import GoodState from '@src/assets/health-good.png';
|
||||||
|
import MediumState from '@src/assets/health-medium.png';
|
||||||
|
import PoorState from '@src/assets/health-poor.png';
|
||||||
|
import DownState from '@src/assets/health-down.png';
|
||||||
|
import UnknownState from '@src/assets/health-unknown.png';
|
||||||
|
import GoodStateEmoji from '@src/assets/health-good-emoji.png';
|
||||||
|
import MediumStateEmoji from '@src/assets/health-medium-emoji.png';
|
||||||
|
import PoorStateEmoji from '@src/assets/health-poor-emoji.png';
|
||||||
|
import DownStateEmoji from '@src/assets/health-down-emoji.png';
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
|
export enum HealthStateEnum {
|
||||||
|
UNKNOWN = -1,
|
||||||
|
GOOD,
|
||||||
|
MEDIUM,
|
||||||
|
POOR,
|
||||||
|
DOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HealthStateProps {
|
||||||
|
state: HealthStateEnum;
|
||||||
|
width: string | number;
|
||||||
|
height: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEALTH_STATE_MAP = {
|
||||||
|
[HealthStateEnum.GOOD]: GoodState,
|
||||||
|
[HealthStateEnum.MEDIUM]: MediumState,
|
||||||
|
[HealthStateEnum.POOR]: PoorState,
|
||||||
|
[HealthStateEnum.DOWN]: DownState,
|
||||||
|
[HealthStateEnum.UNKNOWN]: UnknownState,
|
||||||
|
};
|
||||||
|
|
||||||
|
const HEALTH_STATE_EMOJI_MAP = {
|
||||||
|
[HealthStateEnum.GOOD]: GoodStateEmoji,
|
||||||
|
[HealthStateEnum.MEDIUM]: MediumStateEmoji,
|
||||||
|
[HealthStateEnum.POOR]: PoorStateEmoji,
|
||||||
|
[HealthStateEnum.DOWN]: DownStateEmoji,
|
||||||
|
[HealthStateEnum.UNKNOWN]: DownStateEmoji,
|
||||||
|
};
|
||||||
|
|
||||||
|
const HEALTH_STATE_DESC_MAP = {
|
||||||
|
[HealthStateEnum.GOOD]: '状态优异',
|
||||||
|
[HealthStateEnum.MEDIUM]: '状态良好',
|
||||||
|
[HealthStateEnum.POOR]: '状态较差',
|
||||||
|
[HealthStateEnum.DOWN]: '状态异常',
|
||||||
|
[HealthStateEnum.UNKNOWN]: '状态异常',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getHealthStateEmoji = (state: HealthStateEnum, width = 16, height = 16) => {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
style={{ marginTop: -3 }}
|
||||||
|
src={HEALTH_STATE_EMOJI_MAP[state] || HEALTH_STATE_EMOJI_MAP[HealthStateEnum.UNKNOWN]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getHealthStateDesc = (state: HealthStateEnum) => {
|
||||||
|
return HEALTH_STATE_DESC_MAP[state] || HEALTH_STATE_DESC_MAP[HealthStateEnum.UNKNOWN];
|
||||||
|
};
|
||||||
|
|
||||||
|
const HealthState = (props: HealthStateProps) => {
|
||||||
|
const { state, width, height } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="health-state" style={{ width, height }}>
|
||||||
|
<img src={HEALTH_STATE_MAP[state] || UnknownState} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HealthState;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer } from 'knowdesign';
|
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer, Tooltip } from 'knowdesign';
|
||||||
import { IconFont } from '@knowdesign/icons';
|
import { IconFont } from '@knowdesign/icons';
|
||||||
import API from '@src/api';
|
import API from '@src/api';
|
||||||
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
||||||
import './index.less';
|
import { healthSorceList, sliderValueMap, sortFieldList, sortTypes, statusFilters } from './config';
|
||||||
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
|
|
||||||
import ClusterList from './List';
|
import ClusterList 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';
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
const CheckboxGroup = Checkbox.Group;
|
const CheckboxGroup = Checkbox.Group;
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
@@ -19,8 +19,17 @@ interface ClustersState {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ClustersHealthState {
|
||||||
|
deadCount: number;
|
||||||
|
goodCount: number;
|
||||||
|
mediumCount: number;
|
||||||
|
poorCount: number;
|
||||||
|
total: number;
|
||||||
|
unknownCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchParams {
|
export interface SearchParams {
|
||||||
healthScoreRange?: [number, number];
|
healthState?: number[];
|
||||||
checkedKafkaVersions?: string[];
|
checkedKafkaVersions?: string[];
|
||||||
sortInfo?: {
|
sortInfo?: {
|
||||||
sortField: string;
|
sortField: string;
|
||||||
@@ -74,16 +83,24 @@ const MultiClusterPage = () => {
|
|||||||
liveCount: 0,
|
liveCount: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
|
const [clustersHealthState, setClustersHealthState] = React.useState<ClustersHealthState>();
|
||||||
|
const [sliderInfo, setSliderInfo] = React.useState<{
|
||||||
|
value: [number, number];
|
||||||
|
desc: string;
|
||||||
|
}>({
|
||||||
|
value: [0, 5],
|
||||||
|
desc: '',
|
||||||
|
});
|
||||||
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
|
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
|
||||||
const [searchParams, setSearchParams] = React.useState<SearchParams>({
|
const [searchParams, setSearchParams] = React.useState<SearchParams>({
|
||||||
keywords: '',
|
keywords: '',
|
||||||
checkedKafkaVersions: [],
|
checkedKafkaVersions: [],
|
||||||
healthScoreRange: [0, 100],
|
|
||||||
sortInfo: {
|
sortInfo: {
|
||||||
sortField: 'HealthScore',
|
sortField: 'HealthState',
|
||||||
sortType: 'asc',
|
sortType: 'asc',
|
||||||
},
|
},
|
||||||
clusterStatus: [0, 1],
|
clusterStatus: [0, 1],
|
||||||
|
healthState: [-1, 0, 1, 2, 3],
|
||||||
// 是否拉取当前所有数据
|
// 是否拉取当前所有数据
|
||||||
isReloadAll: false,
|
isReloadAll: false,
|
||||||
});
|
});
|
||||||
@@ -91,10 +108,28 @@ const MultiClusterPage = () => {
|
|||||||
const searchKeyword = useRef('');
|
const searchKeyword = useRef('');
|
||||||
const isReload = useRef(false);
|
const isReload = useRef(false);
|
||||||
|
|
||||||
|
const getPhyClusterHealthState = () => {
|
||||||
|
Utils.request(API.phyClusterHealthState).then((res: ClustersHealthState) => {
|
||||||
|
setClustersHealthState(res || undefined);
|
||||||
|
|
||||||
|
const result: string[] = [];
|
||||||
|
for (let i = 0; i < sliderInfo.value[1] - sliderInfo.value[0]; i++) {
|
||||||
|
const val = sliderValueMap[(sliderInfo.value[1] - i) as keyof typeof sliderValueMap];
|
||||||
|
result.push(`${val.name}: ${res?.[val.key as keyof ClustersHealthState]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSliderInfo((cur) => ({
|
||||||
|
...cur,
|
||||||
|
desc: result.reverse().join(', '),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 获取集群状态
|
// 获取集群状态
|
||||||
const getPhyClusterState = () => {
|
const getPhyClusterState = () => {
|
||||||
|
getPhyClusterHealthState();
|
||||||
Utils.request(API.phyClusterState)
|
Utils.request(API.phyClusterState)
|
||||||
.then((res: any) => {
|
.then((res: ClustersState) => {
|
||||||
setStateInfo(res);
|
setStateInfo(res);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -111,13 +146,14 @@ const MultiClusterPage = () => {
|
|||||||
|
|
||||||
const updateSearchParams = (params: SearchParams) => {
|
const updateSearchParams = (params: SearchParams) => {
|
||||||
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
|
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
|
||||||
|
getPhyClusterHealthState();
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchParamsChangeFunc = {
|
const searchParamsChangeFunc = {
|
||||||
// 健康分改变
|
// 健康分改变
|
||||||
onSilderChange: (value: [number, number]) =>
|
onSilderChange: (value: number[]) =>
|
||||||
updateSearchParams({
|
updateSearchParams({
|
||||||
healthScoreRange: value,
|
healthState: value,
|
||||||
}),
|
}),
|
||||||
// 排序信息改变
|
// 排序信息改变
|
||||||
onSortInfoChange: (type: string, value: string) =>
|
onSortInfoChange: (type: string, value: string) =>
|
||||||
@@ -251,16 +287,41 @@ const MultiClusterPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
||||||
<h3 className="header-filter-bottom-item-title title-right">健康分</h3>
|
<h3 className="header-filter-bottom-item-title title-right">健康状态</h3>
|
||||||
<div className="header-filter-bottom-item-content">
|
<Tooltip title={sliderInfo.desc} overlayClassName="cluster-health-state-tooltip">
|
||||||
<Slider
|
<div className="header-filter-bottom-item-content" id="clusters-slider">
|
||||||
range
|
<Slider
|
||||||
step={20}
|
dots
|
||||||
defaultValue={[0, 100]}
|
range={{ draggableTrack: true }}
|
||||||
marks={healthSorceList}
|
step={1}
|
||||||
onAfterChange={searchParamsChangeFunc.onSilderChange}
|
max={5}
|
||||||
/>
|
marks={healthSorceList}
|
||||||
</div>
|
value={sliderInfo.value}
|
||||||
|
tooltipVisible={false}
|
||||||
|
onChange={(value: [number, number]) => {
|
||||||
|
if (value[0] !== value[1]) {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < value[1] - value[0]; i++) {
|
||||||
|
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
|
||||||
|
result.push(`${val.name}: ${clustersHealthState?.[val.key as keyof ClustersHealthState]}`);
|
||||||
|
}
|
||||||
|
setSliderInfo({
|
||||||
|
value,
|
||||||
|
desc: result.reverse().join(', '),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onAfterChange={(value: [number, number]) => {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < value[1] - value[0]; i++) {
|
||||||
|
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
|
||||||
|
result.push(val.code);
|
||||||
|
}
|
||||||
|
searchParamsChangeFunc.onSilderChange(result);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -269,7 +330,7 @@ const MultiClusterPage = () => {
|
|||||||
<div className="multi-cluster-filter-select">
|
<div className="multi-cluster-filter-select">
|
||||||
<Select
|
<Select
|
||||||
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
|
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
|
||||||
defaultValue="HealthScore"
|
defaultValue="HealthState"
|
||||||
style={{ width: 170, marginRight: 12 }}
|
style={{ width: 170, marginRight: 12 }}
|
||||||
>
|
>
|
||||||
{sortFieldList.map((item) => (
|
{sortFieldList.map((item) => (
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import { timeFormat, oneDayMillims } from '@src/constants/common';
|
|||||||
import { IMetricPoint, linesMetric, pointsMetric } from './config';
|
import { IMetricPoint, linesMetric, pointsMetric } from './config';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import api, { MetricType } from '@src/api';
|
import api, { MetricType } from '@src/api';
|
||||||
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
|
||||||
import { ClustersPermissionMap } from '../CommonConfig';
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
import { getDataUnit } from '@src/constants/chartConfig';
|
import { getDataUnit } from '@src/constants/chartConfig';
|
||||||
import SmallChart from '@src/components/SmallChart';
|
import SmallChart from '@src/components/SmallChart';
|
||||||
|
import HealthState, { HealthStateEnum } from '@src/components/HealthState';
|
||||||
import { SearchParams } from './HomePage';
|
import { SearchParams } from './HomePage';
|
||||||
|
|
||||||
const DEFAULT_PAGE_SIZE = 10;
|
const DEFAULT_PAGE_SIZE = 10;
|
||||||
|
|
||||||
enum ClusterRunState {
|
export enum ClusterRunState {
|
||||||
Raft = 2,
|
Raft = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,12 +165,9 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
|
|||||||
fieldName: 'kafkaVersion',
|
fieldName: 'kafkaVersion',
|
||||||
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
|
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
|
||||||
},
|
},
|
||||||
],
|
|
||||||
rangeFilterDTOList: [
|
|
||||||
{
|
{
|
||||||
fieldMaxValue: searchParams.healthScoreRange[1],
|
fieldName: 'HealthState',
|
||||||
fieldMinValue: searchParams.healthScoreRange[0],
|
fieldValueList: searchParams.healthState,
|
||||||
fieldName: 'HealthScore',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
searchKeywords: searchParams.keywords,
|
searchKeywords: searchParams.keywords,
|
||||||
@@ -257,7 +254,7 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
|
|||||||
Zookeepers: zks,
|
Zookeepers: zks,
|
||||||
HealthCheckPassed: healthCheckPassed,
|
HealthCheckPassed: healthCheckPassed,
|
||||||
HealthCheckTotal: healthCheckTotal,
|
HealthCheckTotal: healthCheckTotal,
|
||||||
HealthScore: healthScore,
|
HealthState: healthState,
|
||||||
ZookeepersAvailable: zookeepersAvailable,
|
ZookeepersAvailable: zookeepersAvailable,
|
||||||
LoadReBalanceCpu: loadReBalanceCpu,
|
LoadReBalanceCpu: loadReBalanceCpu,
|
||||||
LoadReBalanceDisk: loadReBalanceDisk,
|
LoadReBalanceDisk: loadReBalanceDisk,
|
||||||
@@ -272,28 +269,16 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
|
|||||||
history.push(`/cluster/${itemData.id}/cluster`);
|
history.push(`/cluster/${itemData.id}/cluster`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={'multi-cluster-list-item'}>
|
<div className="multi-cluster-list-item">
|
||||||
<div className="multi-cluster-list-item-healthy">
|
<div className="multi-cluster-list-item-healthy">
|
||||||
<Progress
|
<div className="healthy-box">
|
||||||
type="circle"
|
<HealthState state={healthState} width={70} height={70} />
|
||||||
status={!itemData.alive ? 'exception' : healthScore >= 90 ? 'success' : 'normal'}
|
<div className="healthy-degree">
|
||||||
strokeWidth={4}
|
<span className="healthy-degree-status">通过</span>
|
||||||
// className={healthScore > 90 ? 'green-circle' : ''}
|
<span className="healthy-degree-proportion">
|
||||||
className={+itemData.alive <= 0 ? 'red-circle' : +healthScore < 90 ? 'blue-circle' : 'green-circle'}
|
{healthCheckPassed}/{healthCheckTotal}
|
||||||
strokeColor={getHealthProcessColor(healthScore, itemData.alive)}
|
</span>
|
||||||
percent={itemData.alive ? healthScore : 100}
|
</div>
|
||||||
format={() => (
|
|
||||||
<div className={`healthy-percent ${getHealthClassName(healthScore, itemData?.alive)}`}>
|
|
||||||
{getHealthText(healthScore, itemData?.alive)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
width={70}
|
|
||||||
/>
|
|
||||||
<div className="healthy-degree">
|
|
||||||
<span className="healthy-degree-status">通过</span>
|
|
||||||
<span className="healthy-degree-proportion">
|
|
||||||
{healthCheckPassed}/{healthCheckTotal}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="multi-cluster-list-item-right">
|
<div className="multi-cluster-list-item-right">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { HealthStateEnum } from '@src/components/HealthState';
|
||||||
import { FormItemType, IFormItem } from 'knowdesign/es/extend/x-form';
|
import { FormItemType, IFormItem } from 'knowdesign/es/extend/x-form';
|
||||||
|
|
||||||
export const bootstrapServersErrCodes = [10, 11, 12];
|
export const bootstrapServersErrCodes = [10, 11, 12];
|
||||||
@@ -21,8 +22,8 @@ export const sortFieldList = [
|
|||||||
value: 'createTime',
|
value: 'createTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '健康分',
|
label: '健康状态',
|
||||||
value: 'HealthScore',
|
value: 'HealthState',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Messages',
|
label: 'Messages',
|
||||||
@@ -71,18 +72,41 @@ export const metricNameMap = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sliderValueMap = {
|
||||||
|
1: {
|
||||||
|
code: HealthStateEnum.GOOD,
|
||||||
|
key: 'goodCount',
|
||||||
|
name: '好',
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
code: HealthStateEnum.MEDIUM,
|
||||||
|
key: 'mediumCount',
|
||||||
|
name: '中',
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
code: HealthStateEnum.POOR,
|
||||||
|
key: 'poorCount',
|
||||||
|
name: '差',
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
code: HealthStateEnum.DOWN,
|
||||||
|
key: 'deadCount',
|
||||||
|
name: 'Down',
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
code: HealthStateEnum.UNKNOWN,
|
||||||
|
key: 'unknownCount',
|
||||||
|
name: 'Unknown',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const healthSorceList = {
|
export const healthSorceList = {
|
||||||
0: 0,
|
0: '',
|
||||||
10: '',
|
1: '好',
|
||||||
20: 20,
|
2: '中',
|
||||||
30: '',
|
3: '差',
|
||||||
40: 40,
|
4: 'Down',
|
||||||
50: '',
|
5: 'Unknown',
|
||||||
60: 60,
|
|
||||||
70: '',
|
|
||||||
80: 80,
|
|
||||||
90: '',
|
|
||||||
100: 100,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IMetricPoint {
|
export interface IMetricPoint {
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
@error-color: #f46a6a;
|
@error-color: #f46a6a;
|
||||||
|
|
||||||
|
.cluster-health-state-tooltip {
|
||||||
|
.dcloud-tooltip-arrow,
|
||||||
|
.dcloud-tooltip-inner {
|
||||||
|
margin-bottom: -10px;
|
||||||
|
}
|
||||||
|
.dcloud-tooltip-inner {
|
||||||
|
padding: 2px 4px;
|
||||||
|
min-height: 25px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.multi-cluster-page {
|
.multi-cluster-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -229,6 +241,9 @@
|
|||||||
&-slider {
|
&-slider {
|
||||||
width: 298px;
|
width: 298px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
.dcloud-slider-mark {
|
||||||
|
left: -27px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
@@ -404,59 +419,29 @@
|
|||||||
transition: 0.5s all;
|
transition: 0.5s all;
|
||||||
|
|
||||||
&-healthy {
|
&-healthy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
.dcloud-progress-inner {
|
.healthy-box {
|
||||||
border-radius: 50%;
|
position: relative;
|
||||||
}
|
height: 70px;
|
||||||
.green-circle {
|
margin-top: 2px;
|
||||||
.dcloud-progress-inner {
|
|
||||||
background: #f5fdfc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.red-circle {
|
|
||||||
.dcloud-progress-inner {
|
|
||||||
background: #fffafa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.healthy-percent {
|
|
||||||
font-family: DIDIFD-Regular;
|
|
||||||
font-size: 40px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 36px;
|
|
||||||
color: #00c0a2;
|
|
||||||
|
|
||||||
&.less-90 {
|
|
||||||
color: @primary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-info {
|
|
||||||
color: #e9e7e7;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.down {
|
|
||||||
font-family: @font-family-bold;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #ff7066;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.healthy-degree {
|
.healthy-degree {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
font-family: @font-family-bold;
|
font-family: @font-family-bold;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
line-height: 15px;
|
line-height: 1;
|
||||||
margin-top: 6px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
&-status {
|
&-status {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
color: #74788d;
|
color: #74788d;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-proportion {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,27 @@ import { useIntl } from 'react-intl';
|
|||||||
import { getHealthySettingColumn } from './config';
|
import { getHealthySettingColumn } from './config';
|
||||||
import API from '../../api';
|
import API from '../../api';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import notification from '@src/components/Notification';
|
||||||
|
|
||||||
|
interface HealthConfig {
|
||||||
|
dimensionCode: number;
|
||||||
|
dimensionName: string;
|
||||||
|
configGroup: string;
|
||||||
|
configItem: string;
|
||||||
|
configName: string;
|
||||||
|
configDesc: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const { clusterId } = useParams<{ clusterId: string }>();
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [initialValues, setInitialValues] = useState({} as any);
|
const [initialValues, setInitialValues] = useState({} as any);
|
||||||
|
|
||||||
const [data, setData] = React.useState([]);
|
const [data, setData] = React.useState<HealthConfig[]>([]);
|
||||||
const { clusterId } = useParams<{ clusterId: string }>();
|
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
setVisible,
|
setVisible,
|
||||||
@@ -23,29 +34,25 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const getHealthconfig = () => {
|
const getHealthconfig = () => {
|
||||||
return Utils.request(API.getClusterHealthyConfigs(+clusterId)).then((res: any) => {
|
return Utils.request(API.getClusterHealthyConfigs(+clusterId)).then((res: HealthConfig[]) => {
|
||||||
const values = {} as any;
|
const values = {} as any;
|
||||||
|
res.sort((a, b) => a.dimensionCode - b.dimensionCode);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
res = res.map((item: any) => {
|
res.forEach((item) => {
|
||||||
const itemValue = JSON.parse(item.value);
|
const itemValue = JSON.parse(item.value);
|
||||||
item.weight = itemValue?.weight;
|
const { value, latestMinutes, detectedTimes, amount, ratio } = itemValue;
|
||||||
|
|
||||||
item.configItemName =
|
value && (values[`value_${item.configItem}`] = value);
|
||||||
item.configItem.indexOf('Group Re-Balance') > -1
|
latestMinutes && (values[`latestMinutes_${item.configItem}`] = latestMinutes);
|
||||||
? 'ReBalance'
|
detectedTimes && (values[`detectedTimes_${item.configItem}`] = detectedTimes);
|
||||||
: item.configItem.includes('副本未同步')
|
amount && (values[`amount_${item.configItem}`] = amount);
|
||||||
? 'UNDER_REPLICA'
|
ratio && (values[`ratio_${item.configItem}`] = ratio);
|
||||||
: item.configItem;
|
|
||||||
|
|
||||||
values[`weight_${item.configItemName}`] = itemValue?.weight;
|
|
||||||
values[`value_${item.configItemName}`] = itemValue?.value;
|
|
||||||
values[`latestMinutes_${item.configItemName}`] = itemValue?.latestMinutes;
|
|
||||||
values[`detectedTimes_${item.configItemName}`] = itemValue?.detectedTimes;
|
|
||||||
return item;
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
//
|
notification.error({
|
||||||
|
message: '健康项检查规则解析失败',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const formItemsValue = {
|
const formItemsValue = {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
@@ -70,10 +77,11 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
|||||||
clusterId: +clusterId,
|
clusterId: +clusterId,
|
||||||
value: JSON.stringify({
|
value: JSON.stringify({
|
||||||
clusterPhyId: +clusterId,
|
clusterPhyId: +clusterId,
|
||||||
detectedTimes: res[`detectedTimes_${item.configItemName}`],
|
detectedTimes: res[`detectedTimes_${item.configItem}`],
|
||||||
latestMinutes: res[`latestMinutes_${item.configItemName}`],
|
latestMinutes: res[`latestMinutes_${item.configItem}`],
|
||||||
weight: res[`weight_${item.configItemName}`],
|
amount: res[`amount_${item.configItem}`],
|
||||||
value: item.configItemName === 'Controller' ? 1 : res[`value_${item.configItemName}`],
|
ratio: res[`ratio_${item.configItem}`],
|
||||||
|
value: item.configItem === 'Controller' ? 1 : res[`value_${item.configItem}`],
|
||||||
}),
|
}),
|
||||||
valueGroup: item.configGroup,
|
valueGroup: item.configGroup,
|
||||||
valueName: item.configName,
|
valueName: item.configName,
|
||||||
@@ -120,7 +128,7 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
|||||||
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
|
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
|
||||||
<ProTable
|
<ProTable
|
||||||
tableProps={{
|
tableProps={{
|
||||||
rowKey: 'dimensionCode',
|
rowKey: 'configItem',
|
||||||
showHeader: false,
|
showHeader: false,
|
||||||
dataSource: data,
|
dataSource: data,
|
||||||
columns: getHealthySettingColumn(form, data, clusterId),
|
columns: getHealthySettingColumn(form, data, clusterId),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppContainer, Divider, Progress, Tooltip, Utils } from 'knowdesign';
|
import { AppContainer, Divider, Tooltip, Utils } from 'knowdesign';
|
||||||
import { IconFont } from '@knowdesign/icons';
|
import { IconFont } from '@knowdesign/icons';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import AccessClusters from '../MutliClusterPage/AccessCluster';
|
import AccessClusters from '../MutliClusterPage/AccessCluster';
|
||||||
@@ -7,8 +7,9 @@ import API from '../../api';
|
|||||||
import HealthySetting from './HealthySetting';
|
import HealthySetting from './HealthySetting';
|
||||||
import CheckDetail from './CheckDetail';
|
import CheckDetail from './CheckDetail';
|
||||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||||
import { getHealthClassName, getHealthProcessColor, getHealthState, getHealthText, renderToolTipValue } from './config';
|
import { renderToolTipValue } from './config';
|
||||||
import { ClustersPermissionMap } from '../CommonConfig';
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
|
import HealthState, { getHealthStateDesc, getHealthStateEmoji } from '@src/components/HealthState';
|
||||||
|
|
||||||
const LeftSider = () => {
|
const LeftSider = () => {
|
||||||
const [global] = AppContainer.useGlobalValue();
|
const [global] = AppContainer.useGlobalValue();
|
||||||
@@ -40,6 +41,7 @@ const LeftSider = () => {
|
|||||||
return Utils.post(
|
return Utils.post(
|
||||||
API.getPhyClusterMetrics(+clusterId),
|
API.getPhyClusterMetrics(+clusterId),
|
||||||
[
|
[
|
||||||
|
'HealthState',
|
||||||
'HealthScore',
|
'HealthScore',
|
||||||
'HealthCheckPassed',
|
'HealthCheckPassed',
|
||||||
'HealthCheckTotal',
|
'HealthCheckTotal',
|
||||||
@@ -102,23 +104,12 @@ const LeftSider = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="left-sider">
|
<div className="left-sider">
|
||||||
<div className="state-card">
|
<div className="state-card">
|
||||||
<Progress
|
<HealthState state={clusterMetrics?.HealthState} width={74} height={74} />
|
||||||
type="circle"
|
|
||||||
status="active"
|
|
||||||
strokeWidth={4}
|
|
||||||
strokeColor={getHealthProcessColor(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}
|
|
||||||
percent={clusterMetrics?.HealthScore ?? '-'}
|
|
||||||
className={+clusterMetrics.Alive <= 0 ? 'red-circle' : +clusterMetrics?.HealthScore < 90 ? 'blue-circle' : 'green-circle'}
|
|
||||||
format={() => (
|
|
||||||
<div className={`healthy-percent ${getHealthClassName(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}`}>
|
|
||||||
{getHealthText(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
width={75}
|
|
||||||
/>
|
|
||||||
<div className="healthy-state">
|
<div className="healthy-state">
|
||||||
<div className="healthy-state-status">
|
<div className="healthy-state-status">
|
||||||
<span>{getHealthState(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}</span>
|
<span>
|
||||||
|
{getHealthStateEmoji(clusterMetrics?.HealthState)} 集群{getHealthStateDesc(clusterMetrics?.HealthState)}
|
||||||
|
</span>
|
||||||
{/* 健康分设置 */}
|
{/* 健康分设置 */}
|
||||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_HEALTHY) ? (
|
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_HEALTHY) ? (
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -7,30 +7,15 @@ import { IconFont } from '@knowdesign/icons';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { systemKey } from '../../constants/menu';
|
import { systemKey } from '../../constants/menu';
|
||||||
|
|
||||||
const statusTxtEmojiMap = {
|
|
||||||
success: {
|
|
||||||
emoji: '👍',
|
|
||||||
txt: '优异',
|
|
||||||
},
|
|
||||||
normal: {
|
|
||||||
emoji: '😊',
|
|
||||||
txt: '正常',
|
|
||||||
},
|
|
||||||
exception: {
|
|
||||||
emoji: '👻',
|
|
||||||
txt: '异常',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dimensionMap = {
|
export const dimensionMap = {
|
||||||
'-1': {
|
// '-1': {
|
||||||
label: 'Unknown',
|
// label: 'Unknown',
|
||||||
href: ``,
|
// href: ``,
|
||||||
},
|
// },
|
||||||
0: {
|
// 0: {
|
||||||
label: 'Cluster',
|
// label: 'Cluster',
|
||||||
href: ``,
|
// href: ``,
|
||||||
},
|
// },
|
||||||
1: {
|
1: {
|
||||||
label: 'Broker',
|
label: 'Broker',
|
||||||
href: `/broker`,
|
href: `/broker`,
|
||||||
@@ -43,30 +28,100 @@ export const dimensionMap = {
|
|||||||
label: 'ConsumerGroup',
|
label: 'ConsumerGroup',
|
||||||
href: `/consumers`,
|
href: `/consumers`,
|
||||||
},
|
},
|
||||||
|
4: {
|
||||||
|
label: 'Zookeeper',
|
||||||
|
href: '/zookeeper',
|
||||||
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
export const getHealthState = (value: number, down: number) => {
|
const toLowerCase = (name = '') => {
|
||||||
if (value === undefined) return '-';
|
const [first, ...rest] = name.split('');
|
||||||
const progressStatus = +down <= 0 ? 'exception' : value >= 90 ? 'success' : 'normal';
|
return first.toUpperCase() + rest.join('').toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const CONFIG_ITEM_DETAIL_DESC = {
|
||||||
|
Controller: () => {
|
||||||
|
return '集群 Controller 数等于 1';
|
||||||
|
},
|
||||||
|
RequestQueueSize: (valueGroup: any) => {
|
||||||
|
return `Broker-RequestQueueSize 小于 ${valueGroup?.value}`;
|
||||||
|
},
|
||||||
|
NoLeader: (valueGroup: any) => {
|
||||||
|
return `Topic 无 Leader 数小于 ${valueGroup?.value}`;
|
||||||
|
},
|
||||||
|
NetworkProcessorAvgIdlePercent: (valueGroup: any) => {
|
||||||
|
return `Broker-NetworkProcessorAvgIdlePercent 的 idle 大于 ${valueGroup?.value * 100}%`;
|
||||||
|
},
|
||||||
|
UnderReplicaTooLong: (valueGroup: any) => {
|
||||||
|
return `Topic 小于 ${parseFloat(((valueGroup?.detectedTimes / valueGroup?.latestMinutes) * 100).toFixed(2))}% 周期处于未同步状态`;
|
||||||
|
},
|
||||||
|
'Group Re-Balance': (valueGroup: any) => {
|
||||||
|
return `Consumer Group 小于 ${parseFloat(
|
||||||
|
((valueGroup?.detectedTimes / valueGroup?.latestMinutes) * 100).toFixed(2)
|
||||||
|
)}% 周期处于 Re-balance 状态`;
|
||||||
|
},
|
||||||
|
BrainSplit: () => {
|
||||||
|
return `Zookeeper 未脑裂`;
|
||||||
|
},
|
||||||
|
OutstandingRequests: (valueGroup: any) => {
|
||||||
|
return `Zookeeper 请求堆积数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||||
|
},
|
||||||
|
WatchCount: (valueGroup: any) => {
|
||||||
|
return `Zookeeper 订阅数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||||
|
},
|
||||||
|
AliveConnections: (valueGroup: any) => {
|
||||||
|
return `Zookeeper 连接数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||||
|
},
|
||||||
|
ApproximateDataSize: (valueGroup: any) => {
|
||||||
|
return `Zookeeper 数据大小小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||||
|
},
|
||||||
|
SentRate: (valueGroup: any) => {
|
||||||
|
return `Zookeeper 首发包数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConfigItemDetailDesc = (item: keyof typeof CONFIG_ITEM_DETAIL_DESC, valueGroup: any) => {
|
||||||
|
return CONFIG_ITEM_DETAIL_DESC[item]?.(valueGroup);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormItem = (params: { configItem: string; type?: string; percent?: boolean; attrs?: any; validator?: any }) => {
|
||||||
|
const { validator, configItem, percent, type = 'value', attrs = { min: 0 } } = params;
|
||||||
return (
|
return (
|
||||||
<span>
|
<Form.Item
|
||||||
{statusTxtEmojiMap[progressStatus].emoji} 集群状态{statusTxtEmojiMap[progressStatus].txt}
|
name={`${type}_${configItem}`}
|
||||||
</span>
|
label=""
|
||||||
|
rules={
|
||||||
|
validator
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: validator,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{percent ? (
|
||||||
|
<InputNumber
|
||||||
|
size="small"
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
style={{ width: 86 }}
|
||||||
|
formatter={(value) => `${value * 100}%`}
|
||||||
|
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHealthText = (value: number, down: number) => {
|
|
||||||
return +down <= 0 ? 'Down' : value ? value.toFixed(0) : '-';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getHealthProcessColor = (value: number, down: number) => {
|
|
||||||
return +down <= 0 ? '#FF7066' : +value < 90 ? '#556EE6' : '#00C0A2';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getHealthClassName = (value: number, down: number) => {
|
|
||||||
return +down <= 0 ? 'down' : value === undefined ? 'no-info' : +value < 90 ? 'less-90' : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderToolTipValue = (value: string, num: number) => {
|
export const renderToolTipValue = (value: string, num: number) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -88,48 +143,40 @@ export const getDetailColumn = (clusterId: number) => [
|
|||||||
title: '检查模块',
|
title: '检查模块',
|
||||||
dataIndex: 'dimension',
|
dataIndex: 'dimension',
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
render: (text: number) => {
|
render: (text: number, record: any) => {
|
||||||
if (text === 0 || text === -1) return dimensionMap[text]?.label;
|
return dimensionMap[text] ? (
|
||||||
return <Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{dimensionMap[text]?.label}</Link>;
|
<Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{toLowerCase(record?.dimensionName)}</Link>
|
||||||
|
) : (
|
||||||
|
toLowerCase(record?.dimensionName)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '检查项',
|
title: '检查项',
|
||||||
dataIndex: 'checkConfig',
|
dataIndex: 'checkConfig',
|
||||||
render(config: any, record: any) {
|
render(config: any, record: any) {
|
||||||
const valueGroup = JSON.parse(config.value);
|
let valueGroup = {};
|
||||||
if (record.configItem === 'Controller') {
|
try {
|
||||||
return '集群 Controller 数等于 1';
|
valueGroup = JSON.parse(config.value);
|
||||||
} else if (record.configItem === 'RequestQueueSize') {
|
} catch (e) {
|
||||||
return `Broker-RequestQueueSize 小于 ${valueGroup.value}`;
|
//
|
||||||
} else if (record.configItem === 'NoLeader') {
|
|
||||||
return `Topic 无 Leader 数小于 ${valueGroup.value}`;
|
|
||||||
} else if (record.configItem === 'NetworkProcessorAvgIdlePercent') {
|
|
||||||
return `Broker-NetworkProcessorAvgIdlePercent 的 idle 大于 ${valueGroup.value}%`;
|
|
||||||
} else if (record.configItem === 'UnderReplicaTooLong') {
|
|
||||||
return `Topic 小于 ${parseFloat(((valueGroup.detectedTimes / valueGroup.latestMinutes) * 100).toFixed(2))}% 周期处于未同步状态`;
|
|
||||||
} else if (record.configItem === 'Group Re-Balance') {
|
|
||||||
return `Consumer Group 小于 ${parseFloat(
|
|
||||||
((valueGroup.detectedTimes / valueGroup.latestMinutes) * 100).toFixed(2)
|
|
||||||
)}% 周期处于 Re-balance 状态`;
|
|
||||||
}
|
}
|
||||||
|
return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-';
|
||||||
return <></>;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: '权重',
|
// title: '权重',
|
||||||
dataIndex: 'weightPercent',
|
// dataIndex: 'weightPercent',
|
||||||
width: 80,
|
// width: 80,
|
||||||
render(value: number) {
|
// render(value: number) {
|
||||||
return `${value}%`;
|
// return `${value}%`;
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
title: '得分',
|
// title: '得分',
|
||||||
dataIndex: 'score',
|
// dataIndex: 'score',
|
||||||
width: 60,
|
// width: 60,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: '检查时间',
|
title: '检查时间',
|
||||||
width: 190,
|
width: 190,
|
||||||
@@ -168,168 +215,160 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string)
|
|||||||
{
|
{
|
||||||
title: '检查模块',
|
title: '检查模块',
|
||||||
dataIndex: 'dimensionCode',
|
dataIndex: 'dimensionCode',
|
||||||
|
width: 140,
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
render: (text: number) => {
|
render: (text: number, record: any) => {
|
||||||
if (text === 0 || text === -1) return dimensionMap[text]?.label;
|
return dimensionMap[text] ? (
|
||||||
return <Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{dimensionMap[text]?.label}</Link>;
|
<Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{toLowerCase(record?.dimensionName)}</Link>
|
||||||
|
) : (
|
||||||
|
toLowerCase(record?.dimensionName)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '检查项',
|
title: '检查项',
|
||||||
dataIndex: 'configItem',
|
dataIndex: 'configItem',
|
||||||
width: 200,
|
width: 230,
|
||||||
needTooltip: true,
|
needTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '检查项描述',
|
title: '检查项描述',
|
||||||
dataIndex: 'configDesc',
|
dataIndex: 'configDesc',
|
||||||
width: 240,
|
width: 310,
|
||||||
needToolTip: true,
|
needToolTip: true,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: '权重',
|
// title: '权重',
|
||||||
dataIndex: 'weight',
|
// dataIndex: 'weight',
|
||||||
// width: 180,
|
// // width: 180,
|
||||||
// eslint-disable-next-line react/display-name
|
// // eslint-disable-next-line react/display-name
|
||||||
render: (text: number, record: any) => {
|
// render: (text: number, record: any) => {
|
||||||
return (
|
// return (
|
||||||
<>
|
// <>
|
||||||
<Form.Item
|
// <Form.Item
|
||||||
name={`weight_${record.configItemName}`}
|
// name={`weight_${record.configItemName}`}
|
||||||
label=""
|
// label=""
|
||||||
rules={[
|
// rules={[
|
||||||
{
|
// {
|
||||||
required: true,
|
// required: true,
|
||||||
validator: async (rule: any, value: string) => {
|
// validator: async (rule: any, value: string) => {
|
||||||
const otherWeightCongigName: string[] = [];
|
// const otherWeightCongigName: string[] = [];
|
||||||
let totalPercent = 0;
|
// let totalPercent = 0;
|
||||||
data.map((item: any) => {
|
// data.map((item: any) => {
|
||||||
if (item.configItemName !== record.configItemName) {
|
// if (item.configItemName !== record.configItemName) {
|
||||||
otherWeightCongigName.push(`weight_${item.configItemName}`);
|
// otherWeightCongigName.push(`weight_${item.configItemName}`);
|
||||||
totalPercent += form.getFieldValue(`weight_${item.configItemName}`) ?? 0;
|
// totalPercent += form.getFieldValue(`weight_${item.configItemName}`) ?? 0;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
if (!value) {
|
// if (!value) {
|
||||||
return Promise.reject('请输入权重');
|
// return Promise.reject('请输入权重');
|
||||||
}
|
// }
|
||||||
if (+value < 0) {
|
// if (+value < 0) {
|
||||||
return Promise.reject('最小为0');
|
// return Promise.reject('最小为0');
|
||||||
}
|
// }
|
||||||
if (+value + totalPercent !== 100) {
|
// if (+value + totalPercent !== 100) {
|
||||||
return Promise.reject('总和应为100%');
|
// return Promise.reject('总和应为100%');
|
||||||
}
|
// }
|
||||||
form.setFields(otherWeightCongigName.map((i) => ({ name: i, errors: [] })));
|
// form.setFields(otherWeightCongigName.map((i) => ({ name: i, errors: [] })));
|
||||||
return Promise.resolve('');
|
// return Promise.resolve('');
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
]}
|
// ]}
|
||||||
>
|
// >
|
||||||
<InputNumber
|
// <InputNumber
|
||||||
size="small"
|
// size="small"
|
||||||
min={0}
|
// min={0}
|
||||||
max={100}
|
// max={100}
|
||||||
formatter={(value) => `${value}%`}
|
// formatter={(value) => `${value}%`}
|
||||||
parser={(value: any) => value.replace('%', '')}
|
// parser={(value: any) => value.replace('%', '')}
|
||||||
/>
|
// />
|
||||||
</Form.Item>
|
// </Form.Item>
|
||||||
</>
|
// </>
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: '检查规则',
|
title: '检查规则',
|
||||||
// width: 350,
|
// width: 350,
|
||||||
dataIndex: 'passed',
|
dataIndex: 'passed',
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
render: (text: any, record: any) => {
|
render: (text: any, record: any) => {
|
||||||
const getFormItem = (params: { type?: string; percent?: boolean; attrs?: any; validator?: any }) => {
|
const configItem = record.configItem;
|
||||||
const { validator, percent, type = 'value', attrs = { min: 0 } } = params;
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
name={`${type}_${record.configItemName}`}
|
|
||||||
label=""
|
|
||||||
rules={
|
|
||||||
validator
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
validator: validator,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{percent ? (
|
|
||||||
<InputNumber
|
|
||||||
size="small"
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
style={{ width: 86 }}
|
|
||||||
formatter={(value) => `${value * 100}%`}
|
|
||||||
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (record.configItemName === 'Controller') {
|
switch (configItem) {
|
||||||
return <div className="table-form-item">≠ 1 则不通过</div>;
|
case 'Controller': {
|
||||||
}
|
return <div className="table-form-item">≠ 1 则不通过</div>;
|
||||||
if (record.configItemName === 'RequestQueueSize' || record.configItemName === 'NoLeader') {
|
}
|
||||||
return (
|
case 'BrainSplit': {
|
||||||
<div className="table-form-item">
|
return <div className="table-form-item">脑裂则不通过</div>;
|
||||||
<span className="left-text">≥</span>
|
}
|
||||||
{getFormItem({ attrs: { min: 0, max: 99998 } })}
|
case 'RequestQueueSize':
|
||||||
<span className="right-text">则不通过</span>
|
case 'NoLeader': {
|
||||||
</div>
|
return (
|
||||||
);
|
<div className="table-form-item">
|
||||||
}
|
<span className="left-text">≥</span>
|
||||||
if (record.configItemName === 'NetworkProcessorAvgIdlePercent') {
|
{getFormItem({ configItem, attrs: { min: 0, max: 99998 } })}
|
||||||
return (
|
<span className="right-text">则不通过</span>
|
||||||
<div className="table-form-item">
|
</div>
|
||||||
<span className="left-text">≤</span>
|
);
|
||||||
{getFormItem({ percent: true })}
|
}
|
||||||
<span className="right-text">则不通过</span>
|
case 'SentRate':
|
||||||
</div>
|
case 'WatchCount':
|
||||||
);
|
case 'AliveConnections':
|
||||||
}
|
case 'ApproximateDataSize':
|
||||||
if (record.configItemName === 'UnderReplicaTooLong' || record.configItemName === 'ReBalance') {
|
case 'OutstandingRequests': {
|
||||||
return (
|
return (
|
||||||
<div className="table-form-item">
|
<div className="table-form-item">
|
||||||
{getFormItem({ type: 'latestMinutes', attrs: { min: 1, max: 10080 } })}
|
<span className="left-text">总容量指标</span>
|
||||||
<span className="right-text left-text">周期内,≥</span>
|
{getFormItem({ configItem, type: 'amount' })}
|
||||||
{getFormItem({
|
<span className="left-text">, ≥</span>
|
||||||
type: 'detectedTimes',
|
{getFormItem({ configItem, type: 'ratio', percent: true })}
|
||||||
attrs: { min: 1, max: 10080 },
|
<span className="right-text">则不通过</span>
|
||||||
validator: async (rule: any, value: string) => {
|
</div>
|
||||||
const latestMinutesValue = form.getFieldValue(`latestMinutes_${record.configItemName}`);
|
);
|
||||||
|
}
|
||||||
|
case 'NetworkProcessorAvgIdlePercent': {
|
||||||
|
return (
|
||||||
|
<div className="table-form-item">
|
||||||
|
<span className="left-text">≤</span>
|
||||||
|
{getFormItem({ configItem, percent: true })}
|
||||||
|
<span className="right-text">则不通过</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'UnderReplicaTooLong':
|
||||||
|
case 'Group Re-Balance': {
|
||||||
|
return (
|
||||||
|
<div className="table-form-item">
|
||||||
|
{getFormItem({ type: 'latestMinutes', configItem, attrs: { min: 1, max: 10080 } })}
|
||||||
|
<span className="right-text left-text">周期内,≥</span>
|
||||||
|
{getFormItem({
|
||||||
|
type: 'detectedTimes',
|
||||||
|
configItem,
|
||||||
|
attrs: { min: 1, max: 10080 },
|
||||||
|
validator: async (rule: any, value: string) => {
|
||||||
|
const latestMinutesValue = form.getFieldValue(`latestMinutes_${configItem}`);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject('请输入');
|
return Promise.reject('请输入');
|
||||||
}
|
}
|
||||||
if (+value < 1) {
|
if (+value < 1) {
|
||||||
return Promise.reject('最小为1');
|
return Promise.reject('最小为1');
|
||||||
}
|
}
|
||||||
if (+value > +latestMinutesValue) {
|
if (+value > +latestMinutesValue) {
|
||||||
return Promise.reject('值不能大于周期');
|
return Promise.reject('值不能大于周期');
|
||||||
}
|
}
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
<span className="right-text">则不通过</span>
|
<span className="right-text">则不通过</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <></>;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as any;
|
] as any;
|
||||||
|
|||||||
@@ -195,15 +195,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.healthy-state {
|
.healthy-state {
|
||||||
margin-left: 14px;
|
margin-left: 10px;
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
&-status {
|
&-status {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
margin-bottom: 13px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
@@ -225,7 +224,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dcloud-divider-horizontal {
|
.dcloud-divider-horizontal {
|
||||||
margin: 16px 4px;
|
margin: 0 16px 14px 0;
|
||||||
padding: 0px 20px;
|
padding: 0px 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||