mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-10 11:40:01 +08:00
304 lines
12 KiB
JavaScript
304 lines
12 KiB
JavaScript
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import React, {useCallback, useEffect, useState} from 'react';
|
|
import {Button, Modal, notification, Select, Spin, Table} from 'antd';
|
|
import {useLanguage} from "../../i18n/LanguageContext";
|
|
import {remoteApi, tools} from "../../api/remoteApi/remoteApi"; // 确保路径正确
|
|
|
|
const {Option} = Select;
|
|
|
|
const Cluster = () => {
|
|
const {t} = useLanguage();
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [clusterNames, setClusterNames] = useState([]);
|
|
const [selectedCluster, setSelectedCluster] = useState('');
|
|
const [instances, setInstances] = useState([]);
|
|
const [allBrokersData, setAllBrokersData] = useState({});
|
|
|
|
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
|
const [configModalVisible, setConfigModalVisible] = useState(false);
|
|
const [currentDetail, setCurrentDetail] = useState({});
|
|
const [currentConfig, setCurrentConfig] = useState({});
|
|
const [currentBrokerName, setCurrentBrokerName] = useState('');
|
|
const [currentIndex, setCurrentIndex] = useState(null); // 对应 brokerId
|
|
const [currentBrokerAddress, setCurrentBrokerAddress] = useState('');
|
|
const [api, contextHolder] = notification.useNotification();
|
|
|
|
const switchCluster = useCallback((clusterName) => {
|
|
if (allBrokersData[clusterName]) {
|
|
setInstances(allBrokersData[clusterName]);
|
|
} else {
|
|
setInstances([]);
|
|
}
|
|
}, [allBrokersData]);
|
|
|
|
const handleChangeCluster = (value) => {
|
|
setSelectedCluster(value);
|
|
switchCluster(value);
|
|
};
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
remoteApi.queryClusterList((resp) => {
|
|
setLoading(false);
|
|
if (resp.status === 0) {
|
|
const {clusterInfo, brokerServer} = resp.data;
|
|
const {clusterAddrTable, brokerAddrTable} = clusterInfo;
|
|
|
|
const generatedBrokers = tools.generateBrokerMap(brokerServer, clusterAddrTable, brokerAddrTable);
|
|
setAllBrokersData(generatedBrokers);
|
|
|
|
const names = Object.keys(clusterAddrTable);
|
|
setClusterNames(names);
|
|
|
|
if (names.length > 0) {
|
|
const defaultCluster = names[0];
|
|
setSelectedCluster(defaultCluster);
|
|
if (generatedBrokers[defaultCluster]) {
|
|
setInstances(generatedBrokers[defaultCluster]);
|
|
} else {
|
|
setInstances([]);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
api.error({message: resp.errMsg || t.QUERY_CLUSTER_LIST_FAILED, duration: 2});
|
|
}
|
|
});
|
|
}, []);
|
|
|
|
const showDetail = (brokerName, brokerId, record) => { // 传入 record 整个对象,方便直接显示
|
|
setCurrentBrokerName(brokerName);
|
|
setCurrentIndex(brokerId);
|
|
setCurrentDetail(record); // 直接使用 record 作为详情
|
|
setDetailModalVisible(true);
|
|
};
|
|
|
|
const showConfig = (brokerAddress, brokerName, brokerId) => { // 保持一致,传入 brokerId
|
|
setCurrentBrokerName(brokerName);
|
|
setCurrentIndex(brokerId);
|
|
setCurrentBrokerAddress(brokerAddress);
|
|
|
|
setLoading(true);
|
|
remoteApi.queryBrokerConfig(brokerAddress, (resp) => {
|
|
setLoading(false);
|
|
if (resp.status === 0) {
|
|
// ✨ 确保 resp.data 是一个对象,如果后端返回的不是对象,这里需要处理
|
|
if (typeof resp.data === 'object' && resp.data !== null) {
|
|
setCurrentConfig(resp.data);
|
|
setConfigModalVisible(true);
|
|
} else {
|
|
api.error({message: t.INVALID_CONFIG_DATA || 'Invalid config data received', duration: 2});
|
|
setCurrentConfig({}); // 清空配置,避免显示错误
|
|
}
|
|
} else {
|
|
api.error({message: resp.errMsg || t.QUERY_BROKER_CONFIG_FAILED, duration: 2});
|
|
}
|
|
});
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: t.SPLIT,
|
|
dataIndex: 'brokerName', // 直接使用 brokerId
|
|
key: 'split',
|
|
align: 'center'
|
|
},
|
|
{
|
|
title: t.NO,
|
|
dataIndex: 'brokerId', // 直接使用 brokerId
|
|
key: 'no',
|
|
align: 'center',
|
|
render: (brokerId) => `${brokerId}${brokerId === 0 ? `(${t.MASTER})` : `(${t.SLAVE})`}`,
|
|
},
|
|
{
|
|
title: t.ADDRESS,
|
|
dataIndex: 'address', // 确保 generateBrokerMap 返回的数据有 address 字段
|
|
key: 'address',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t.VERSION,
|
|
dataIndex: 'brokerVersionDesc',
|
|
key: 'version',
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: t.PRO_MSG_TPS,
|
|
dataIndex: 'putTps',
|
|
key: 'putTps',
|
|
align: 'center',
|
|
render: (text) => {
|
|
const tpsValue = text ? Number(String(text).split(' ')[0]) : 0; // 确保text是字符串
|
|
return tpsValue.toFixed(2);
|
|
},
|
|
},
|
|
{
|
|
title: t.CUS_MSG_TPS,
|
|
key: 'cusMsgTps',
|
|
align: 'center',
|
|
render: (_, record) => {
|
|
// 根据你提供的数据结构,这里可能是 getTransferredTps
|
|
const val = record.getTransferedTps?.trim() ? record.getTransferedTps : record.getTransferredTps;
|
|
const tpsValue = val ? Number(String(val).split(' ')[0]) : 0; // 确保val是字符串
|
|
return tpsValue.toFixed(2);
|
|
},
|
|
},
|
|
{
|
|
title: t.YESTERDAY_PRO_COUNT,
|
|
key: 'yesterdayProCount',
|
|
align: 'center',
|
|
render: (_, record) => {
|
|
const putTotalTodayMorning = parseFloat(record.msgPutTotalTodayMorning || 0);
|
|
const putTotalYesterdayMorning = parseFloat(record.msgPutTotalYesterdayMorning || 0);
|
|
return (putTotalTodayMorning - putTotalYesterdayMorning).toLocaleString();
|
|
}
|
|
},
|
|
{
|
|
title: t.YESTERDAY_CUS_COUNT,
|
|
key: 'yesterdayCusCount',
|
|
align: 'center',
|
|
render: (_, record) => {
|
|
const getTotalTodayMorning = parseFloat(record.msgGetTotalTodayMorning || 0);
|
|
const getTotalYesterdayMorning = parseFloat(record.msgGetTotalYesterdayMorning || 0);
|
|
return (getTotalTodayMorning - getTotalYesterdayMorning).toLocaleString();
|
|
}
|
|
},
|
|
{
|
|
title: t.TODAY_PRO_COUNT,
|
|
key: 'todayProCount',
|
|
align: 'center',
|
|
render: (_, record) => {
|
|
const putTotalTodayNow = parseFloat(record.msgPutTotalTodayNow || 0);
|
|
const putTotalTodayMorning = parseFloat(record.msgPutTotalTodayMorning || 0);
|
|
return (putTotalTodayNow - putTotalTodayMorning).toLocaleString();
|
|
}
|
|
},
|
|
{
|
|
title: t.TODAY_CUS_COUNT,
|
|
key: 'todayCusCount',
|
|
align: 'center',
|
|
render: (_, record) => {
|
|
const getTotalTodayNow = parseFloat(record.msgGetTotalTodayNow || 0);
|
|
const getTotalTodayMorning = parseFloat(record.msgGetTotalTodayMorning || 0);
|
|
return (getTotalTodayNow - getTotalTodayMorning).toLocaleString();
|
|
}
|
|
},
|
|
{
|
|
title: t.OPERATION,
|
|
key: 'operation',
|
|
align: 'center',
|
|
render: (_, record) => (
|
|
<>
|
|
<Button size="small" type="primary"
|
|
onClick={() => showDetail(record.brokerName, record.brokerId, record)}
|
|
style={{marginRight: 8}}>
|
|
{t.STATUS}
|
|
</Button>
|
|
{/* 传入 record.address */}
|
|
<Button size="small" type="primary"
|
|
onClick={() => showConfig(record.address, record.brokerName, record.brokerId)}>
|
|
{t.CONFIG}
|
|
</Button>
|
|
</>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<>
|
|
{contextHolder}
|
|
<Spin spinning={loading} tip={t.LOADING}>
|
|
<div style={{padding: 24}}>
|
|
<div style={{marginBottom: 16, display: 'flex', alignItems: 'center'}}>
|
|
<label style={{marginRight: 8}}>{t.CLUSTER}:</label>
|
|
<Select
|
|
style={{width: 300}}
|
|
placeholder={t.SELECT_CLUSTER || "Please select a cluster"}
|
|
value={selectedCluster}
|
|
onChange={handleChangeCluster}
|
|
allowClear
|
|
>
|
|
{clusterNames.map((name) => (
|
|
<Option key={name} value={name}>
|
|
{name}
|
|
</Option>
|
|
))}
|
|
</Select>
|
|
</div>
|
|
|
|
<Table
|
|
dataSource={instances}
|
|
columns={columns}
|
|
rowKey={(record) => `${record.brokerName}-${record.brokerId}`}
|
|
pagination={false}
|
|
bordered
|
|
size="middle"
|
|
/>
|
|
|
|
<Modal
|
|
title={`${t.BROKER} [${currentBrokerName}][${currentIndex}]`}
|
|
open={detailModalVisible}
|
|
footer={null}
|
|
onCancel={() => setDetailModalVisible(false)}
|
|
width={800}
|
|
bodyStyle={{maxHeight: '60vh', overflowY: 'auto'}}
|
|
>
|
|
<Table
|
|
dataSource={Object.entries(currentDetail).map(([key, value]) => ({key, value}))}
|
|
columns={[
|
|
{title: t.KEY || 'Key', dataIndex: 'key', key: 'key'},
|
|
{title: t.VALUE || 'Value', dataIndex: 'value', key: 'value'},
|
|
]}
|
|
pagination={false}
|
|
size="small"
|
|
bordered
|
|
rowKey="key"
|
|
/>
|
|
</Modal>
|
|
|
|
<Modal
|
|
title={`${t.BROKER} [${currentBrokerName}][${currentIndex}]`}
|
|
open={configModalVisible}
|
|
footer={null}
|
|
onCancel={() => setConfigModalVisible(false)}
|
|
width={800}
|
|
bodyStyle={{maxHeight: '60vh', overflowY: 'auto'}}
|
|
>
|
|
<Table
|
|
dataSource={Object.entries(currentConfig).map(([key, value]) => ({key, value}))}
|
|
columns={[
|
|
{title: t.KEY || 'Key', dataIndex: 'key', key: 'key'},
|
|
{title: t.VALUE || 'Value', dataIndex: 'value', key: 'value'},
|
|
]}
|
|
pagination={false}
|
|
size="small"
|
|
bordered
|
|
rowKey="key"
|
|
/>
|
|
</Modal>
|
|
</div>
|
|
</Spin>
|
|
</>
|
|
|
|
);
|
|
};
|
|
|
|
export default Cluster;
|