mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-03 02:52:08 +08:00
feat: 新增Mirror Maker 2.0(MM2)
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user