mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-12 11:02:37 +08:00
kafka-manager 2.0
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import * as React from 'react';
|
||||
import { ILabelValue, IBrokersBasicInfo, IOptionType, IClusterReal } from 'types/base-type';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment';
|
||||
import Url from 'lib/url-parser';
|
||||
import { admin } from 'store/admin';
|
||||
import { PageHeader, Descriptions, Spin } from 'component/antd';
|
||||
import { selectBrokerMap } from 'constants/status-map';
|
||||
import { StatusGraghCom } from 'component/flow-table';
|
||||
import { NetWorkFlow, renderTrafficTable } from 'container/network-flow';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class BaseInfo extends React.Component {
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
}
|
||||
|
||||
public updateRealStatus = () => {
|
||||
admin.getBrokersMetrics(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public onSelectChange(e: IOptionType) {
|
||||
return admin.changeBrokerType(e);
|
||||
}
|
||||
|
||||
public getOptionApi = () => {
|
||||
return admin.getBrokersMetricsHistory(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getBrokersBasicInfo(this.clusterId, this.brokerId);
|
||||
admin.getBrokersMetrics(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public renderBrokerContent() {
|
||||
let content = {} as IBrokersBasicInfo;
|
||||
content = admin.brokersBasicInfo ? admin.brokersBasicInfo : content;
|
||||
const brokerContent = [{
|
||||
value: content.host,
|
||||
label: '主机名',
|
||||
}, {
|
||||
value: content.port,
|
||||
label: '服务端口',
|
||||
}, {
|
||||
value: content.jmxPort,
|
||||
label: 'JMX端口',
|
||||
}, {
|
||||
value: content.topicNum,
|
||||
label: 'Topic数',
|
||||
}, {
|
||||
value: content.leaderCount,
|
||||
label: 'Leader分区数',
|
||||
}, {
|
||||
value: content.partitionCount,
|
||||
label: '分区数',
|
||||
}, {
|
||||
value: moment(content.startTime).format(timeFormat),
|
||||
label: '启动时间',
|
||||
}];
|
||||
return (
|
||||
<>
|
||||
<div className="chart-title">基本信息</div>
|
||||
<PageHeader className="detail" title="">
|
||||
<Descriptions size="small" column={3}>
|
||||
{brokerContent.map((item: ILabelValue, index: number) => (
|
||||
<Descriptions.Item key={index} label={item.label}>{item.value}</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderHistoryTraffic() {
|
||||
return (
|
||||
<NetWorkFlow
|
||||
key="1"
|
||||
selectArr={selectBrokerMap}
|
||||
type={admin.type}
|
||||
selectChange={(value: IOptionType) => this.onSelectChange(value)}
|
||||
getApi={() => this.getOptionApi()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderTrafficInfo = () => {
|
||||
return (
|
||||
<Spin spinning={admin.realBrokerLoading}>
|
||||
{renderTrafficTable(this.updateRealStatus, StatusGragh)}
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
{this.renderBrokerContent()}
|
||||
{this.renderTrafficInfo()}
|
||||
{this.renderHistoryTraffic()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class StatusGragh extends StatusGraghCom<IClusterReal> {
|
||||
public getData = () => {
|
||||
return admin.brokersMetrics;
|
||||
}
|
||||
|
||||
public getLoading = () => {
|
||||
return admin.realBrokerLoading;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Table, Tooltip } from 'component/antd';
|
||||
import { diskDefault } from 'constants/status-map';
|
||||
import Url from 'lib/url-parser';
|
||||
import { pagination } from 'constants/table';
|
||||
import { admin } from 'store/admin';
|
||||
import { IPartitionsLocation } from 'types/base-type';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import './index.less';
|
||||
|
||||
@observer
|
||||
export class DiskInfo extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
filterStatusVisible: false,
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
}
|
||||
|
||||
public getDescription = (value: any, record: any) => {
|
||||
return Object.keys(value).map((key: keyof any, index: any) => {
|
||||
return (
|
||||
<>
|
||||
<p key={index}>
|
||||
<span>{value[key]}</span>
|
||||
{(record[key] as []).join(',')}(共{(record[key] as []).length}个)
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public getMoreDetail = (record: IPartitionsLocation) => {
|
||||
return (
|
||||
<div className="p-description" key={record.key}>
|
||||
<p><span>diskName: </span>{record.diskName}</p>
|
||||
<p><span>brokerId: </span>{record.brokerId}</p>
|
||||
<p><span>isUnderReplicated:</span>{record.underReplicated + ''}</p>
|
||||
<p><span>topic: </span>{record.topicName}</p>
|
||||
{this.getDescription(diskDefault, record)}
|
||||
<p><span>clusterId: </span>{record.clusterId}</p>
|
||||
<p><span>underReplicatedPartitions: </span></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getPartitionsLocation(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public getData<T extends IPartitionsLocation>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IPartitionsLocation) =>
|
||||
(item.diskName !== undefined && item.diskName !== null) && item.diskName.toLowerCase().includes(searchKey as string)
|
||||
|| (item.topicName !== undefined && item.topicName !== null) && item.topicName.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderDiskInfo() {
|
||||
const underReplicated = Object.assign({
|
||||
title: '状态',
|
||||
dataIndex: 'underReplicated',
|
||||
key: 'underReplicated',
|
||||
filters: [{ text: '已同步', value: 'false' }, { text: '未同步', value: 'true' }],
|
||||
onFilter: (value: string, record: IPartitionsLocation) => record.underReplicated + '' === value,
|
||||
render: (t: boolean) => <span>{t ? '未同步' : '已同步'}</span>,
|
||||
}, this.renderColumnsFilter('filterStatusVisible'));
|
||||
const columns = [{
|
||||
title: '磁盘名称',
|
||||
dataIndex: 'diskName',
|
||||
key: 'diskName',
|
||||
sorter: (a: IPartitionsLocation, b: IPartitionsLocation) => a.diskName.charCodeAt(0) - b.diskName.charCodeAt(0),
|
||||
render: (val: string) => <Tooltip placement="bottomLeft" title={val}> {val} </Tooltip>,
|
||||
}, {
|
||||
title: 'Topic名称',
|
||||
dataIndex: 'topicName',
|
||||
key: 'topicName',
|
||||
sorter: (a: IPartitionsLocation, b: IPartitionsLocation) => a.topicName.charCodeAt(0) - b.topicName.charCodeAt(0),
|
||||
render: (val: string) => <Tooltip placement="bottomLeft" title={val}> {val} </Tooltip>,
|
||||
}, {
|
||||
title: 'Leader分区',
|
||||
dataIndex: 'leaderPartitions',
|
||||
key: 'leaderPartitions',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 250,
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}),
|
||||
render: (value: number[]) => {
|
||||
return (
|
||||
<Tooltip placement="bottomLeft" title={value.join('、')}>
|
||||
{value.map(i => <span key={i} className="p-params">{i}</span>)}
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Follow分区',
|
||||
dataIndex: 'followerPartitions',
|
||||
key: 'followerPartitions',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 250,
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}),
|
||||
render: (value: number[]) => {
|
||||
return (
|
||||
<Tooltip placement="bottomLeft" title={value.join('、')}>
|
||||
{value.map(i => <span key={i} className="p-params">{i}</span>)}
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: '未同步副本',
|
||||
dataIndex: 'notUnderReplicatedPartitions',
|
||||
render: (value: number[]) => {
|
||||
return (
|
||||
<Tooltip placement="bottomLeft" title={value.join('、')}>
|
||||
{value.map(i => <span key={i} className="p-params p-params-unFinished">{i}</span>)}
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
underReplicated,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch('', '请输入磁盘名或者Topic名称')}
|
||||
</ul>
|
||||
<Table
|
||||
columns={columns}
|
||||
expandIconColumnIndex={-1}
|
||||
expandedRowRender={this.getMoreDetail}
|
||||
dataSource={this.getData(admin.partitionsLocation)}
|
||||
rowKey="key"
|
||||
pagination={pagination}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
admin.partitionsLocation ? <> {this.renderDiskInfo()} </> : null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
.p-params {
|
||||
display: inline-block;
|
||||
padding: 0px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(217, 217, 217, 1);
|
||||
margin: 0px 8px 8px 0px;
|
||||
&-unFinished {
|
||||
background: rgba(245, 34, 45, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.p-description {
|
||||
margin-left: 20px;
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 180px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
p.k-title {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-family: PingFangSC-Medium;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
padding-left: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right-flow {
|
||||
.k-abs {
|
||||
right: 24px;
|
||||
cursor: pointer;
|
||||
& > i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-flow{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.k-text{
|
||||
width: 420px;
|
||||
line-height: 50px;
|
||||
background: #00000005;
|
||||
}
|
||||
}
|
||||
|
||||
.mb-24 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.k-summary {
|
||||
width: 100%;
|
||||
font-family: PingFangSC-Regular;
|
||||
background: #fff;
|
||||
.k-row-1 {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
border-bottom: solid 1px #e8e8e8;
|
||||
div {
|
||||
flex: 1;
|
||||
line-height: 48px;
|
||||
padding-left: 32px;
|
||||
font-size: 15px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
div + div {
|
||||
border-left: solid 1px #e8e8e8;
|
||||
}
|
||||
}
|
||||
.k-row-2 {
|
||||
width: 100%;
|
||||
padding: 24px 0;
|
||||
border-top: solid 1px #e8e8e8;
|
||||
border-bottom: solid 1px #e8e8e8;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
div {
|
||||
height: 58px;
|
||||
flex: 1;
|
||||
span {
|
||||
display: block;
|
||||
line-height: 22px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
p {
|
||||
line-height: 32px;
|
||||
margin-top: 4px;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
div + div {
|
||||
border-left: solid 2px #e8e8e8;
|
||||
}
|
||||
}
|
||||
.k-row-3 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
div {
|
||||
padding: 9px 0;
|
||||
flex: 1;
|
||||
span {
|
||||
display: block;
|
||||
line-height: 22px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
p {
|
||||
line-height: 22px;
|
||||
margin-top: 1px;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
.long-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
div + div {
|
||||
border-left: solid 2px #e8e8e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
.option-map {
|
||||
min-width: 1200px;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tabs, PageHeader } from 'antd';
|
||||
import { handleTabKey } from 'lib/utils';
|
||||
import { IMetaData } from 'types/base-type';
|
||||
import Url from 'lib/url-parser';
|
||||
import { BaseInfo } from './base-info';
|
||||
import { MonitorInfo } from './monitor-info';
|
||||
import { TopicInfo } from './topic-info';
|
||||
import { DiskInfo } from './disk-info';
|
||||
import { PartitionInfo } from './partition-info';
|
||||
import { TopicAnalysis } from './topic-analysis';
|
||||
import { handlePageBack } from 'lib/utils';
|
||||
import { admin } from 'store/admin';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
@observer
|
||||
export class BrokerDetail extends React.Component {
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getBasicInfo(this.clusterId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
let content = {} as IMetaData;
|
||||
content = admin.basicInfo ? admin.basicInfo : content;
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
className="detail topic-detail-header"
|
||||
onBack={() => handlePageBack(`/admin/cluster-detail?clusterId=${this.clusterId}#3`)}
|
||||
title={`集群列表/${content.clusterName || ''}/${this.brokerId || ''}`}
|
||||
/>
|
||||
<Tabs activeKey={location.hash.substr(1) || '1'} type="card" onChange={handleTabKey}>
|
||||
<TabPane tab="基本信息" key="1">
|
||||
<BaseInfo/>
|
||||
</TabPane>
|
||||
<TabPane tab="监控信息" key="2">
|
||||
<MonitorInfo />
|
||||
</TabPane>
|
||||
<TabPane tab="Topic信息" key="3">
|
||||
<TopicInfo tab={'Topic信息'} />
|
||||
</TabPane>
|
||||
<TabPane tab="磁盘信息" key="4">
|
||||
<DiskInfo tab={'磁盘信息'} />
|
||||
</TabPane>
|
||||
<TabPane tab="partition信息" key="5">
|
||||
<PartitionInfo tab={'partition信息'} />
|
||||
</TabPane>
|
||||
<TabPane tab="Topic分析" key="6">
|
||||
<TopicAnalysis />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import Url from 'lib/url-parser';
|
||||
import { adminMonitor } from 'store/admin-monitor';
|
||||
import moment from 'moment';
|
||||
import './index.less';
|
||||
import { ExpandCard } from 'component/expand-card';
|
||||
import { DataCurveFilter } from '../data-curve';
|
||||
import { allCurves, ICurveType } from '../data-curve/config';
|
||||
import { CommonCurve } from 'container/common-curve';
|
||||
|
||||
@observer
|
||||
export class MonitorInfo extends React.Component {
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
public $chart: any;
|
||||
public chart11: any;
|
||||
|
||||
public state = {
|
||||
startTime: moment().subtract(1, 'hour'),
|
||||
endTime: moment(),
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
adminMonitor.setCurrentBrokerId(this.brokerId);
|
||||
adminMonitor.setCurrentClusterId(this.clusterId);
|
||||
}
|
||||
|
||||
public handleRef(chart: any, index: number) {
|
||||
this.$chart[index] = chart;
|
||||
}
|
||||
|
||||
public getCurves = (curveType: ICurveType) => {
|
||||
return curveType.curves.map(o => {
|
||||
return <CommonCurve key={o.path} options={o} parser={curveType.parser} />;
|
||||
});
|
||||
}
|
||||
|
||||
public renderChart() {
|
||||
return (
|
||||
<div className="curve-wrapper">
|
||||
<DataCurveFilter />
|
||||
{allCurves.map(c => {
|
||||
return <ExpandCard key={c.type} title={c.title} charts={this.getCurves(c)} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
adminMonitor.getBrokersMetricsList(moment().subtract(1, 'hour').format('x'), moment().format('x'));
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
{adminMonitor.brokersMetricsHistory ? this.renderChart() : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Table, Tooltip } from 'component/antd';
|
||||
import { columsDefault } from 'constants/status-map';
|
||||
import Url from 'lib/url-parser';
|
||||
import { pagination } from 'constants/table';
|
||||
import { admin } from 'store/admin';
|
||||
import { IBrokersPartitions } from 'types/base-type';
|
||||
import './index.less';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { getPartitionInfoColumns } from '../config';
|
||||
|
||||
@observer
|
||||
export class PartitionInfo extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
public state = {
|
||||
searchKey: '',
|
||||
filterStatusVisible: false,
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
}
|
||||
|
||||
public getColumns = () => {
|
||||
const columns = getPartitionInfoColumns();
|
||||
const status = Object.assign({
|
||||
title: '状态',
|
||||
dataIndex: 'underReplicated',
|
||||
key: 'underReplicated',
|
||||
onCell: null,
|
||||
width: '7%',
|
||||
filters: [{ text: '已同步', value: true }, { text: '未同步', value: false }],
|
||||
onFilter: (value: string, record: IBrokersPartitions) => record.underReplicated === Boolean(value),
|
||||
render: (value: string) => <span>{Boolean(value) ? '已同步' : '未同步'}</span>,
|
||||
}, this.renderColumnsFilter('filterStatusVisible'));
|
||||
|
||||
const col = columns.splice(4, 0, status);
|
||||
return columns;
|
||||
}
|
||||
|
||||
public getDescription = (value: any, record: any) => {
|
||||
return Object.keys(value).map((key: keyof any, index: number) => {
|
||||
return (
|
||||
<>
|
||||
<p key={index}>
|
||||
<span>{value[key]}</span>
|
||||
{(record[key] as []).join(',')}(共{(record[key] as []).length}个)
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public getMoreDetail = (record: IBrokersPartitions) => {
|
||||
return (
|
||||
<div className="p-description">
|
||||
<p><span>Topic: </span>{record.topicName}</p>
|
||||
<p><span>isUnderReplicated:</span>{record.underReplicated ? '已同步' : '未同步'}</p>
|
||||
{this.getDescription(columsDefault, record)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public getData<T extends IBrokersPartitions>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IBrokersPartitions) =>
|
||||
(item.topicName !== undefined && item.topicName !== null) && item.topicName.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getBrokersPartitions(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch('', '请输入Topic')}
|
||||
</ul>
|
||||
<Table
|
||||
loading={admin.realBrokerLoading}
|
||||
columns={this.getColumns()}
|
||||
expandIconAsCell={true}
|
||||
expandIconColumnIndex={-1}
|
||||
expandedRowRender={this.getMoreDetail}
|
||||
dataSource={this.getData(admin.brokersPartitions)}
|
||||
rowKey="key"
|
||||
pagination={pagination}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import * as React from 'react';
|
||||
import { Table, Tooltip } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { admin } from 'store/admin';
|
||||
import { brokerMetrics } from 'constants/status-map';
|
||||
import { IBrokerHistory, IAnalysisTopicVO } from 'types/base-type';
|
||||
import Url from 'lib/url-parser';
|
||||
import './index.less';
|
||||
|
||||
const columns = [{
|
||||
title: 'Topic名称',
|
||||
dataIndex: 'topicName',
|
||||
key: 'topicName',
|
||||
sorter: (a: IAnalysisTopicVO, b: IAnalysisTopicVO) => a.topicName.charCodeAt(0) - b.topicName.charCodeAt(0),
|
||||
render: (val: string) => <Tooltip placement="bottomLeft" title={val}> {val} </Tooltip>,
|
||||
},
|
||||
{
|
||||
title: 'Bytes In(KB/s)',
|
||||
dataIndex: 'bytesInRate',
|
||||
key: 'bytesInRate',
|
||||
sorter: (a: IAnalysisTopicVO, b: IAnalysisTopicVO) => Number(b.bytesIn) - Number(a.bytesIn),
|
||||
render: (t: number, record: any) => `${record && (record.bytesIn / 1024).toFixed(2)} (${+Math.ceil((t * 100))}%)`,
|
||||
},
|
||||
{
|
||||
title: 'Bytes Out(KB/s)',
|
||||
dataIndex: 'bytesOutRate',
|
||||
key: 'bytesOutRate',
|
||||
sorter: (a: IAnalysisTopicVO, b: IAnalysisTopicVO) => Number(b.bytesOut) - Number(a.bytesOut),
|
||||
render: (t: number, record: any) => `${record && (record.bytesOut / 1024).toFixed(2)} (${+Math.ceil((t * 100))}%)`,
|
||||
},
|
||||
{
|
||||
title: 'Message In(秒)',
|
||||
dataIndex: 'messagesInRate',
|
||||
key: 'messagesInRate',
|
||||
sorter: (a: IAnalysisTopicVO, b: IAnalysisTopicVO) => Number(b.messagesIn) - Number(a.messagesIn),
|
||||
render: (t: number, record: any) => `${record && record.messagesIn} (${+Math.ceil((t * 100))}%)`,
|
||||
},
|
||||
{
|
||||
title: 'Total Fetch Requests(秒)',
|
||||
dataIndex: 'totalFetchRequestsRate',
|
||||
key: 'totalFetchRequestsRate',
|
||||
sorter: (a: IAnalysisTopicVO, b: IAnalysisTopicVO) => Number(b.totalFetchRequests) - Number(a.totalFetchRequests),
|
||||
render: (t: number, record: any) => `${record && record.totalFetchRequests} (${+Math.ceil((t * 100))}%)`,
|
||||
},
|
||||
{
|
||||
title: 'Total Produce Requests(秒)',
|
||||
dataIndex: 'totalProduceRequestsRate',
|
||||
key: 'totalProduceRequestsRate',
|
||||
sorter: (a: IAnalysisTopicVO, b: IAnalysisTopicVO) => Number(b.totalProduceRequests) - Number(a.totalProduceRequests),
|
||||
render: (t: number, record: any) => `${record && record.totalProduceRequests} (${+Math.ceil((t * 100))}%)`,
|
||||
}];
|
||||
|
||||
@observer
|
||||
export class TopicAnalysis extends React.Component {
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
}
|
||||
|
||||
public brokerStatus() {
|
||||
return (
|
||||
<div className="k-summary">
|
||||
<div className="k-row-3">
|
||||
<div>
|
||||
<span>Broker ID</span>
|
||||
<p>{this.brokerId}</p>
|
||||
</div>
|
||||
{admin.brokersAnalysis ?
|
||||
Object.keys(brokerMetrics).map((i: keyof IBrokerHistory) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<span className={brokerMetrics[i].length > 25 ? 'long-text' : ''}>
|
||||
{brokerMetrics[i]}</span>
|
||||
<p>{(admin.brokersAnalysis[i] === null || admin.brokersAnalysis[i] === undefined) ?
|
||||
'' : admin.brokersAnalysis[i].toFixed(2)}</p>
|
||||
</div>
|
||||
);
|
||||
}) : ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getBrokersAnalysis(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
let analysisTopic = [] as IAnalysisTopicVO[];
|
||||
analysisTopic = admin.brokersAnalysisTopic ? admin.brokersAnalysisTopic : analysisTopic;
|
||||
return(
|
||||
<>
|
||||
<div className="k-row right-flow mb-24">
|
||||
<p className="k-title">Broker 状态</p>
|
||||
{this.brokerStatus()}
|
||||
</div>
|
||||
<div className="k-row right-flow">
|
||||
<div className="title-flow">
|
||||
<p className="k-title">Topic 状态</p>
|
||||
<span className="didi-theme k-text">说明:数值后的百分比表示“占Broker总量的百分比”</span>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="key"
|
||||
columns={columns}
|
||||
dataSource={analysisTopic}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import * as React from 'react';
|
||||
import moment from 'moment';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pagination, cellStyle } from 'constants/table';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { IBrokersTopics } from 'types/base-type';
|
||||
import Url from 'lib/url-parser';
|
||||
import { Table, Tooltip } from 'component/antd';
|
||||
import { admin } from 'store/admin';
|
||||
import { region } from 'store/region';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class TopicInfo extends SearchAndFilterContainer {
|
||||
public state = {
|
||||
searchKey: '',
|
||||
};
|
||||
|
||||
public clusterId: number;
|
||||
public brokerId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
this.brokerId = Number(url.search.brokerId);
|
||||
}
|
||||
|
||||
public getData<T extends IBrokersTopics>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IBrokersTopics) =>
|
||||
(item.appName !== undefined && item.appName !== null) && item.appName.toLowerCase().includes(searchKey as string)
|
||||
|| (item.topicName !== undefined && item.topicName !== null) && item.topicName.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderTopicInfo() {
|
||||
const cloumns = [{
|
||||
title: 'Topic名称',
|
||||
key: 'topicName',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 250,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
sorter: (a: IBrokersTopics, b: IBrokersTopics) => a.topicName.charCodeAt(0) - b.topicName.charCodeAt(0),
|
||||
render: (t: string, r: IBrokersTopics) => {
|
||||
return (
|
||||
<Tooltip placement="bottomLeft" title={r.topicName} >
|
||||
<a
|
||||
// tslint:disable-next-line:max-line-length
|
||||
href={`${this.urlPrefix}/topic/topic-detail?clusterId=${this.clusterId}&topic=${r.topicName || ''}&isPhysicalClusterId=true®ion=${region.currentRegion}`}
|
||||
>
|
||||
{r.topicName}
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: '分区数',
|
||||
dataIndex: 'partitionNum',
|
||||
key: 'partitionNum',
|
||||
sorter: (a: IBrokersTopics, b: IBrokersTopics) => b.partitionNum - a.partitionNum,
|
||||
}, {
|
||||
title: '副本数',
|
||||
dataIndex: 'replicaNum',
|
||||
key: 'replicaNum',
|
||||
sorter: (a: IBrokersTopics, b: IBrokersTopics) => b.replicaNum - a.replicaNum,
|
||||
}, {
|
||||
title: 'Bytes In(KB/s)',
|
||||
dataIndex: 'byteIn',
|
||||
key: 'byteIn',
|
||||
sorter: (a: IBrokersTopics, b: IBrokersTopics) => b.byteIn - a.byteIn,
|
||||
render: (t: number) => t === null ? '' : (t / 1024).toFixed(2),
|
||||
}, {
|
||||
title: 'QPS',
|
||||
dataIndex: 'produceRequest',
|
||||
key: 'produceRequest',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokersTopics, b: IBrokersTopics) => b.produceRequest - a.produceRequest,
|
||||
render: (t: number) => t === null ? '' : t.toFixed(2),
|
||||
}, {
|
||||
title: '所属应用',
|
||||
dataIndex: 'appName',
|
||||
key: 'appName',
|
||||
width: '10%',
|
||||
render: (val: string, record: IBrokersTopics) => (
|
||||
<Tooltip placement="bottomLeft" title={record.appId} >
|
||||
{val}
|
||||
</Tooltip>
|
||||
),
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
sorter: (a: IBrokersTopics, b: IBrokersTopics) => b.updateTime - a.updateTime,
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
}];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch('', '请输入Topic名称或者负责人')}
|
||||
</ul>
|
||||
<Table columns={cloumns} dataSource={this.getData(admin.brokersTopics)} rowKey="key" pagination={pagination} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getBrokersTopics(this.clusterId, this.brokerId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
admin.brokersTopics ? <> {this.renderTopicInfo()} </> : null
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user