import React, { useCallback } from 'react'; import { useEffect, useState } from 'react'; import { AppContainer, Button, Empty, IconFont, List, Popover, ProTable, Radio, Spin, Utils } from 'knowdesign'; import { CloseOutlined } from '@ant-design/icons'; import api, { MetricType } from '@src/api'; import { useParams } from 'react-router-dom'; import TagsWithHide from '@src/components/TagsWithHide'; import SwitchTab from '@src/components/SwitchTab'; import RenderEmpty from '@src/components/RenderEmpty'; interface PropsType { hashData: any; // searchKeywords: string; } interface PartitionsSummary { brokerCount: number; deadBrokerCount: number; liveBrokerCount: number; noLeaderPartitionCount: number; partitionCount: number; underReplicatedPartitionCount: number; } const PARTITION_DETAIL_METRICS = ['LogEndOffset', 'LogStartOffset', 'Messages', 'LogSize'] as const; interface PartitionDetail { partitionId: number; topicName: string; leaderBrokerId: number; assignReplicas: number[]; inSyncReplicas: number[]; latestMetrics: { clusterPhyId: number; metrics: { [K in typeof PARTITION_DETAIL_METRICS[number]]: number; }; timestamp: number; }; } interface BrokersDetail { live: number; dead: number; total: number; partitionCount: number; noLeaderPartitionIdList: number[]; underReplicatedPartitionIdList: number[]; brokerPartitionStateList: brokerPartitionState[]; } interface brokerPartitionState { alive: boolean; brokerId: number; bytesInOneMinuteRate: number; bytesOutOneMinuteRate: number; host: string; replicaList: brokerReplica[]; } interface brokerReplica { leaderBrokerId: number; partitionId: number; topicName: string; isLeaderReplace: boolean; inSync: boolean; } function getTranformedBytes(bytes: number) { const unit = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; let isUp = true, outBytes = bytes; if (typeof outBytes !== 'number') { outBytes = Number(outBytes); if (isNaN(outBytes)) return [outBytes, unit[i]]; } while (isUp) { if (outBytes / 1024 >= 1) { outBytes /= 1024; i++; } else { isUp = false; } } return [outBytes.toFixed(2), unit[i]]; } const PartitionPopoverContent = (props: { clusterId: string; hashData: any; record: brokerReplica; brokerId: brokerPartitionState['brokerId']; close: () => void; }) => { const [global] = AppContainer.useGlobalValue(); const { clusterId, hashData, record, brokerId, close } = props; const [loading, setLoading] = useState(true); const [partitionInfo, setPartitionInfo] = useState<{ label: string; value: string | number }[]>([]); // 获取单个 Partition 详情 const getDetail = () => { const { partitionId, leaderBrokerId, inSync } = record; Utils.request(api.getPartitionMetricInfo(clusterId, hashData.topicName, brokerId, partitionId), { method: 'POST', data: PARTITION_DETAIL_METRICS, }).then((res: any) => { const type = MetricType.Replication; const metricsData = res?.metrics || {}; const partitionInfo = [ { label: 'LeaderBroker', value: leaderBrokerId }, { label: 'BeginningOffset', value: `${metricsData.LogStartOffset === undefined ? '-' : metricsData.LogStartOffset} ${ global.getMetricDefine(type, 'LogStartOffset')?.unit || '' }`, }, { label: 'EndOffset', value: `${metricsData.LogEndOffset === undefined ? '-' : metricsData.LogEndOffset} ${ global.getMetricDefine(type, 'LogEndOffset')?.unit || '' }`, }, { label: 'MsgNum', value: `${metricsData.Messages === undefined ? '-' : metricsData.Messages} ${ global.getMetricDefine(type, 'Messages')?.unit || '' }`, }, { label: 'LogSize', value: `${metricsData.LogSize === undefined ? '-' : Utils.formatAssignSize(metricsData.LogSize, 'MB')} MB`, }, { label: '是否同步', value: inSync ? '是' : '否' }, ]; setPartitionInfo(partitionInfo); setLoading(false); }); }; useEffect(() => { getDetail(); }, []); return (
分区详情
( {item.label} )} />
); }; const PartitionSummary = (props: { clusterId: string; topicName: string }) => { const { clusterId, topicName } = props; const [partitionsSummary, setPartitionsSummary] = useState(null); useEffect(() => { // 获取统计信息 Utils.request(api.getTopicPartitionsSummary(clusterId, topicName)).then((res: PartitionsSummary) => { setPartitionsSummary(res); }); }, []); return ( <>
Brokers 总数 {partitionsSummary?.brokerCount}
live {partitionsSummary?.liveBrokerCount}
down {partitionsSummary?.deadBrokerCount}
Partition 总数 {partitionsSummary?.partitionCount}
No Leader {partitionsSummary?.noLeaderPartitionCount}
URP {partitionsSummary?.underReplicatedPartitionCount}
); }; const PartitionCard = (props: { clusterId: string; hashData: any }) => { const { clusterId, hashData } = props; const [brokersDetail, setBrokersDetail] = useState(); const [hoverPartitionId, setHoverPartitionId] = useState(-1); const [clickPartition, setClickPartition] = useState(''); const [loading, setLoading] = useState(true); const closePartitionDetail = useCallback(() => setClickPartition(''), []); useEffect(() => { Utils.request(api.getTopicBrokersList(clusterId, hashData.topicName)).then( (res: any) => { setBrokersDetail(res); setLoading(false); }, () => setLoading(false) ); }, []); return (
{brokersDetail?.brokerPartitionStateList?.length ? ( brokersDetail?.brokerPartitionStateList.map((partitionState) => { return (
Broker
{partitionState.brokerId}
Host ID
{partitionState.host || '-'}
{['BytesIn', 'BytesOut'].map((type) => { return (
{type}
{getTranformedBytes( type === 'BytesIn' ? partitionState.bytesInOneMinuteRate : partitionState.bytesOutOneMinuteRate ).map((val, i) => { return i ? {val}/s : {val}; })}
); })}
{partitionState.alive ? ( partitionState?.replicaList?.length ? (
{partitionState?.replicaList?.map((partition) => { return (
setHoverPartitionId(partition.partitionId)} onMouseLeave={() => setHoverPartitionId(-1)} onClick={() => setClickPartition(`${partitionState.brokerId}&${partition.partitionId}`)} > !v && closePartitionDetail()} overlayClassName="broker-partition-popover" content={ } destroyTooltipOnHide={true} trigger="click" placement="rightTop" > {partition.partitionId}
); })}
) : ( ) ) : ( )}
); }) ) : loading ? ( <> ) : ( )}
); }; const PartitionTable = (props: { clusterId: string; hashData: any }) => { const { clusterId, hashData } = props; const [loading, setLoading] = useState(true); const [partitionsDetail, setPartitionsDetail] = useState([]); const columns = [ { title: 'Partition ID', dataIndex: 'partitionId', }, { title: 'StartOffset', dataIndex: ['latestMetrics', 'metrics', 'LogStartOffset'], }, { title: 'EndOffset', dataIndex: ['latestMetrics', 'metrics', 'LogEndOffset'], }, { title: 'MsgNum', dataIndex: ['latestMetrics', 'metrics', 'Messages'], }, { title: 'Leader Broker', dataIndex: 'leaderBrokerId', }, { title: 'LogSize(MB)', dataIndex: ['latestMetrics', 'metrics', 'LogSize'], render: (size: number | undefined) => (size === undefined ? '-' : Utils.formatAssignSize(size, 'MB')), }, { title: 'AR', dataIndex: 'assignReplicas', width: 180, render: (arr: PartitionDetail['assignReplicas']) => `共有${len}个`} />, }, { title: 'ISR', dataIndex: 'inSyncReplicas', width: 180, render: (arr: PartitionDetail['inSyncReplicas']) => `共有${len}个`} />, }, ]; useEffect(() => { Utils.request(api.getTopicPartitionsDetail(clusterId, hashData.topicName), { method: 'POST', data: PARTITION_DETAIL_METRICS, }).then((res: PartitionDetail[]) => { setPartitionsDetail(res); setLoading(false); }); }, []); return ( ); }; export default (props: PropsType) => { const { clusterId } = useParams<{ clusterId: string }>(); const { hashData } = props; const [showMode, setShowMode] = useState('card'); return ( <>
{showMode === 'card' && (
Leader
ISR
OSR
)} { setShowMode(key); }} >
{showMode === 'card' ? ( ) : ( )}
); };