feat: 新增Mirror Maker 2.0(MM2)

This commit is contained in:
wyb
2023-02-10 16:39:47 +08:00
committed by lucasun
parent f03460f3cd
commit fa2abadc25
19 changed files with 3096 additions and 32 deletions

View File

@@ -0,0 +1,119 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import CardBar, { healthDataProps } from './index';
import { Tooltip, Utils } from 'knowdesign';
import api from '@src/api';
import { HealthStateEnum } from '../HealthState';
import { InfoCircleOutlined } from '@ant-design/icons';
interface MM2State {
workerCount: number;
aliveConnectorCount: number;
aliveTaskCount: number;
healthCheckPassed: number;
healthCheckTotal: number;
healthState: number;
totalConnectorCount: string;
totalTaskCount: number;
totalServerCount: number;
mirrorMakerCount: number;
}
const getVal = (val: string | number | undefined | null) => {
return val === undefined || val === null || val === '' ? '0' : val;
};
const ConnectCard = ({ state }: { state?: boolean }) => {
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.getMetricPointsLatest(Number(clusterId)), [
'HealthCheckPassed_MirrorMaker',
'HealthCheckTotal_MirrorMaker',
'HealthState_MirrorMaker',
]).then((data: any) => {
setHealthData({
state: data?.metrics?.['HealthState_MirrorMaker'],
passed: data?.metrics?.['HealthCheckPassed_MirrorMaker'] || 0,
total: data?.metrics?.['HealthCheckTotal_MirrorMaker'] || 0,
});
});
};
const getCardInfo = () => {
return Utils.request(api.getMirrorMakerState(clusterId)).then((res: MM2State) => {
const { mirrorMakerCount, aliveConnectorCount, aliveTaskCount, totalConnectorCount, totalTaskCount, workerCount } = res || {};
const cardMap = [
{
title: 'MM2s',
value: getVal(mirrorMakerCount),
customStyle: {
// 自定义cardbar样式
marginLeft: 0,
},
},
{
title: 'Workers',
value: getVal(workerCount),
},
{
title() {
return (
<div>
<span style={{ display: 'inline-block', marginRight: '8px' }}>Connectors</span>
<Tooltip overlayClassName="rebalance-tooltip" title="conector运行数/总数">
<InfoCircleOutlined />
</Tooltip>
</div>
);
},
value() {
return (
<span>
{getVal(aliveConnectorCount)}/{getVal(totalConnectorCount)}
</span>
);
},
},
{
title() {
return (
<div>
<span style={{ display: 'inline-block', marginRight: '8px' }}>Tasks</span>
<Tooltip overlayClassName="rebalance-tooltip" title="Task运行数/总数">
<InfoCircleOutlined />
</Tooltip>
</div>
);
},
value() {
return (
<span>
{getVal(aliveTaskCount)}/{getVal(totalTaskCount)}
</span>
);
},
},
];
setCardData(cardMap);
});
};
useEffect(() => {
setLoading(true);
Promise.all([getHealthData(), getCardInfo()]).finally(() => {
setLoading(false);
});
}, [clusterId, state]);
return <CardBar scene="mm2" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
};
export default ConnectCard;

View File

@@ -0,0 +1,145 @@
/* eslint-disable react/display-name */
import React, { useState, useEffect } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import CardBar from '@src/components/CardBar';
import { healthDataProps } from '.';
import { Tooltip, Utils } from 'knowdesign';
import Api from '@src/api';
import { hashDataParse } from '@src/constants/common';
import { HealthStateEnum } from '../HealthState';
import { InfoCircleOutlined } from '@ant-design/icons';
import { stateEnum } from '@src/pages/Connect/config';
const getVal = (val: string | number | undefined | null) => {
return val === undefined || val === null || val === '' ? '0' : val;
};
const ConnectDetailCard = (props: { record: any; tabSelectType: string }) => {
const { record, tabSelectType } = props;
const urlParams = useParams<{ clusterId: string; brokerId: string }>();
const urlLocation = useLocation<any>();
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
passed: 0,
total: 0,
});
const getHealthData = (tabSelectTypeName: string) => {
return Utils.post(Api.getMirrorMakerMetricPoints(tabSelectTypeName, record?.connectClusterId), [
'HealthState',
'HealthCheckPassed',
'HealthCheckTotal',
]).then((data: any) => {
setHealthData({
state: data?.metrics?.['HealthState'],
passed: data?.metrics?.['HealthCheckPassed'] || 0,
total: data?.metrics?.['HealthCheckTotal'] || 0,
});
});
};
const getCardInfo = (tabSelectTypeName: string) => {
return Utils.request(Api.getConnectDetailState(tabSelectTypeName, record?.connectClusterId)).then((res: any) => {
const { type, aliveTaskCount, state, totalTaskCount, totalWorkerCount } = res || {};
const cordRightMap = [
{
title: 'Status',
// value: Utils.firstCharUppercase(state) || '-',
value: () => {
return (
<>
{
<span style={{ fontFamily: 'HelveticaNeue-Medium', fontSize: 32, color: stateEnum[state].color }}>
{Utils.firstCharUppercase(state) || '-'}
</span>
}
</>
);
},
},
{
title() {
return (
<div>
<span style={{ display: 'inline-block', marginRight: '8px' }}>Tasks</span>
<Tooltip overlayClassName="rebalance-tooltip" title="Task运行数/总数">
<InfoCircleOutlined />
</Tooltip>
</div>
);
},
value() {
return (
<span>
{getVal(aliveTaskCount)}/{getVal(totalTaskCount)}
</span>
);
},
},
{
title: 'Workers',
value: getVal(totalWorkerCount),
},
];
setCardData(cordRightMap);
});
};
const noDataCardInfo = () => {
const cordRightMap = [
{
title: 'Status',
// value: Utils.firstCharUppercase(state) || '-',
value() {
return <span>-</span>;
},
},
{
title() {
return (
<div>
<span style={{ display: 'inline-block', marginRight: '8px' }}>Tasks</span>
<Tooltip overlayClassName="rebalance-tooltip" title="Task运行数/总数">
<InfoCircleOutlined />
</Tooltip>
</div>
);
},
value() {
return <span>-/-</span>;
},
},
{
title: 'Workers',
value() {
return <span>-</span>;
},
},
];
setCardData(cordRightMap);
};
useEffect(() => {
setLoading(true);
const filterCardInfo =
tabSelectType === 'MirrorCheckpoint' && record.checkpointConnector
? getCardInfo(record.checkpointConnector)
: tabSelectType === 'MirrorHeatbeat' && record.heartbeatConnector
? getCardInfo(record.heartbeatConnector)
: tabSelectType === 'MirrorSource' && record.connectorName
? getCardInfo(record.connectorName)
: noDataCardInfo();
Promise.all([getHealthData(record.connectorName), filterCardInfo]).finally(() => {
setLoading(false);
});
}, [record, tabSelectType]);
return (
<CardBar record={record} scene="mm2" healthData={healthData} cardColumns={cardData} showCardBg={false} loading={loading}></CardBar>
);
};
export default ConnectDetailCard;

View File

@@ -18,7 +18,7 @@ export interface CardBarProps {
cardColumns?: any[];
healthData?: healthDataProps;
showCardBg?: boolean;
scene: 'topics' | 'brokers' | 'topic' | 'broker' | 'group' | 'zookeeper' | 'connect' | 'connector';
scene: 'topics' | 'brokers' | 'topic' | 'broker' | 'group' | 'zookeeper' | 'connect' | 'connector' | 'mm2';
record?: any;
loading?: boolean;
needProgress?: boolean;
@@ -67,6 +67,11 @@ const sceneCodeMap = {
fieldName: 'connectorName',
alias: 'Connector',
},
mm2: {
code: 7,
fieldName: 'connectorName',
alias: 'MM2',
},
};
const CardColumnsItem: any = (cardItem: any) => {
const { cardColumnsItemData, showCardBg } = cardItem;
@@ -108,7 +113,7 @@ const CardBar = (props: CardBarProps) => {
const sceneObj = sceneCodeMap[scene];
const path = record
? api.getResourceHealthDetail(
scene === 'connector' ? Number(record?.connectClusterId) : Number(routeParams.clusterId),
scene === 'connector' || scene === 'mm2' ? Number(record?.connectClusterId) : Number(routeParams.clusterId),
sceneObj.code,
record[sceneObj.fieldName]
)

View File

@@ -89,7 +89,15 @@ export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) =>
const columns = [
{
title: `${pathname.endsWith('/broker') ? 'Broker' : pathname.endsWith('/topic') ? 'Topic' : 'Cluster'} Metrics`,
title: `${
pathname.endsWith('/broker')
? 'Broker'
: pathname.endsWith('/topic')
? 'Topic'
: pathname.endsWith('/replication')
? 'MM2'
: 'Cluster'
} Metrics`,
dataIndex: 'category',
key: 'category',
},
@@ -112,7 +120,7 @@ export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) =>
desc,
unit: metricDefine?.unit,
};
if (metricDefine.category) {
if (metricDefine?.category) {
if (!categoryData[metricDefine.category]) {
categoryData[metricDefine.category] = [returnData];
} else {
@@ -129,11 +137,11 @@ export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) =>
};
const formateSelectedKeys = () => {
const newKeys = metricSelect.selectedRows;
const newKeys = metricSelect?.selectedRows;
const result: SelectedMetrics = {};
const selectedCategories: string[] = [];
newKeys.forEach((name: string) => {
newKeys?.forEach((name: string) => {
const metricDefine = global.getMetricDefine(metricSelect?.metricType, name);
if (metricDefine) {
if (!result[metricDefine.category]) {

View File

@@ -167,6 +167,9 @@ const ChartDetail = (props: ChartDetailProps) => {
const getMetricChartData = ([startTime, endTime]: readonly [number, number]) => {
const getQueryUrl = () => {
switch (metricType) {
case MetricType.MM2: {
return api.getMirrorMakerMetrics(clusterId);
}
case MetricType.Connect: {
return api.getConnectClusterMetrics(clusterId);
}
@@ -180,13 +183,16 @@ const ChartDetail = (props: ChartDetailProps) => {
[MetricType.Topic]: 'topics',
[MetricType.Connect]: 'connectClusterIdList',
[MetricType.Connectors]: 'connectorNameList',
[MetricType.MM2]: 'connectorNameList',
};
return Utils.post(getQueryUrl(), {
startTime,
endTime,
metricsNames: [metricName],
topNu: null,
[queryMap[metricType as keyof typeof queryMap]]: queryLines,
[queryMap[metricType as keyof typeof queryMap]]:
metricType === MetricType.MM2 ? queryLines.map((item) => (typeof item === 'string' ? Utils.parseJSON(item) : item)) : queryLines,
});
};

View File

@@ -5,6 +5,7 @@ const METRIC_DASHBOARD_REQ_MAP = {
[MetricType.Broker]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Broker),
[MetricType.Topic]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Topic),
[MetricType.Zookeeper]: (clusterId: string) => api.getZookeeperMetrics(clusterId),
[MetricType.MM2]: (clusterId: string) => api.getMirrorMakerMetrics(clusterId),
};
export const getMetricDashboardReq = (clusterId: string, type: MetricType.Broker | MetricType.Topic | MetricType.Zookeeper) =>

View File

@@ -150,18 +150,35 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
// 获取节点范围列表
const getScopeList = async () => {
const res: any = await Utils.request(api.getDashboardMetadata(clusterId, dashboardType));
const list = res.map((item: any) => {
return dashboardType === MetricType.Broker
? {
label: item.host,
value: item.brokerId,
}
: {
label: item.topicName,
value: item.topicName,
};
});
const res: any = await Utils.request(
dashboardType !== MetricType.MM2 ? api.getDashboardMetadata(clusterId, dashboardType) : api.getMirrorMakerMetadata(clusterId)
);
const mockRes = [{ connectClusterId: 1, connectClusterName: 'connectClusterName', connectorName: 'connectorName' }];
const list =
res.length > 0
? res.map((item: any) => {
return dashboardType === MetricType.Broker
? {
label: item.host,
value: item.brokerId,
}
: dashboardType === MetricType.MM2
? {
label: item.connectorName,
value: JSON.stringify({ connectClusterId: item.connectClusterId, connectorName: item.connectorName }),
}
: {
label: item.topicName,
value: item.topicName,
};
})
: mockRes.map((item) => {
return {
label: item.connectorName,
value: JSON.stringify(item),
};
});
setScopeList(list);
};
@@ -172,19 +189,23 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
const [startTime, endTime] = curHeaderOptions.rangeTime;
const curTimestamp = Date.now();
curFetchingTimestamp.current = curTimestamp;
const reqBody = Object.assign(
{
startTime,
endTime,
metricsNames: metricList || [],
},
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic || dashboardType === MetricType.MM2
? {
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
[dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop
? null
: curHeaderOptions.scopeData.data,
[dashboardType === MetricType.Broker ? 'brokerIds' : dashboardType === MetricType.MM2 ? 'connectorNameList' : 'topics']:
curHeaderOptions?.scopeData?.isTop
? null
: dashboardType === MetricType.MM2
? curHeaderOptions.scopeData.data?.map((item: any) => {
return JSON.parse(item);
})
: curHeaderOptions.scopeData.data,
}
: {}
);
@@ -207,10 +228,31 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
dashboardType,
curHeaderOptions.rangeTime
) as FormattedMetricData[];
// todo 将指标筛选选中但是没有返回的指标插入chartData中
const nullformattedMetricData: any = [];
metricList?.forEach((item) => {
if (formattedMetricData && formattedMetricData.some((key) => item === key.metricName)) {
nullformattedMetricData.push(null);
} else {
const chartData: any = {
metricName: item,
metricType: dashboardType,
metricUnit: global.getMetricDefine(dashboardType, item)?.unit || '',
metricLines: [],
showLegend: false,
targetUnit: undefined,
};
nullformattedMetricData.push(chartData);
}
});
// 指标排序
formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName));
setMetricChartData(formattedMetricData);
const filterNullformattedMetricData = nullformattedMetricData.filter((item: any) => item !== null);
filterNullformattedMetricData.sort(
(a: any, b: any) => metricRankList.current.indexOf(a?.metricName) - metricRankList.current.indexOf(b?.metricName)
);
setMetricChartData([...formattedMetricData, ...filterNullformattedMetricData]);
}
setLoading(false);
},
@@ -255,12 +297,15 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
useEffect(() => {
if (metricList?.length && curHeaderOptions) {
getMetricChartData();
} else {
setMetricChartData([]);
setLoading(false);
}
}, [curHeaderOptions, metricList]);
useEffect(() => {
// 初始化页面,获取 scope 和 metric 信息
(dashboardType === MetricType.Broker || dashboardType === MetricType.Topic) && getScopeList();
(dashboardType === MetricType.Broker || dashboardType === MetricType.Topic || dashboardType === MetricType.MM2) && getScopeList();
}, []);
return (
@@ -270,11 +315,24 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
hideNodeScope={dashboardType === MetricType.Zookeeper}
openMetricFilter={() => metricFilterRef.current?.open()}
nodeSelect={{
name: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper',
name:
dashboardType === MetricType.Broker
? 'Broker'
: dashboardType === MetricType.Topic
? 'Topic'
: dashboardType === MetricType.MM2
? 'MM2'
: 'Zookeeper',
customContent: (
<SelectContent
title={`自定义 ${
dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper'
dashboardType === MetricType.Broker
? 'Broker'
: dashboardType === MetricType.Topic
? 'Topic'
: dashboardType === MetricType.MM2
? 'MM2'
: 'Zookeeper'
} 范围`}
list={scopeList}
/>