mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-10 17:12:11 +08:00
kafka-manager 2.0
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import * as React from 'react';
|
||||
import { Table, notification, Button, Divider, Popconfirm } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pagination } from 'constants/table';
|
||||
import { IBrokerData, IEnumsMap, IMetaData } from 'types/base-type';
|
||||
import { admin } from 'store/admin';
|
||||
import { tableFilter, transBToMB } from 'lib/utils';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { DoughnutChart } from 'component/chart';
|
||||
import { LeaderRebalanceWrapper } from 'container/modal/admin/leader-rebalance';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
import Url from 'lib/url-parser';
|
||||
import moment from 'moment';
|
||||
import './index.less';
|
||||
|
||||
@observer
|
||||
export class ClusterBroker extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
public clusterName: string;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
filterPeakFlowVisible: false,
|
||||
filterReplicatedVisible: false,
|
||||
filterRegionVisible: false,
|
||||
filterStatusVisible: false,
|
||||
reblanceVisible: false,
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public renderBrokerData(clusterBroker: IBrokerData[]) {
|
||||
let peakFlow = [] as IEnumsMap[];
|
||||
peakFlow = admin.peakFlowStatusList ? admin.peakFlowStatusList : peakFlow;
|
||||
|
||||
const peakFlowStatus = Object.assign({
|
||||
title: '峰值状态',
|
||||
dataIndex: 'peakFlowStatus',
|
||||
key: 'peakFlowStatus',
|
||||
width: '8%',
|
||||
filters: peakFlow.map(ele => ({ text: ele.message, value: ele.code + '' })),
|
||||
onFilter: (value: string, record: IBrokerData) => record.peakFlowStatus === +value,
|
||||
render: (value: number) => {
|
||||
let messgae: string;
|
||||
peakFlow.map(ele => {
|
||||
if (ele.code === value) {
|
||||
messgae = ele.message;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<span>{messgae}</span>
|
||||
);
|
||||
},
|
||||
}, this.renderColumnsFilter('filterPeakFlowVisible'));
|
||||
|
||||
const underReplicated = Object.assign({
|
||||
title: '副本状态',
|
||||
dataIndex: 'underReplicated',
|
||||
key: 'underReplicated',
|
||||
width: '8%',
|
||||
filters: [{ text: '同步', value: 'false' }, { text: '未同步', value: 'true' }],
|
||||
onFilter: (value: string, record: IBrokerData) => record.underReplicated === (value === 'true') ? true : false,
|
||||
render: (t: boolean) => t !== null ? <span className={t ? 'fail' : 'success'}>{t ? '未同步' : '同步'}</span> : '',
|
||||
}, this.renderColumnsFilter('filterReplicatedVisible'));
|
||||
|
||||
const region = Object.assign({
|
||||
title: 'regionName',
|
||||
dataIndex: 'regionName',
|
||||
key: 'regionName',
|
||||
width: '10%',
|
||||
filters: tableFilter<any>(clusterBroker, 'regionName'),
|
||||
onFilter: (value: string, record: IBrokerData) => record.regionName === value,
|
||||
}, this.renderColumnsFilter('filterRegionVisible'));
|
||||
|
||||
const status = Object.assign({
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: '8%',
|
||||
filters: [{ text: '未使用', value: '-1' }, { text: '使用中', value: '0' }],
|
||||
onFilter: (value: string, record: IBrokerData) => record.status === Number(value),
|
||||
render: (t: number) => <span className={t === 0 ? 'success' : 'fail'}>{t === 0 ? '使用中' : '未使用'}</span>,
|
||||
}, this.renderColumnsFilter('filterStatusVisible'));
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'brokerId',
|
||||
key: 'brokerId',
|
||||
width: '5%',
|
||||
sorter: (a: IBrokerData, b: IBrokerData) => b.brokerId - a.brokerId,
|
||||
render: (text: number, record: IBrokerData) => {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
const query = `clusterId=${this.clusterId}&brokerId=${record.brokerId}`;
|
||||
const judge = record.underReplicated === false && record.status !== 0;
|
||||
return (
|
||||
<span className={judge ? 'fail' : ''}>
|
||||
{
|
||||
// tslint:disable-next-line:max-line-length
|
||||
record.status === 0 ? <a href={`${this.urlPrefix}/admin/broker-detail?${query}`}>{text}</a>
|
||||
: <a style={{ cursor: 'not-allowed', color: '#999' }}>{text}</a>}
|
||||
</span>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '主机',
|
||||
dataIndex: 'host',
|
||||
key: 'host',
|
||||
width: '10%',
|
||||
sorter: (a: any, b: any) => a.host.charCodeAt(0) - b.host.charCodeAt(0),
|
||||
},
|
||||
{
|
||||
title: 'Port',
|
||||
dataIndex: 'port',
|
||||
key: 'port',
|
||||
width: '6%',
|
||||
sorter: (a: IBrokerData, b: IBrokerData) => b.port - a.port,
|
||||
},
|
||||
{
|
||||
title: 'JMX Port',
|
||||
dataIndex: 'jmxPort',
|
||||
key: 'jmxPort',
|
||||
width: '7%',
|
||||
sorter: (a: IBrokerData, b: IBrokerData) => b.jmxPort - a.jmxPort,
|
||||
},
|
||||
{
|
||||
title: '启动时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokerData, b: IBrokerData) => b.startTime - a.startTime,
|
||||
render: (time: number) => moment(time).format(timeFormat),
|
||||
},
|
||||
{
|
||||
title: 'Bytes In(MB/s)',
|
||||
dataIndex: 'byteIn',
|
||||
key: 'byteIn',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokerData, b: IBrokerData) => b.byteIn - a.byteIn,
|
||||
render: (t: number) => transBToMB(t),
|
||||
},
|
||||
{
|
||||
title: 'Bytes Out(MB/s)',
|
||||
dataIndex: 'byteOut',
|
||||
key: 'byteOut',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokerData, b: IBrokerData) => b.byteOut - a.byteOut,
|
||||
render: (t: number) => transBToMB(t),
|
||||
},
|
||||
peakFlowStatus,
|
||||
underReplicated,
|
||||
region,
|
||||
status,
|
||||
{
|
||||
title: '操作',
|
||||
width: '10%',
|
||||
render: (text: string, record: IBrokerData) => {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
const query = `clusterId=${this.clusterId}&brokerId=${record.brokerId}`;
|
||||
return ( // 0 监控中 可点击详情,不可删除 -1 暂停监控 不可点击详情,可删除
|
||||
<>
|
||||
{record.status === 0 ?
|
||||
<a href={`${this.urlPrefix}/admin/broker-detail?${query}`} className="action-button">详情</a>
|
||||
: <a style={{ cursor: 'not-allowed', color: '#999' }}>详情</a>}
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
onConfirm={() => this.deteleTopic(record)}
|
||||
disabled={record.status === 0}
|
||||
>
|
||||
<a style={record.status === 0 ? { cursor: 'not-allowed', color: '#999' } : {}}>
|
||||
删除
|
||||
</a>
|
||||
</Popconfirm>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Table dataSource={clusterBroker} columns={columns} pagination={pagination} />
|
||||
);
|
||||
}
|
||||
|
||||
public deteleTopic(record: any) {
|
||||
admin.deteleClusterBrokers(this.clusterId, record.brokerId).then(data => {
|
||||
notification.success({ message: '删除成功' });
|
||||
});
|
||||
}
|
||||
|
||||
public reblanceInfo() {
|
||||
this.setState({ reblanceVisible: true });
|
||||
}
|
||||
|
||||
public handleVisible(val: boolean) {
|
||||
this.setState({ reblanceVisible: val });
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
await admin.getPeakFlowStatus();
|
||||
admin.getClusterBroker(this.clusterId);
|
||||
admin.getBrokersMetadata(this.clusterId);
|
||||
admin.getBrokersStatus(this.clusterId);
|
||||
}
|
||||
|
||||
public getData<T extends IBrokerData>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IBrokerData) =>
|
||||
(item.brokerId !== undefined && item.brokerId !== null) && (item.brokerId + '').toLowerCase().includes(searchKey as string)
|
||||
|| (item.host !== undefined && item.host !== null) && item.host.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const content = this.props.basicInfo as IMetaData;
|
||||
if (content) {
|
||||
this.clusterName = content.clusterName;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="diagram">
|
||||
<div className="diagram-box">
|
||||
<h2>峰值使用率分布图</h2>
|
||||
<Divider className="hotspot-divider" />
|
||||
{admin.peakValueList.length ? <DoughnutChart
|
||||
getChartData={() => admin.getPeakFlowChartData(admin.peakValueList, admin.peakValueMap)}
|
||||
/> : null}
|
||||
</div>
|
||||
<div className="diagram-box">
|
||||
<h2>副本状态图</h2>
|
||||
<Divider className="hotspot-divider" />
|
||||
{admin.copyValueList.length ? <DoughnutChart
|
||||
getChartData={() => admin.getSideStatusChartData(admin.copyValueList)}
|
||||
/> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="leader-seacrh">
|
||||
<div className="search-top">
|
||||
{this.renderSearch('', '请输入ID或主机')}
|
||||
<Button onClick={() => this.reblanceInfo()} type="primary">Leader Rebalance</Button>
|
||||
</div>
|
||||
{this.renderBrokerData(this.getData(admin.clusterBroker))}
|
||||
</div>
|
||||
{ this.state.reblanceVisible && <LeaderRebalanceWrapper
|
||||
changeVisible={(val: boolean) => this.handleVisible(val)}
|
||||
visible={this.state.reblanceVisible}
|
||||
clusterId={this.clusterId}
|
||||
clusterName={this.clusterName}
|
||||
/> }
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Table, Modal, Tooltip } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import Url from 'lib/url-parser';
|
||||
import { IOffset, IXFormWrapper } from 'types/base-type';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { pagination } from 'constants/table';
|
||||
import { admin } from 'store/admin';
|
||||
import { getConsumerDetails } from 'lib/api';
|
||||
import './index.less';
|
||||
|
||||
@observer
|
||||
export class ClusterConsumer extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
public consumerDetails = [] as string[];
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
detailsVisible: false,
|
||||
};
|
||||
|
||||
public columns = [{
|
||||
title: '消费组名称',
|
||||
dataIndex: 'consumerGroup',
|
||||
key: 'consumerGroup',
|
||||
width: '70%',
|
||||
sorter: (a: IOffset, b: IOffset) => a.consumerGroup.charCodeAt(0) - b.consumerGroup.charCodeAt(0),
|
||||
render: (text: string) => <Tooltip placement="bottomLeft" title={text} >{text}</Tooltip>,
|
||||
}, {
|
||||
title: 'Location',
|
||||
dataIndex: 'location',
|
||||
key: 'location',
|
||||
width: '20%',
|
||||
render: (t: string) => t.toLowerCase(),
|
||||
}, {
|
||||
title: '操作',
|
||||
key: 'operation',
|
||||
width: '10%',
|
||||
render: (t: string, item: IOffset) => {
|
||||
return (<a onClick={() => this.getConsumeDetails(item)}>详情</a>);
|
||||
},
|
||||
}];
|
||||
private xFormModal: IXFormWrapper;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public getConsumeDetails(record: IOffset) {
|
||||
getConsumerDetails(this.clusterId, record.consumerGroup, record.location).then((data: string[]) => {
|
||||
this.consumerDetails = data;
|
||||
this.setState({ detailsVisible: true });
|
||||
});
|
||||
}
|
||||
|
||||
public handleDetailsOk() {
|
||||
this.setState({ detailsVisible: false });
|
||||
}
|
||||
|
||||
public handleDetailsCancel() {
|
||||
this.setState({ detailsVisible: false });
|
||||
}
|
||||
|
||||
public getData<T extends IOffset>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IOffset) =>
|
||||
(item.consumerGroup !== undefined && item.consumerGroup !== null) && item.consumerGroup.toLowerCase().includes(searchKey as string)
|
||||
|| (item.location !== undefined && item.location !== null) && item.location.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getClusterConsumer(this.clusterId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
let details: any[];
|
||||
details = this.consumerDetails ? this.consumerDetails.map((ele, index) => {
|
||||
return {
|
||||
key: index,
|
||||
topicName: ele,
|
||||
};
|
||||
}) : [];
|
||||
|
||||
const consumptionColumns = [{
|
||||
title: 'Topic名称',
|
||||
dataIndex: 'topicName',
|
||||
key: 'topicName',
|
||||
}];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch()}
|
||||
</ul>
|
||||
<Table
|
||||
columns={this.columns}
|
||||
dataSource={this.getData(admin.consumerData)}
|
||||
pagination={pagination}
|
||||
rowKey="key"
|
||||
/>
|
||||
</div>
|
||||
<Modal
|
||||
title="消费的Topic"
|
||||
visible={this.state.detailsVisible}
|
||||
onOk={() => this.handleDetailsOk()}
|
||||
onCancel={() => this.handleDetailsCancel()}
|
||||
maskClosable={false}
|
||||
footer={null}
|
||||
>
|
||||
<Table
|
||||
columns={consumptionColumns}
|
||||
dataSource={details}
|
||||
pagination={pagination}
|
||||
rowKey="key"
|
||||
scroll={{ y: 260 }}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { Table } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pagination } from 'constants/table';
|
||||
import Url from 'lib/url-parser';
|
||||
import { IController } from 'types/base-type';
|
||||
import { admin } from 'store/admin';
|
||||
import './index.less';
|
||||
import moment from 'moment';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class ClusterController extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public getData<T extends IController>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IController) =>
|
||||
(item.host !== undefined && item.host !== null) && item.host.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderController() {
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'BrokerId',
|
||||
dataIndex: 'brokerId',
|
||||
key: 'brokerId',
|
||||
width: '30%',
|
||||
sorter: (a: IController, b: IController) => b.brokerId - a.brokerId,
|
||||
},
|
||||
{
|
||||
title: 'BrokerHost',
|
||||
key: 'host',
|
||||
dataIndex: 'host',
|
||||
width: '30%',
|
||||
render: (r: string, t: IController) => {
|
||||
return (
|
||||
<a href={`${this.urlPrefix}/admin/broker-detail?clusterId=${this.clusterId}&brokerId=${t.brokerId}`}>{r}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '变更时间',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
width: '40%',
|
||||
sorter: (a: IController, b: IController) => b.timestamp - a.timestamp,
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={this.getData(admin.controllerHistory)}
|
||||
pagination={pagination}
|
||||
rowKey="key"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getControllerHistory(this.clusterId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch('', '请输入Host')}
|
||||
</ul>
|
||||
{this.renderController()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import * as React from 'react';
|
||||
import { PageHeader, Descriptions, Divider, Tooltip, Icon, Spin } from 'component/antd';
|
||||
import { ILabelValue, IMetaData, IOptionType, IClusterReal } from 'types/base-type';
|
||||
import { controlOptionMap, clusterTypeMap } from 'constants/status-map';
|
||||
import { copyString } from 'lib/utils';
|
||||
import { observer } from 'mobx-react';
|
||||
import { admin } from 'store/admin';
|
||||
import Url from 'lib/url-parser';
|
||||
import moment from 'moment';
|
||||
import './index.less';
|
||||
import { StatusGraghCom } from 'component/flow-table';
|
||||
import { renderTrafficTable, NetWorkFlow } from 'container/network-flow';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
interface IOverview {
|
||||
basicInfo: IMetaData;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterOverview extends React.Component<IOverview> {
|
||||
public clusterId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public clusterContent() {
|
||||
const content = this.props.basicInfo as IMetaData;
|
||||
const gmtCreate = moment(content.gmtCreate).format(timeFormat);
|
||||
const clusterContent = [{
|
||||
value: content.clusterName,
|
||||
label: '集群名称',
|
||||
}, {
|
||||
value: clusterTypeMap[content.mode],
|
||||
label: '集群类型',
|
||||
}, {
|
||||
value: gmtCreate,
|
||||
label: '接入时间',
|
||||
}];
|
||||
const clusterInfo = [{
|
||||
value: content.kafkaVersion,
|
||||
label: 'kafka版本',
|
||||
}, {
|
||||
value: content.bootstrapServers,
|
||||
label: 'Bootstrap Severs',
|
||||
}, {
|
||||
value: content.zookeeper,
|
||||
label: 'Zookeeper',
|
||||
}];
|
||||
return (
|
||||
<>
|
||||
<div className="chart-title">基本信息</div>
|
||||
<PageHeader className="detail" title="">
|
||||
<Descriptions size="small" column={3}>
|
||||
{clusterContent.map((item: ILabelValue, index: number) => (
|
||||
<Descriptions.Item
|
||||
key={index}
|
||||
label={item.label}
|
||||
>{item.value}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
{clusterInfo.map((item: ILabelValue, index: number) => (
|
||||
<Descriptions.Item key={index} label={item.label}>
|
||||
<Tooltip placement="bottomLeft" title={item.value}>
|
||||
<span className="overview-bootstrap">
|
||||
<Icon
|
||||
onClick={() => copyString(item.value)}
|
||||
type="copy"
|
||||
className="didi-theme overview-theme"
|
||||
/>
|
||||
<i className="overview-boot">{item.value}</i>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</PageHeader>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public updateRealStatus = () => {
|
||||
admin.getClusterRealTime(this.clusterId);
|
||||
}
|
||||
|
||||
public onSelectChange(e: IOptionType) {
|
||||
return admin.changeType(e);
|
||||
}
|
||||
|
||||
public getOptionApi = () => {
|
||||
return admin.getClusterMetrice(this.clusterId);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getClusterRealTime(this.clusterId);
|
||||
}
|
||||
|
||||
public renderHistoryTraffic() {
|
||||
return (
|
||||
<NetWorkFlow
|
||||
key="1"
|
||||
selectArr={controlOptionMap}
|
||||
type={admin.type}
|
||||
selectChange={(value: IOptionType) => this.onSelectChange(value)}
|
||||
getApi={() => this.getOptionApi()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderTrafficInfo = () => {
|
||||
return (
|
||||
<Spin spinning={admin.realClusterLoading}>
|
||||
{renderTrafficTable(this.updateRealStatus, StatusGragh)}
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<div className="base-info">
|
||||
{this.clusterContent()}
|
||||
{this.renderTrafficInfo()}
|
||||
{this.renderHistoryTraffic()}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class StatusGragh extends StatusGraghCom<IClusterReal> {
|
||||
public getData = () => {
|
||||
return admin.clusterRealData;
|
||||
}
|
||||
public getLoading = () => {
|
||||
return admin.realClusterLoading;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import * as React from 'react';
|
||||
import Url from 'lib/url-parser';
|
||||
import { region } from 'store';
|
||||
import { admin } from 'store/admin';
|
||||
import { Table, notification, Tooltip, Popconfirm } from 'antd';
|
||||
import { pagination, cellStyle } from 'constants/table';
|
||||
import { observer } from 'mobx-react';
|
||||
import { IClusterTopics } from 'types/base-type';
|
||||
import { deleteClusterTopic } from 'lib/api';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { users } from 'store/users';
|
||||
import { urlPrefix } from 'constants/left-menu';
|
||||
import { transMSecondToHour } from 'lib/utils';
|
||||
import './index.less';
|
||||
|
||||
import moment = require('moment');
|
||||
import { ExpandPartitionFormWrapper } from 'container/modal/admin/expand-partition';
|
||||
import { showEditClusterTopic } from 'container/modal/admin';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class ClusterTopic extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
public clusterTopicsFrom: IClusterTopics;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
expandVisible: false,
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public getBaseInfo(item: IClusterTopics) {
|
||||
admin.getTopicsBasicInfo(item.clusterId, item.topicName).then(data => {
|
||||
showEditClusterTopic(data);
|
||||
});
|
||||
}
|
||||
|
||||
public handleVisible(val: boolean) {
|
||||
this.setState({ expandVisible: val });
|
||||
}
|
||||
|
||||
public expandPartition(item: IClusterTopics) {
|
||||
this.clusterTopicsFrom = item;
|
||||
this.setState({
|
||||
expandVisible: true,
|
||||
});
|
||||
}
|
||||
|
||||
public deleteTopic(item: IClusterTopics) {
|
||||
const value = [{
|
||||
clusterId: item.clusterId,
|
||||
topicName: item.topicName,
|
||||
unForce: false,
|
||||
}];
|
||||
deleteClusterTopic(value).then(data => {
|
||||
notification.success({ message: '删除成功' });
|
||||
admin.getClusterTopics(this.clusterId);
|
||||
});
|
||||
}
|
||||
|
||||
public getData<T extends IClusterTopics>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IClusterTopics) =>
|
||||
(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 componentDidMount() {
|
||||
admin.getClusterTopics(this.clusterId);
|
||||
}
|
||||
|
||||
public renderClusterTopicList() {
|
||||
const clusterColumns = [
|
||||
{
|
||||
title: 'Topic名称',
|
||||
dataIndex: 'topicName',
|
||||
key: 'topicName',
|
||||
width: '15%',
|
||||
sorter: (a: IClusterTopics, b: IClusterTopics) => a.topicName.charCodeAt(0) - b.topicName.charCodeAt(0),
|
||||
render: (text: string, record: IClusterTopics) => {
|
||||
return (
|
||||
<Tooltip placement="bottomLeft" title={record.topicName} >
|
||||
<a
|
||||
// tslint:disable-next-line:max-line-length
|
||||
href={`${urlPrefix}/topic/topic-detail?clusterId=${record.clusterId || ''}&topic=${record.topicName || ''}&isPhysicalClusterId=true®ion=${region.currentRegion}`}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
</Tooltip>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'QPS',
|
||||
dataIndex: 'produceRequest',
|
||||
key: 'produceRequest',
|
||||
width: '10%',
|
||||
sorter: (a: IClusterTopics, b: IClusterTopics) => b.produceRequest - a.produceRequest,
|
||||
render: (t: number) => t === null ? '' : t.toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Bytes In(KB/s)',
|
||||
dataIndex: 'byteIn',
|
||||
key: 'byteIn',
|
||||
width: '15%',
|
||||
sorter: (a: IClusterTopics, b: IClusterTopics) => b.byteIn - a.byteIn,
|
||||
render: (t: number) => t === null ? '' : (t / 1024).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: '所属应用',
|
||||
dataIndex: 'appName',
|
||||
key: 'appName',
|
||||
width: '10%',
|
||||
render: (val: string, record: IClusterTopics) => (
|
||||
<Tooltip placement="bottomLeft" title={record.appId} >
|
||||
{val}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '保存时间(h)',
|
||||
dataIndex: 'retentionTime',
|
||||
key: 'retentionTime',
|
||||
width: '10%',
|
||||
sorter: (a: IClusterTopics, b: IClusterTopics) => b.retentionTime - a.retentionTime,
|
||||
render: (time: any) => transMSecondToHour(time),
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
title: 'Topic说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
width: '15%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 180,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: '30%',
|
||||
render: (value: string, item: IClusterTopics) => (
|
||||
<>
|
||||
<a onClick={() => this.getBaseInfo(item)} className="action-button">编辑</a>
|
||||
<a onClick={() => this.expandPartition(item)} className="action-button">扩分区</a>
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
onConfirm={() => this.deleteTopic(item)}
|
||||
>
|
||||
<a>删除</a>
|
||||
</Popconfirm>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
if (users.currentUser.role !== 2) {
|
||||
clusterColumns.splice(-1, 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch('', '请输入Topic名称,应用名称')}
|
||||
</ul>
|
||||
<Table
|
||||
loading={admin.loading}
|
||||
rowKey="key"
|
||||
dataSource={this.getData(admin.clusterTopics)}
|
||||
columns={clusterColumns}
|
||||
pagination={pagination}
|
||||
/>
|
||||
</div>
|
||||
{this.renderExpandModal()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderExpandModal() {
|
||||
let formData = {} as IClusterTopics;
|
||||
formData = this.clusterTopicsFrom ? this.clusterTopicsFrom : formData;
|
||||
return (
|
||||
<>
|
||||
{this.state.expandVisible && <ExpandPartitionFormWrapper
|
||||
handleVisible={(val: boolean) => this.handleVisible(val)}
|
||||
visible={this.state.expandVisible}
|
||||
formData={formData}
|
||||
clusterId={this.clusterId}
|
||||
/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
admin.clusterTopics ? <> {this.renderClusterTopicList()} </> : null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { Table, Tooltip } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pagination } from 'constants/table';
|
||||
import Url from 'lib/url-parser';
|
||||
import { IThrottles } from 'types/base-type';
|
||||
import { admin } from 'store/admin';
|
||||
import './index.less';
|
||||
|
||||
@observer
|
||||
export class CurrentLimiting extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public renderController() {
|
||||
const clientType = Object.assign({
|
||||
title: '类型',
|
||||
dataIndex: 'throttleClientType',
|
||||
key: 'throttleClientType',
|
||||
width: '15%',
|
||||
filters: [{ text: 'fetch', value: 'Fetch' }, { text: 'produce', value: 'Produce' }],
|
||||
onFilter: (value: string, record: IThrottles) => record.throttleClientType === value,
|
||||
render: (t: string) => t,
|
||||
}, this.renderColumnsFilter('filterStatus'));
|
||||
const columns = [
|
||||
{
|
||||
title: 'Topic名称',
|
||||
key: 'topicName',
|
||||
dataIndex: 'topicName',
|
||||
width: '50%',
|
||||
sorter: (a: IThrottles, b: IThrottles) => a.topicName.charCodeAt(0) - b.topicName.charCodeAt(0),
|
||||
render: (val: string) => <Tooltip placement="bottomLeft" title={val}> {val} </Tooltip>,
|
||||
},
|
||||
{
|
||||
title: '应用ID',
|
||||
dataIndex: 'appId',
|
||||
key: 'appId',
|
||||
width: '15%',
|
||||
sorter: (a: IThrottles, b: IThrottles) => a.appId.charCodeAt(0) - b.appId.charCodeAt(0),
|
||||
},
|
||||
clientType,
|
||||
{
|
||||
title: 'Broker',
|
||||
dataIndex: 'brokerIdList',
|
||||
key: 'brokerIdList',
|
||||
width: '20%',
|
||||
render: (value: number[]) => {
|
||||
const num = value ? `[${value.join(',')}]` : '';
|
||||
return(
|
||||
<span>{num}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
const { searchKey } = this.state;
|
||||
if (!admin.clustersThrottles ) return null;
|
||||
const clustersThrottles = admin.clustersThrottles.filter(d =>
|
||||
((d.topicName !== undefined && d.topicName !== null) && d.topicName.toLowerCase().includes(searchKey.toLowerCase()))
|
||||
|| ((d.appId !== undefined && d.appId !== null) && d.appId.toLowerCase().includes(searchKey.toLowerCase())));
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={clustersThrottles}
|
||||
pagination={pagination}
|
||||
rowKey="key"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getClustersThrottles(this.clusterId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
{this.renderSearch()}
|
||||
</ul>
|
||||
{this.renderController()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Table, notification, Tooltip, Popconfirm } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { pagination, cellStyle } from 'constants/table';
|
||||
import { wrapper } from 'store';
|
||||
import { admin } from 'store/admin';
|
||||
import { IXFormWrapper, IBrokersRegions, INewRegions, IMetaData } from 'types/base-type';
|
||||
import { deleteRegions } from 'lib/api';
|
||||
import { transBToMB } from 'lib/utils';
|
||||
import Url from 'lib/url-parser';
|
||||
import moment from 'moment';
|
||||
import './index.less';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class ExclusiveCluster extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
filterStatus: false,
|
||||
};
|
||||
|
||||
private xFormModal: IXFormWrapper;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public renderColumns = () => {
|
||||
const status = Object.assign({
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: '10%',
|
||||
filters: [{ text: '正常', value: '0' }, { text: '容量已满', value: '1' }],
|
||||
onFilter: (value: string, record: IBrokersRegions) => record.status === Number(value),
|
||||
render: (t: number) => <span className={t === 0 ? 'success' : 'fail'}>{t === 0 ? '正常' : '容量已满'}</span>,
|
||||
}, this.renderColumnsFilter('filterStatus'));
|
||||
return [
|
||||
{
|
||||
title: 'RegionID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: '7%',
|
||||
},
|
||||
{
|
||||
title: 'Region名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '13%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 160,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
sorter: (a: IBrokersRegions, b: IBrokersRegions) => a.name.charCodeAt(0) - b.name.charCodeAt(0),
|
||||
render: (text: string, r: IBrokersRegions) => (
|
||||
<Tooltip placement="bottomLeft" title={text}>
|
||||
{text}
|
||||
</Tooltip>),
|
||||
},
|
||||
{
|
||||
title: 'BrokerIdList',
|
||||
dataIndex: 'brokerIdList',
|
||||
key: 'brokerIdList',
|
||||
width: '10%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 200,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
render: (value: number[]) => {
|
||||
const num = value ? value.join(',') : '';
|
||||
return(
|
||||
<Tooltip placement="bottomLeft" title={num}>
|
||||
{num}
|
||||
</Tooltip>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '预估容量(MB/s)',
|
||||
dataIndex: 'capacity',
|
||||
key: 'capacity',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokersRegions, b: IBrokersRegions) => b.capacity - a.capacity,
|
||||
render: (t: number) => transBToMB(t),
|
||||
},
|
||||
{
|
||||
title: '实际流量(MB/s)',
|
||||
dataIndex: 'realUsed',
|
||||
key: 'realUsed',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokersRegions, b: IBrokersRegions) => b.realUsed - a.realUsed,
|
||||
render: (t: number) => transBToMB(t),
|
||||
},
|
||||
{
|
||||
title: '预估流量(MB/s)',
|
||||
dataIndex: 'estimateUsed',
|
||||
key: 'estimateUsed',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokersRegions, b: IBrokersRegions) => b.estimateUsed - a.estimateUsed,
|
||||
render: (t: number) => transBToMB(t),
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'gmtModify',
|
||||
key: 'gmtModify',
|
||||
width: '10%',
|
||||
sorter: (a: IBrokersRegions, b: IBrokersRegions) => b.gmtModify - a.gmtModify,
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
},
|
||||
status,
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
width: '10%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 200,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
render: (text: string, r: IBrokersRegions) => (
|
||||
<Tooltip placement="bottomLeft" title={text}>
|
||||
{text}
|
||||
</Tooltip>),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: '10%',
|
||||
render: (text: string, record: IBrokersRegions) => {
|
||||
return (
|
||||
<span className="table-operation">
|
||||
<a onClick={() => this.addOrModifyRegion(record)}>编辑</a>
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
onConfirm={() => this.handleDeleteRegion(record)}
|
||||
>
|
||||
<a>删除</a>
|
||||
</Popconfirm>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public handleDeleteRegion = (record: IBrokersRegions) => {
|
||||
deleteRegions(record.id).then(() => {
|
||||
notification.success({ message: '删除成功' });
|
||||
admin.getBrokersRegions(this.clusterId);
|
||||
});
|
||||
}
|
||||
|
||||
public addOrModifyRegion(record?: IBrokersRegions) {
|
||||
const content = this.props.basicInfo as IMetaData;
|
||||
|
||||
this.xFormModal = {
|
||||
formMap: [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Region名称',
|
||||
rules: [{ required: true, message: '请输入Region名称' }],
|
||||
attrs: { placeholder: '请输入Region名称' },
|
||||
},
|
||||
{
|
||||
key: 'clusterName',
|
||||
label: '集群名称',
|
||||
rules: [{ required: true, message: '请输入集群名称' }],
|
||||
defaultValue: content.clusterName,
|
||||
attrs: {
|
||||
disabled: true,
|
||||
placeholder: '请输入集群名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'brokerIdList',
|
||||
label: 'Broker列表',
|
||||
defaultValue: record ? record.brokerIdList.join(',') : [],
|
||||
rules: [{ required: true, message: '请输入BrokerIdList' }],
|
||||
attrs: {
|
||||
placeholder: '请输入BrokerIdList',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: '状态',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '容量已满',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
defaultValue: 0,
|
||||
rules: [{ required: true, message: '请选择状态' }],
|
||||
attrs: {
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
label: '备注',
|
||||
type: 'text_area',
|
||||
rules: [{
|
||||
required: false,
|
||||
}],
|
||||
attrs: {
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
],
|
||||
formData: record,
|
||||
visible: true,
|
||||
title: `${record ? '编辑' : '新增Region'}`,
|
||||
onSubmit: (value: INewRegions) => {
|
||||
value.clusterId = this.clusterId;
|
||||
value.brokerIdList = value.brokerIdList && Array.isArray(value.brokerIdList) ?
|
||||
value.brokerIdList : value.brokerIdList.split(',');
|
||||
if (record) {
|
||||
value.id = record.id;
|
||||
}
|
||||
delete value.clusterName;
|
||||
if (record) {
|
||||
return admin.editRegions(this.clusterId, value).then(data => {
|
||||
notification.success({ message: '编辑Region成功' });
|
||||
});
|
||||
}
|
||||
return admin.addNewRegions(this.clusterId, value).then(data => {
|
||||
notification.success({ message: '新建Region成功' });
|
||||
});
|
||||
},
|
||||
};
|
||||
wrapper.open(this.xFormModal);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getBrokersRegions(this.clusterId);
|
||||
admin.getBrokersMetadata(this.clusterId);
|
||||
}
|
||||
|
||||
public getData<T extends IBrokersRegions>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: IBrokersRegions) =>
|
||||
(item.name !== undefined && item.name !== null) && item.name.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderRegion() {
|
||||
return (
|
||||
<Table
|
||||
columns={this.renderColumns()}
|
||||
dataSource={this.getData(admin.brokersRegions)}
|
||||
pagination={pagination}
|
||||
rowKey="id"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
<li className="k-add" onClick={() => this.addOrModifyRegion()}>
|
||||
<i className="k-icon-xinjian didi-theme" />
|
||||
<span>新增Region</span>
|
||||
</li>
|
||||
{this.renderSearch('', '请输入Region名称')}
|
||||
</ul>
|
||||
{this.renderRegion()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
.table-operation-bar {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
z-index: 100;
|
||||
li {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
.ant-select {
|
||||
width: 150px;
|
||||
}
|
||||
.ant-input-search {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.traffic-table {
|
||||
margin: 10px 0;
|
||||
min-height: 450px;
|
||||
.traffic-header {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-weight: bold;
|
||||
background: rgb(245, 245, 245);
|
||||
border: 1px solid #e8e8e8;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 13px;
|
||||
line-height: 44px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.k-abs {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.implement-button {
|
||||
float: right;
|
||||
margin-right: -120px;
|
||||
}
|
||||
|
||||
.diagram {
|
||||
min-width: 900px;
|
||||
background: white;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.diagram-box {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 46%;
|
||||
height: 100%;
|
||||
h2 {
|
||||
line-height: 30px;
|
||||
font-size: 13px;
|
||||
margin-left: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.descriptions {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.leader-seacrh {
|
||||
z-index: 9999999;
|
||||
margin-top: 10px;
|
||||
.search-top {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tabs, PageHeader } from 'antd';
|
||||
import { IMetaData } from 'types/base-type';
|
||||
import { ClusterOverview } from './cluster-overview';
|
||||
import { ClusterTopic } from './cluster-topic';
|
||||
import { ClusterBroker } from './cluster-broker';
|
||||
import { ClusterConsumer } from './cluster-consumer';
|
||||
import { ExclusiveCluster } from './exclusive-cluster';
|
||||
import { LogicalCluster } from './logical-cluster';
|
||||
import { ClusterController } from './cluster-controller';
|
||||
import { CurrentLimiting } from './current-limiting';
|
||||
import { handleTabKey } from 'lib/utils';
|
||||
import { admin } from 'store/admin';
|
||||
import { handlePageBack } from 'lib/utils';
|
||||
import Url from 'lib/url-parser';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
@observer
|
||||
export class ClusterDetail extends React.Component {
|
||||
public clusterId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
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')}
|
||||
title={`集群列表/${content.clusterName || ''}`}
|
||||
/>
|
||||
<Tabs activeKey={location.hash.substr(1) || '1'} type="card" onChange={handleTabKey}>
|
||||
<TabPane tab="集群概览" key="1">
|
||||
<ClusterOverview basicInfo={content} />
|
||||
</TabPane>
|
||||
<TabPane tab="Topic信息" key="2">
|
||||
<ClusterTopic tab={'Topic信息'}/>
|
||||
</TabPane>
|
||||
<TabPane tab="Broker信息" key="3">
|
||||
<ClusterBroker tab={'Broker信息'} basicInfo={content} />
|
||||
</TabPane>
|
||||
<TabPane tab="消费组信息" key="4">
|
||||
<ClusterConsumer tab={'消费组信息'} />
|
||||
</TabPane>
|
||||
<TabPane tab="Region信息" key="5">
|
||||
<ExclusiveCluster tab={'Region信息'} basicInfo={content} />
|
||||
</TabPane>
|
||||
<TabPane tab="逻辑集群信息" key="6">
|
||||
<LogicalCluster tab={'逻辑集群信息'} basicInfo={content} />
|
||||
</TabPane>
|
||||
<TabPane tab="Controller变更历史" key="7">
|
||||
<ClusterController tab={'Controller变更历史'} />
|
||||
</TabPane>
|
||||
<TabPane tab="限流信息" key="8">
|
||||
<CurrentLimiting tab={'限流信息'}/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Table, notification, Popconfirm } from 'component/antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { pagination } from 'constants/table';
|
||||
import Url from 'lib/url-parser';
|
||||
import moment from 'moment';
|
||||
import { admin } from 'store/admin';
|
||||
import { cluster } from 'store/cluster';
|
||||
import { ILogicalCluster } from 'types/base-type';
|
||||
import './index.less';
|
||||
import { app } from 'store/app';
|
||||
import { showLogicalClusterOpModal } from 'container/modal';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class LogicalCluster extends SearchAndFilterContainer {
|
||||
public clusterId: number;
|
||||
|
||||
public state = {
|
||||
searchKey: '',
|
||||
filterStatus: false,
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.clusterId = Number(url.search.clusterId);
|
||||
}
|
||||
|
||||
public renderColumns = () => {
|
||||
return [
|
||||
{
|
||||
title: '逻辑集群ID',
|
||||
dataIndex: 'logicalClusterId',
|
||||
key: 'logicalClusterId',
|
||||
},
|
||||
{
|
||||
title: '逻辑集群名称',
|
||||
dataIndex: 'logicalClusterName',
|
||||
key: 'logicalClusterName',
|
||||
},
|
||||
{
|
||||
title: '应用ID',
|
||||
dataIndex: 'appId',
|
||||
key: 'appId',
|
||||
},
|
||||
{
|
||||
title: 'RegionIdList',
|
||||
dataIndex: 'regionIdList',
|
||||
key: 'regionIdList',
|
||||
render: (value: number[]) => {
|
||||
const num = value ? `[${value.join(',')}]` : '';
|
||||
return(
|
||||
<span>{num}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '集群模式',
|
||||
dataIndex: 'mode',
|
||||
key: 'mode',
|
||||
render: (value: number) => {
|
||||
let val = '';
|
||||
cluster.clusterModes.forEach((ele: any) => {
|
||||
if (value === ele.code) {
|
||||
val = ele.message;
|
||||
}
|
||||
});
|
||||
return(<span>{val}</span>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'gmtModify',
|
||||
key: 'gmtModify',
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render: (text: string, record: ILogicalCluster) => {
|
||||
return (
|
||||
<span className="table-operation">
|
||||
<a onClick={() => this.editRegion(record)}>编辑</a>
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
onConfirm={() => this.handleDeleteRegion(record)}
|
||||
>
|
||||
<a>删除</a>
|
||||
</Popconfirm>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public handleDeleteRegion = (record: ILogicalCluster) => {
|
||||
admin.deteleLogicalClusters(this.clusterId, record.logicalClusterId).then(() => {
|
||||
notification.success({ message: '删除成功' });
|
||||
});
|
||||
}
|
||||
|
||||
public async editRegion(record: ILogicalCluster) {
|
||||
await admin.queryLogicalClusters(record.logicalClusterId);
|
||||
await this.addOrEditLogicalCluster(admin.queryLogical);
|
||||
}
|
||||
|
||||
public addOrEditLogicalCluster(record?: ILogicalCluster) {
|
||||
showLogicalClusterOpModal(this.clusterId, record);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
admin.getLogicalClusters(this.clusterId);
|
||||
cluster.getClusterModes();
|
||||
|
||||
admin.getBrokersRegions(this.clusterId);
|
||||
if (!app.adminAppData.length) {
|
||||
app.getAdminAppList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public getData<T extends ILogicalCluster>(origin: T[]) {
|
||||
let data: T[] = origin;
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
data = searchKey ? origin.filter((item: ILogicalCluster) =>
|
||||
(item.logicalClusterName !== undefined && item.logicalClusterName !== null)
|
||||
&& item.logicalClusterName.toLowerCase().includes(searchKey as string)
|
||||
|| (item.appId !== undefined && item.appId !== null) && item.appId.toLowerCase().includes(searchKey as string),
|
||||
) : origin ;
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderLogicalCluster() {
|
||||
return (
|
||||
<Table
|
||||
columns={this.renderColumns()}
|
||||
dataSource={this.getData(admin.logicalClusters)}
|
||||
pagination={pagination}
|
||||
rowKey="key"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="k-row">
|
||||
<ul className="k-tab">
|
||||
<li>{this.props.tab}</li>
|
||||
<li className="k-add" onClick={() => this.addOrEditLogicalCluster()}>
|
||||
<i className="k-icon-xinjian didi-theme" />
|
||||
<span>新增逻辑集群</span>
|
||||
</li>
|
||||
{this.renderSearch('', '请输入逻辑集群名称或AppId')}
|
||||
</ul>
|
||||
{this.renderLogicalCluster()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user