[ISSUE #330] Format code and update the doc (#334)

This commit is contained in:
Crazylychee
2025-07-05 20:50:36 +08:00
committed by GitHub
parent 706082c62f
commit ff73529a75
85 changed files with 1012 additions and 906 deletions

View File

@@ -6,19 +6,14 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo
In the project directory, you can run: In the project directory, you can run:
### `npm start` ### `npm run start`
Runs the app in the development mode.\ Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser. Open [http://localhost:3003](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\ The page will reload when you make changes.\
You may also see any lint errors in the console. You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build` ### `npm run build`
Builds the app for production to the `build` folder.\ Builds the app for production to the `build` folder.\

View File

@@ -18,12 +18,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" /> <link rel="shortcut icon" href="./favicon.ico" type="image/x-icon"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="" /> <meta name="description" content=""/>
<meta name="keywords" content="" /> <meta name="keywords" content=""/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>RocketMQ Dashboard</title> <title>RocketMQ Dashboard</title>
</head> </head>
<body> <body>

View File

@@ -17,7 +17,7 @@
import React from 'react'; import React from 'react';
import AppRouter from './router'; // 你 router/index.jsx 导出的组件 import AppRouter from './router'; // 你 router/index.jsx 导出的组件
import { ToastContainer } from 'react-toastify'; import {ToastContainer} from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import {ConfigProvider} from "antd"; import {ConfigProvider} from "antd";
import {useTheme} from "./store/context/ThemeContext"; import {useTheme} from "./store/context/ThemeContext";
@@ -28,8 +28,8 @@ function App() {
return ( return (
<> <>
<ConfigProvider theme={currentTheme}> <ConfigProvider theme={currentTheme}>
<ToastContainer /> <ToastContainer/>
<AppRouter /> <AppRouter/>
</ConfigProvider> </ConfigProvider>
</> </>
); );

View File

@@ -15,11 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { render, screen } from '@testing-library/react'; import {render, screen} from '@testing-library/react';
import App from './App'; import App from './App';
test('renders learn react link', () => { test('renders learn react link', () => {
render(<App />); render(<App/>);
const linkElement = screen.getByText(/learn react/i); const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument(); expect(linkElement).toBeInTheDocument();
}); });

View File

@@ -16,20 +16,20 @@
*/ */
import React from 'react'; import React from 'react';
import { Form, Input, Typography, Modal } from 'antd'; import {Form, Input, Typography} from 'antd';
import moment from 'moment'; import moment from 'moment';
import { useLanguage } from '../i18n/LanguageContext'; // 根据实际路径调整 import {useLanguage} from '../i18n/LanguageContext'; // 根据实际路径调整
const { Text } = Typography; const {Text} = Typography;
const DlqMessageDetailViewDialog = ({ ngDialogData }) => { const DlqMessageDetailViewDialog = ({ngDialogData}) => {
const { t } = useLanguage(); const {t} = useLanguage();
const messageView = ngDialogData?.messageView || {}; const messageView = ngDialogData?.messageView || {};
return ( return (
<div style={{ padding: '20px' }}> <div style={{padding: '20px'}}>
<Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}> <Form layout="horizontal" labelCol={{span: 4}} wrapperCol={{span: 20}}>
<Form.Item label="Message ID:"> <Form.Item label="Message ID:">
<Text strong>{messageView.msgId}</Text> <Text strong>{messageView.msgId}</Text>
</Form.Item> </Form.Item>
@@ -39,7 +39,7 @@ const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
<Form.Item label="Properties:"> <Form.Item label="Properties:">
<Input.TextArea <Input.TextArea
value={typeof messageView.properties === 'object' ? JSON.stringify(messageView.properties, null, 2) : messageView.properties} value={typeof messageView.properties === 'object' ? JSON.stringify(messageView.properties, null, 2) : messageView.properties}
style={{ minHeight: 100, resize: 'none' }} style={{minHeight: 100, resize: 'none'}}
readOnly readOnly
/> />
</Form.Item> </Form.Item>
@@ -61,7 +61,7 @@ const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
<Form.Item label="Message body:"> <Form.Item label="Message body:">
<Input.TextArea <Input.TextArea
value={messageView.messageBody} value={messageView.messageBody}
style={{ minHeight: 100, resize: 'none' }} style={{minHeight: 100, resize: 'none'}}
readOnly readOnly
/> />
</Form.Item> </Form.Item>

View File

@@ -16,16 +16,16 @@
*/ */
import React from 'react'; import React from 'react';
import { Modal, Button, Typography, Descriptions, Tag, Spin, notification } from 'antd'; import {Button, Descriptions, Modal, notification, Spin, Tag, Typography} from 'antd';
import moment from 'moment'; import moment from 'moment';
import { ExclamationCircleOutlined, SyncOutlined } from '@ant-design/icons'; import {SyncOutlined} from '@ant-design/icons';
import { useLanguage } from '../i18n/LanguageContext'; import {useLanguage} from '../i18n/LanguageContext';
import { remoteApi } from '../api/remoteApi/remoteApi'; // 确保这个路径正确 import {remoteApi} from '../api/remoteApi/remoteApi'; // 确保这个路径正确
const { Text, Paragraph } = Typography; const {Text, Paragraph} = Typography;
const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResendMessage }) => { const MessageDetailViewDialog = ({visible, onCancel, messageId, topic, onResendMessage}) => {
const { t } = useLanguage(); const {t} = useLanguage();
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
const [messageDetail, setMessageDetail] = React.useState(null); const [messageDetail, setMessageDetail] = React.useState(null);
const [error, setError] = React.useState(null); const [error, setError] = React.useState(null);
@@ -89,17 +89,21 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
> >
<Spin spinning={loading} tip={t.LOADING}> <Spin spinning={loading} tip={t.LOADING}>
{error && ( {error && (
<Paragraph type="danger" style={{ textAlign: 'center' }}> <Paragraph type="danger" style={{textAlign: 'center'}}>
{error} {error}
</Paragraph> </Paragraph>
)} )}
{messageDetail ? ( // 确保 messageDetail 存在时才渲染内容 {messageDetail ? ( // 确保 messageDetail 存在时才渲染内容
<> <>
{/* 消息信息部分 */} {/* 消息信息部分 */}
<Descriptions title={<Text strong>{t.MESSAGE_INFO}</Text>} bordered column={2} size="small" style={{ marginBottom: 20 }}> <Descriptions title={<Text strong>{t.MESSAGE_INFO}</Text>} bordered column={2} size="small"
<Descriptions.Item label="Topic" span={2}><Text copyable>{messageDetail.messageView.topic}</Text></Descriptions.Item> style={{marginBottom: 20}}>
<Descriptions.Item label="Message ID" span={2}><Text copyable>{messageDetail.messageView.msgId}</Text></Descriptions.Item> <Descriptions.Item label="Topic" span={2}><Text
<Descriptions.Item label="StoreHost">{messageDetail.messageView.storeHost}</Descriptions.Item> copyable>{messageDetail.messageView.topic}</Text></Descriptions.Item>
<Descriptions.Item label="Message ID" span={2}><Text
copyable>{messageDetail.messageView.msgId}</Text></Descriptions.Item>
<Descriptions.Item
label="StoreHost">{messageDetail.messageView.storeHost}</Descriptions.Item>
<Descriptions.Item label="BornHost">{messageDetail.messageView.bornHost}</Descriptions.Item> <Descriptions.Item label="BornHost">{messageDetail.messageView.bornHost}</Descriptions.Item>
<Descriptions.Item label="StoreTime"> <Descriptions.Item label="StoreTime">
{moment(messageDetail.messageView.storeTimestamp).format("YYYY-MM-DD HH:mm:ss")} {moment(messageDetail.messageView.storeTimestamp).format("YYYY-MM-DD HH:mm:ss")}
@@ -108,26 +112,33 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
{moment(messageDetail.messageView.bornTimestamp).format("YYYY-MM-DD HH:mm:ss")} {moment(messageDetail.messageView.bornTimestamp).format("YYYY-MM-DD HH:mm:ss")}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="Queue ID">{messageDetail.messageView.queueId}</Descriptions.Item> <Descriptions.Item label="Queue ID">{messageDetail.messageView.queueId}</Descriptions.Item>
<Descriptions.Item label="Queue Offset">{messageDetail.messageView.queueOffset}</Descriptions.Item> <Descriptions.Item
<Descriptions.Item label="StoreSize">{messageDetail.messageView.storeSize} bytes</Descriptions.Item> label="Queue Offset">{messageDetail.messageView.queueOffset}</Descriptions.Item>
<Descriptions.Item label="ReconsumeTimes">{messageDetail.messageView.reconsumeTimes}</Descriptions.Item> <Descriptions.Item
label="StoreSize">{messageDetail.messageView.storeSize} bytes</Descriptions.Item>
<Descriptions.Item
label="ReconsumeTimes">{messageDetail.messageView.reconsumeTimes}</Descriptions.Item>
<Descriptions.Item label="BodyCRC">{messageDetail.messageView.bodyCRC}</Descriptions.Item> <Descriptions.Item label="BodyCRC">{messageDetail.messageView.bodyCRC}</Descriptions.Item>
<Descriptions.Item label="SysFlag">{messageDetail.messageView.sysFlag}</Descriptions.Item> <Descriptions.Item label="SysFlag">{messageDetail.messageView.sysFlag}</Descriptions.Item>
<Descriptions.Item label="Flag">{messageDetail.messageView.flag}</Descriptions.Item> <Descriptions.Item label="Flag">{messageDetail.messageView.flag}</Descriptions.Item>
<Descriptions.Item label="PreparedTransactionOffset">{messageDetail.messageView.preparedTransactionOffset}</Descriptions.Item> <Descriptions.Item
label="PreparedTransactionOffset">{messageDetail.messageView.preparedTransactionOffset}</Descriptions.Item>
</Descriptions> </Descriptions>
{/* 消息属性部分 */} {/* 消息属性部分 */}
{Object.keys(messageDetail.messageView.properties).length > 0 && ( {Object.keys(messageDetail.messageView.properties).length > 0 && (
<Descriptions title={<Text strong>{t.MESSAGE_PROPERTIES}</Text>} bordered column={1} size="small" style={{ marginBottom: 20 }}> <Descriptions title={<Text strong>{t.MESSAGE_PROPERTIES}</Text>} bordered column={1}
size="small" style={{marginBottom: 20}}>
{Object.entries(messageDetail.messageView.properties).map(([key, value]) => ( {Object.entries(messageDetail.messageView.properties).map(([key, value]) => (
<Descriptions.Item label={key} key={key}><Text copyable>{value}</Text></Descriptions.Item> <Descriptions.Item label={key} key={key}><Text
copyable>{value}</Text></Descriptions.Item>
))} ))}
</Descriptions> </Descriptions>
)} )}
{/* 消息体部分 */} {/* 消息体部分 */}
<Descriptions title={<Text strong>{t.MESSAGE_BODY}</Text>} bordered column={1} size="small" style={{ marginBottom: 20 }}> <Descriptions title={<Text strong>{t.MESSAGE_BODY}</Text>} bordered column={1} size="small"
style={{marginBottom: 20}}>
<Descriptions.Item> <Descriptions.Item>
<Paragraph <Paragraph
copyable copyable
@@ -146,9 +157,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
{messageDetail.messageTrackList && messageDetail.messageTrackList.length > 0 ? ( {messageDetail.messageTrackList && messageDetail.messageTrackList.length > 0 ? (
<> <>
<Text strong>{t.MESSAGE_TRACKING}</Text> <Text strong>{t.MESSAGE_TRACKING}</Text>
<div style={{ marginTop: 10 }}> <div style={{marginTop: 10}}>
{messageDetail.messageTrackList.map((track, index) => ( {messageDetail.messageTrackList.map((track, index) => (
<Descriptions bordered column={1} size="small" key={index} style={{ marginBottom: 15 }}> <Descriptions bordered column={1} size="small" key={index}
style={{marginBottom: 15}}>
<Descriptions.Item label={t.CONSUMER_GROUP}> <Descriptions.Item label={t.CONSUMER_GROUP}>
{track.consumerGroup} {track.consumerGroup}
</Descriptions.Item> </Descriptions.Item>
@@ -165,10 +177,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t.OPERATION}> <Descriptions.Item label={t.OPERATION}>
<Button <Button
icon={<SyncOutlined />} icon={<SyncOutlined/>}
onClick={() => onResendMessage(messageDetail.messageView, track.consumerGroup)} onClick={() => onResendMessage(messageDetail.messageView, track.consumerGroup)}
size="small" size="small"
style={{ marginRight: 8 }} style={{marginRight: 8}}
> >
{t.RESEND_MESSAGE} {t.RESEND_MESSAGE}
</Button> </Button>
@@ -181,7 +193,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
ellipsis={{ ellipsis={{
rows: 2, // 默认显示2行 rows: 2, // 默认显示2行
expandable: true, expandable: true,
symbol: <Text style={{ color: '#1890ff', cursor: 'pointer' }}>{t.READ_MORE}</Text>, // 蓝色展开文本 symbol: <Text style={{
color: '#1890ff',
cursor: 'pointer'
}}>{t.READ_MORE}</Text>, // 蓝色展开文本
}} }}
> >
{track.exceptionDesc} {track.exceptionDesc}
@@ -198,7 +213,8 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
</> </>
) : ( ) : (
// 当 messageDetail 为 null 时,可以显示一个占位符或者不显示内容 // 当 messageDetail 为 null 时,可以显示一个占位符或者不显示内容
!loading && !error && <Paragraph style={{ textAlign: 'center' }}>{t.NO_MESSAGE_DETAIL_AVAILABLE}</Paragraph> !loading && !error &&
<Paragraph style={{textAlign: 'center'}}>{t.NO_MESSAGE_DETAIL_AVAILABLE}</Paragraph>
)} )}
</Spin> </Spin>
</Modal> </Modal>

View File

@@ -15,15 +15,15 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useEffect, useRef } from 'react'; import React, {useEffect, useRef} from 'react';
import { Form, Input, Typography, Collapse, Table, Tag } from 'antd'; import {Collapse, Form, Input, Table, Tag, Typography} from 'antd';
import moment from 'moment'; import moment from 'moment';
import { useLanguage } from '../i18n/LanguageContext'; import {useLanguage} from '../i18n/LanguageContext';
import Paragraph from "antd/es/skeleton/Paragraph"; import Paragraph from "antd/es/skeleton/Paragraph";
import * as echarts from 'echarts'; // Import ECharts import * as echarts from 'echarts'; // Import ECharts
const { Text } = Typography; const {Text} = Typography;
const { Panel } = Collapse; const {Panel} = Collapse;
// Constants for styling and formatting, derived from the example // Constants for styling and formatting, derived from the example
const SUCCESS_COLOR = '#75d874'; const SUCCESS_COLOR = '#75d874';
@@ -36,8 +36,8 @@ const TIME_FORMAT_PATTERN = "YYYY-MM-DD HH:mm:ss.SSS";
const DEFAULT_DISPLAY_DURATION = 10 * 1000; const DEFAULT_DISPLAY_DURATION = 10 * 1000;
const TRANSACTION_CHECK_COST_TIME = 50; // transactionTraceNode do not have costTime, assume it cost 50ms const TRANSACTION_CHECK_COST_TIME = 50; // transactionTraceNode do not have costTime, assume it cost 50ms
const MessageTraceDetailViewDialog = ({ ngDialogData }) => { const MessageTraceDetailViewDialog = ({ngDialogData}) => {
const { t } = useLanguage(); const {t} = useLanguage();
const messageTraceGraphRef = useRef(null); const messageTraceGraphRef = useRef(null);
const producerNode = ngDialogData?.producerNode; const producerNode = ngDialogData?.producerNode;
@@ -125,6 +125,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
} }
return `Cost Time: ${formatCostTimeStr(costTime)}<br/>` return `Cost Time: ${formatCostTimeStr(costTime)}<br/>`
} }
function buildTimeStamp(timestamp) { function buildTimeStamp(timestamp) {
if (timestamp < 0) { if (timestamp < 0) {
return 'N/A'; return 'N/A';
@@ -323,94 +324,181 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
// ... (rest of your existing component code) // ... (rest of your existing component code)
const transactionColumns = [ const transactionColumns = [
{ title: t.TIMESTAMP, dataIndex: 'beginTimestamp', key: 'beginTimestamp', align: 'center', render: (text) => moment(text).format('YYYY-MM-DD HH:mm:ss.SSS') }, {
{ title: t.TRANSACTION_STATE, dataIndex: 'transactionState', key: 'transactionState', align: 'center', render: (text) => <Tag color={text === 'COMMIT_MESSAGE' ? 'green' : (text === 'ROLLBACK_MESSAGE' ? 'red' : 'default')}>{text}</Tag> }, title: t.TIMESTAMP,
{ title: t.FROM_TRANSACTION_CHECK, dataIndex: 'fromTransactionCheck', key: 'fromTransactionCheck', align: 'center', render: (text) => (text ? <Tag color="blue">{t.YES}</Tag> : <Tag color="purple">{t.NO}</Tag>) }, dataIndex: 'beginTimestamp',
{ title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center' }, key: 'beginTimestamp',
{ title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center' }, align: 'center',
render: (text) => moment(text).format('YYYY-MM-DD HH:mm:ss.SSS')
},
{
title: t.TRANSACTION_STATE,
dataIndex: 'transactionState',
key: 'transactionState',
align: 'center',
render: (text) => <Tag
color={text === 'COMMIT_MESSAGE' ? 'green' : (text === 'ROLLBACK_MESSAGE' ? 'red' : 'default')}>{text}</Tag>
},
{
title: t.FROM_TRANSACTION_CHECK,
dataIndex: 'fromTransactionCheck',
key: 'fromTransactionCheck',
align: 'center',
render: (text) => (text ? <Tag color="blue">{t.YES}</Tag> : <Tag color="purple">{t.NO}</Tag>)
},
{title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center'},
{title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center'},
]; ];
const consumeColumns = [ const consumeColumns = [
{ title: t.BEGIN_TIMESTAMP, dataIndex: 'beginTimestamp', key: 'beginTimestamp', align: 'center', render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS') }, {
{ title: t.END_TIMESTAMP, dataIndex: 'endTimestamp', key: 'endTimestamp', align: 'center', render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS') }, title: t.BEGIN_TIMESTAMP,
{ title: t.COST_TIME, dataIndex: 'costTime', key: 'costTime', align: 'center', render: (text) => text < 0 ? 'N/A' : `${text === 0 ? '<1' : text}ms` }, dataIndex: 'beginTimestamp',
{ title: t.STATUS, dataIndex: 'status', key: 'status', align: 'center', render: (text) => <Tag color={text === 'SUCCESS' ? 'green' : (text === 'FAILED' ? 'red' : 'default')}>{text}</Tag> }, key: 'beginTimestamp',
{ title: t.RETRY_TIMES, dataIndex: 'retryTimes', key: 'retryTimes', align: 'center', render: (text) => text < 0 ? 'N/A' : text }, align: 'center',
{ title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center' }, render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS')
{ title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center' }, },
{
title: t.END_TIMESTAMP,
dataIndex: 'endTimestamp',
key: 'endTimestamp',
align: 'center',
render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS')
},
{
title: t.COST_TIME,
dataIndex: 'costTime',
key: 'costTime',
align: 'center',
render: (text) => text < 0 ? 'N/A' : `${text === 0 ? '<1' : text}ms`
},
{
title: t.STATUS,
dataIndex: 'status',
key: 'status',
align: 'center',
render: (text) => <Tag
color={text === 'SUCCESS' ? 'green' : (text === 'FAILED' ? 'red' : 'default')}>{text}</Tag>
},
{
title: t.RETRY_TIMES,
dataIndex: 'retryTimes',
key: 'retryTimes',
align: 'center',
render: (text) => text < 0 ? 'N/A' : text
},
{title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center'},
{title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center'},
]; ];
return ( return (
<div style={{ padding: '20px', backgroundColor: '#f0f2f5' }}> <div style={{padding: '20px', backgroundColor: '#f0f2f5'}}>
<div style={{ marginBottom: '20px', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}> <div style={{
marginBottom: '20px',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'
}}>
<Collapse defaultActiveKey={['messageTraceGraph']} expandIconPosition="right"> <Collapse defaultActiveKey={['messageTraceGraph']} expandIconPosition="right">
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.MESSAGE_TRACE_GRAPH}</Typography.Title>} key="messageTraceGraph"> <Panel header={<Typography.Title level={3} style={{
<div ref={messageTraceGraphRef} style={{ height: 500, width: '100%', backgroundColor: '#fff', padding: '10px' }}> margin: 0,
color: '#333'
}}>{t.MESSAGE_TRACE_GRAPH}</Typography.Title>} key="messageTraceGraph">
<div ref={messageTraceGraphRef}
style={{height: 500, width: '100%', backgroundColor: '#fff', padding: '10px'}}>
{/* ECharts message trace graph will be rendered here */} {/* ECharts message trace graph will be rendered here */}
{(!producerNode && subscriptionNodeList.length === 0) && ( {(!producerNode && subscriptionNodeList.length === 0) && (
<Text type="secondary" style={{ display: 'block', textAlign: 'center', marginTop: '150px' }}>{t.TRACE_GRAPH_PLACEHOLDER}</Text> <Text type="secondary" style={{
display: 'block',
textAlign: 'center',
marginTop: '150px'
}}>{t.TRACE_GRAPH_PLACEHOLDER}</Text>
)} )}
</div> </div>
</Panel> </Panel>
</Collapse> </Collapse>
</div> </div>
<div style={{ marginBottom: '20px', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}> <div style={{
marginBottom: '20px',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'
}}>
<Collapse defaultActiveKey={['sendMessageTrace']} expandIconPosition="right"> <Collapse defaultActiveKey={['sendMessageTrace']} expandIconPosition="right">
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.SEND_MESSAGE_TRACE}</Typography.Title>} key="sendMessageTrace"> <Panel header={<Typography.Title level={3} style={{
margin: 0,
color: '#333'
}}>{t.SEND_MESSAGE_TRACE}</Typography.Title>} key="sendMessageTrace">
{!producerNode ? ( {!producerNode ? (
<Paragraph style={{ padding: '16px', textAlign: 'center', color: '#666' }}>{t.NO_PRODUCER_TRACE_DATA}</Paragraph> <Paragraph style={{
padding: '16px',
textAlign: 'center',
color: '#666'
}}>{t.NO_PRODUCER_TRACE_DATA}</Paragraph>
) : ( ) : (
<div style={{ padding: '16px', backgroundColor: '#fff' }}> <div style={{padding: '16px', backgroundColor: '#fff'}}>
<Typography.Title level={4} style={{ marginBottom: '20px' }}> <Typography.Title level={4} style={{marginBottom: '20px'}}>
{t.SEND_MESSAGE_INFO} : ( {t.MESSAGE_ID} <Text strong copyable>{producerNode.msgId}</Text> ) {t.SEND_MESSAGE_INFO} : ( {t.MESSAGE_ID} <Text strong
copyable>{producerNode.msgId}</Text> )
</Typography.Title> </Typography.Title>
<Form layout="vertical" colon={false}> <Form layout="vertical" colon={false}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '16px' }}> <div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
gap: '16px'
}}>
<Form.Item label={<Text strong>{t.TOPIC}</Text>}> <Form.Item label={<Text strong>{t.TOPIC}</Text>}>
<Input value={producerNode.topic} readOnly /> <Input value={producerNode.topic} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.PRODUCER_GROUP}</Text>}> <Form.Item label={<Text strong>{t.PRODUCER_GROUP}</Text>}>
<Input value={producerNode.groupName} readOnly /> <Input value={producerNode.groupName} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.MESSAGE_KEY}</Text>}> <Form.Item label={<Text strong>{t.MESSAGE_KEY}</Text>}>
<Input value={producerNode.keys} readOnly /> <Input value={producerNode.keys} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.TAG}</Text>}> <Form.Item label={<Text strong>{t.TAG}</Text>}>
<Input value={producerNode.tags} readOnly /> <Input value={producerNode.tags} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.BEGIN_TIMESTAMP}</Text>}> <Form.Item label={<Text strong>{t.BEGIN_TIMESTAMP}</Text>}>
<Input value={moment(producerNode.traceNode.beginTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')} readOnly /> <Input
value={moment(producerNode.traceNode.beginTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')}
readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.END_TIMESTAMP}</Text>}> <Form.Item label={<Text strong>{t.END_TIMESTAMP}</Text>}>
<Input value={moment(producerNode.traceNode.endTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')} readOnly /> <Input
value={moment(producerNode.traceNode.endTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')}
readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.COST_TIME}</Text>}> <Form.Item label={<Text strong>{t.COST_TIME}</Text>}>
<Input value={`${producerNode.traceNode.costTime === 0 ? '<1' : producerNode.traceNode.costTime}ms`} readOnly /> <Input
value={`${producerNode.traceNode.costTime === 0 ? '<1' : producerNode.traceNode.costTime}ms`}
readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.MSG_TYPE}</Text>}> <Form.Item label={<Text strong>{t.MSG_TYPE}</Text>}>
<Input value={producerNode.traceNode.msgType} readOnly /> <Input value={producerNode.traceNode.msgType} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.CLIENT_HOST}</Text>}> <Form.Item label={<Text strong>{t.CLIENT_HOST}</Text>}>
<Input value={producerNode.traceNode.clientHost} readOnly /> <Input value={producerNode.traceNode.clientHost} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.STORE_HOST}</Text>}> <Form.Item label={<Text strong>{t.STORE_HOST}</Text>}>
<Input value={producerNode.traceNode.storeHost} readOnly /> <Input value={producerNode.traceNode.storeHost} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.RETRY_TIMES}</Text>}> <Form.Item label={<Text strong>{t.RETRY_TIMES}</Text>}>
<Input value={producerNode.traceNode.retryTimes} readOnly /> <Input value={producerNode.traceNode.retryTimes} readOnly/>
</Form.Item> </Form.Item>
<Form.Item label={<Text strong>{t.OFFSET_MSG_ID}</Text>}> <Form.Item label={<Text strong>{t.OFFSET_MSG_ID}</Text>}>
<Input value={producerNode.offSetMsgId} readOnly /> <Input value={producerNode.offSetMsgId} readOnly/>
</Form.Item> </Form.Item>
</div> </div>
</Form> </Form>
{producerNode.transactionNodeList && producerNode.transactionNodeList.length > 0 && ( {producerNode.transactionNodeList && producerNode.transactionNodeList.length > 0 && (
<div style={{ marginTop: '30px' }}> <div style={{marginTop: '30px'}}>
<Typography.Title level={4} style={{ marginBottom: '15px' }}>{t.CHECK_TRANSACTION_INFO}:</Typography.Title> <Typography.Title level={4}
style={{marginBottom: '15px'}}>{t.CHECK_TRANSACTION_INFO}:</Typography.Title>
<Table <Table
columns={transactionColumns} columns={transactionColumns}
dataSource={producerNode.transactionNodeList} dataSource={producerNode.transactionNodeList}
@@ -418,7 +506,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
bordered bordered
pagination={false} pagination={false}
size="middle" size="middle"
scroll={{ x: 'max-content' }} scroll={{x: 'max-content'}}
/> />
</div> </div>
)} )}
@@ -428,22 +516,31 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
</Collapse> </Collapse>
</div> </div>
<div style={{ borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}> <div style={{borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'}}>
<Collapse defaultActiveKey={['consumeMessageTrace']} expandIconPosition="right"> <Collapse defaultActiveKey={['consumeMessageTrace']} expandIconPosition="right">
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.CONSUME_MESSAGE_TRACE}</Typography.Title>} key="consumeMessageTrace"> <Panel header={<Typography.Title level={3} style={{
margin: 0,
color: '#333'
}}>{t.CONSUME_MESSAGE_TRACE}</Typography.Title>} key="consumeMessageTrace">
{subscriptionNodeList.length === 0 ? ( {subscriptionNodeList.length === 0 ? (
<Paragraph style={{ padding: '16px', textAlign: 'center', color: '#666' }}>{t.NO_CONSUMER_TRACE_DATA}</Paragraph> <Paragraph style={{
padding: '16px',
textAlign: 'center',
color: '#666'
}}>{t.NO_CONSUMER_TRACE_DATA}</Paragraph>
) : ( ) : (
<div style={{ padding: '16px', backgroundColor: '#fff' }}> <div style={{padding: '16px', backgroundColor: '#fff'}}>
{subscriptionNodeList.map(subscriptionNode => ( {subscriptionNodeList.map(subscriptionNode => (
<Collapse <Collapse
key={subscriptionNode.subscriptionGroup} key={subscriptionNode.subscriptionGroup}
style={{ marginBottom: '10px', border: '1px solid #e0e0e0', borderRadius: '4px' }} style={{marginBottom: '10px', border: '1px solid #e0e0e0', borderRadius: '4px'}}
defaultActiveKey={[subscriptionNode.subscriptionGroup]} defaultActiveKey={[subscriptionNode.subscriptionGroup]}
ghost ghost
> >
<Panel <Panel
header={<Typography.Title level={4} style={{ margin: 0 }}>{t.SUBSCRIPTION_GROUP}: <Text strong>{subscriptionNode.subscriptionGroup}</Text></Typography.Title>} header={<Typography.Title level={4}
style={{margin: 0}}>{t.SUBSCRIPTION_GROUP}: <Text
strong>{subscriptionNode.subscriptionGroup}</Text></Typography.Title>}
key={subscriptionNode.subscriptionGroup} key={subscriptionNode.subscriptionGroup}
> >
<Table <Table
@@ -453,7 +550,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
bordered bordered
pagination={false} pagination={false}
size="middle" size="middle"
scroll={{ x: 'max-content' }} scroll={{x: 'max-content'}}
/> />
</Panel> </Panel>
</Collapse> </Collapse>

View File

@@ -16,29 +16,29 @@
*/ */
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import { Layout, Menu, Dropdown, Button, Drawer, Grid, Space } from 'antd'; import {Button, Drawer, Dropdown, Grid, Layout, Menu, Space} from 'antd';
import {GlobalOutlined, DownOutlined, UserOutlined, MenuOutlined, BgColorsOutlined} from '@ant-design/icons'; import {BgColorsOutlined, DownOutlined, GlobalOutlined, MenuOutlined, UserOutlined} from '@ant-design/icons';
import { useLocation, useNavigate } from 'react-router-dom'; import {useLocation, useNavigate} from 'react-router-dom';
import { useLanguage } from '../i18n/LanguageContext'; import {useLanguage} from '../i18n/LanguageContext';
import {useTheme} from "../store/context/ThemeContext"; import {useTheme} from "../store/context/ThemeContext";
import {remoteApi} from "../api/remoteApi/remoteApi"; import {remoteApi} from "../api/remoteApi/remoteApi";
const { Header } = Layout; const {Header} = Layout;
const { useBreakpoint } = Grid; // Used to determine screen breakpoints const {useBreakpoint} = Grid; // Used to determine screen breakpoints
const Navbar = ({ rmqVersion = true, showAcl = true}) => { const Navbar = ({rmqVersion = true, showAcl = true}) => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const { lang, setLang, t } = useLanguage(); const {lang, setLang, t} = useLanguage();
const screens = useBreakpoint(); // Get current screen size breakpoints const screens = useBreakpoint(); // Get current screen size breakpoints
const { currentThemeName, setCurrentThemeName } = useTheme(); const {currentThemeName, setCurrentThemeName} = useTheme();
const [userName, setUserName] = useState(null); const [userName, setUserName] = useState(null);
const [drawerVisible, setDrawerVisible] = useState(false); // Controls drawer visibility const [drawerVisible, setDrawerVisible] = useState(false); // Controls drawer visibility
// Get selected menu item key based on current route path // Get selected menu item key based on current route path
const getPath = () => location.pathname.replace('/', ''); const getPath = () => location.pathname.replace('/', '');
const handleMenuClick = ({ key }) => { const handleMenuClick = ({key}) => {
navigate(`/${key}`); navigate(`/${key}`);
setDrawerVisible(false); // Close drawer after clicking a menu item setDrawerVisible(false); // Close drawer after clicking a menu item
}; };
@@ -63,13 +63,13 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
const storedUsername = window.localStorage.getItem("username"); const storedUsername = window.localStorage.getItem("username");
if (storedUsername) { if (storedUsername) {
setUserName(storedUsername); setUserName(storedUsername);
}else { } else {
setUserName(null); setUserName(null);
} }
}, []); }, []);
const langMenu = ( const langMenu = (
<Menu onClick={({ key }) => setLang(key)}> <Menu onClick={({key}) => setLang(key)}>
<Menu.Item key="en">{t.ENGLISH}</Menu.Item> <Menu.Item key="en">{t.ENGLISH}</Menu.Item>
<Menu.Item key="zh">{t.CHINESE}</Menu.Item> <Menu.Item key="zh">{t.CHINESE}</Menu.Item>
</Menu> </Menu>
@@ -82,7 +82,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
); );
const themeMenu = ( const themeMenu = (
<Menu onClick={({ key }) => setCurrentThemeName(key)}> <Menu onClick={({key}) => setCurrentThemeName(key)}>
<Menu.Item key="default">{t.BLUE} ({t.DEFAULT})</Menu.Item> <Menu.Item key="default">{t.BLUE} ({t.DEFAULT})</Menu.Item>
<Menu.Item key="pink">{t.PINK}</Menu.Item> <Menu.Item key="pink">{t.PINK}</Menu.Item>
<Menu.Item key="green">{t.GREEN}</Menu.Item> <Menu.Item key="green">{t.GREEN}</Menu.Item>
@@ -92,17 +92,17 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
// Menu item configuration // Menu item configuration
const menuItems = [ const menuItems = [
{ key: 'ops', label: t.OPS }, {key: 'ops', label: t.OPS},
...(rmqVersion ? [{ key: 'proxy', label: t.PROXY }] : []), ...(rmqVersion ? [{key: 'proxy', label: t.PROXY}] : []),
{ key: '', label: t.DASHBOARD }, // Dashboard corresponds to root path {key: '', label: t.DASHBOARD}, // Dashboard corresponds to root path
{ key: 'cluster', label: t.CLUSTER }, {key: 'cluster', label: t.CLUSTER},
{ key: 'topic', label: t.TOPIC }, {key: 'topic', label: t.TOPIC},
{ key: 'consumer', label: t.CONSUMER }, {key: 'consumer', label: t.CONSUMER},
{ key: 'producer', label: t.PRODUCER }, {key: 'producer', label: t.PRODUCER},
{ key: 'message', label: t.MESSAGE }, {key: 'message', label: t.MESSAGE},
{ key: 'dlqMessage', label: t.DLQ_MESSAGE }, {key: 'dlqMessage', label: t.DLQ_MESSAGE},
{ key: 'messageTrace', label: t.MESSAGETRACE }, {key: 'messageTrace', label: t.MESSAGETRACE},
...(showAcl ? [{ key: 'acl', label: t.WHITE_LIST }] : []), ...(showAcl ? [{key: 'acl', label: t.ACL_MANAGEMENT}] : []),
]; ];
// Determine if it's a small screen (e.g., less than md) // Determine if it's a small screen (e.g., less than md)
@@ -120,7 +120,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
padding: isExtraSmallScreen ? '0 16px' : '0 24px', // Smaller padding on extra small screens padding: isExtraSmallScreen ? '0 16px' : '0 24px', // Smaller padding on extra small screens
}} }}
> >
<div className="navbar-left" style={{ display: 'flex', alignItems: 'center' }}> <div className="navbar-left" style={{display: 'flex', alignItems: 'center'}}>
<div <div
style={{ style={{
fontWeight: 'bold', fontWeight: 'bold',
@@ -141,33 +141,33 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
mode="horizontal" mode="horizontal"
items={menuItems} items={menuItems}
theme="dark" // Use dark theme to match Header background theme="dark" // Use dark theme to match Header background
style={{ flex: 1, minWidth: 0 }} // Allow menu items to adapt width style={{flex: 1, minWidth: 0}} // Allow menu items to adapt width
/> />
)} )}
</div> </div>
<Space size={isExtraSmallScreen ? 8 : 16} > {/* Adjust spacing for buttons */} <Space size={isExtraSmallScreen ? 8 : 16}> {/* Adjust spacing for buttons */}
{/* Theme switch button */} {/* Theme switch button */}
<Dropdown overlay={themeMenu}> <Dropdown overlay={themeMenu}>
<Button icon={<BgColorsOutlined />} size="small"> <Button icon={<BgColorsOutlined/>} size="small">
{!isExtraSmallScreen && `${t.TOPIC}: ${currentThemeName}`} {!isExtraSmallScreen && `${t.TOPIC}: ${currentThemeName}`}
<DownOutlined /> <DownOutlined/>
</Button> </Button>
</Dropdown> </Dropdown>
<Dropdown overlay={langMenu}> <Dropdown overlay={langMenu}>
<Button icon={<GlobalOutlined />} size="small"> <Button icon={<GlobalOutlined/>} size="small">
{!isExtraSmallScreen && t.CHANGE_LANG} {/* Hide text on extra small screens */} {!isExtraSmallScreen && t.CHANGE_LANG} {/* Hide text on extra small screens */}
<DownOutlined /> <DownOutlined/>
</Button> </Button>
</Dropdown> </Dropdown>
{userName && ( {userName && (
<Dropdown overlay={userMenu}> <Dropdown overlay={userMenu}>
{/* 使用一个可点击的元素作为 Dropdown 的唯一子元素 */} {/* 使用一个可点击的元素作为 Dropdown 的唯一子元素 */}
<a onClick={e => e.preventDefault()} style={{ display: 'flex', alignItems: 'center' }}> <a onClick={e => e.preventDefault()} style={{display: 'flex', alignItems: 'center'}}>
<UserOutlined style={{ marginRight: 8 }} /> {/* 添加一些间距 */} <UserOutlined style={{marginRight: 8}}/> {/* 添加一些间距 */}
{userName} {userName}
<DownOutlined style={{ marginLeft: 8 }} /> <DownOutlined style={{marginLeft: 8}}/>
</a> </a>
</Dropdown> </Dropdown>
)} )}
@@ -175,9 +175,9 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
{isSmallScreen && ( // Display hamburger icon on small screens {isSmallScreen && ( // Display hamburger icon on small screens
<Button <Button
type="primary" type="primary"
icon={<MenuOutlined />} icon={<MenuOutlined/>}
onClick={() => setDrawerVisible(true)} onClick={() => setDrawerVisible(true)}
style={{ marginLeft: isExtraSmallScreen ? 8 : 16 }} // Adjust margin for hamburger icon style={{marginLeft: isExtraSmallScreen ? 8 : 16}} // Adjust margin for hamburger icon
/> />
)} )}
</Space> </Space>
@@ -192,7 +192,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
open={drawerVisible} open={drawerVisible}
// If you want the Drawer's background to match the Menu's background color, you can set bodyStyle like this // If you want the Drawer's background to match the Menu's background color, you can set bodyStyle like this
// or set components.Drawer.colorBgElevated in theme.js, etc. // or set components.Drawer.colorBgElevated in theme.js, etc.
bodyStyle={{ padding: 0, backgroundColor: '#1c324a' }} // Set Drawer body background to dark bodyStyle={{padding: 0, backgroundColor: '#1c324a'}} // Set Drawer body background to dark
width={200} // Set drawer width width={200} // Set drawer width
> >
<Menu <Menu
@@ -201,7 +201,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
mode="inline" // Use vertical menu in drawer mode="inline" // Use vertical menu in drawer
items={menuItems} items={menuItems}
theme="dark" theme="dark"
style={{ height: '100%', borderRight: 0 }} // Ensure menu fills the drawer style={{height: '100%', borderRight: 0}} // Ensure menu fills the drawer
/> />
</Drawer> </Drawer>
</Header> </Header>

View File

@@ -15,23 +15,23 @@
* limitations under the License. * limitations under the License.
*/ */
import { Input, Select, Tag, Space } from 'antd'; import {Input, Select, Space, Tag} from 'antd';
import { PlusOutlined } from '@ant-design/icons'; import {PlusOutlined} from '@ant-design/icons';
import React, { useState } from 'react'; import React, {useState} from 'react';
const { Option } = Select; const {Option} = Select;
// 资源类型枚举 // 资源类型枚举
const resourceTypes = [ const resourceTypes = [
{ value: 0, label: 'Unknown', prefix: 'UNKNOWN' }, {value: 0, label: 'Unknown', prefix: 'UNKNOWN'},
{ value: 1, label: 'Any', prefix: 'ANY' }, {value: 1, label: 'Any', prefix: 'ANY'},
{ value: 2, label: 'Cluster', prefix: 'CLUSTER' }, {value: 2, label: 'Cluster', prefix: 'CLUSTER'},
{ value: 3, label: 'Namespace', prefix: 'NAMESPACE' }, {value: 3, label: 'Namespace', prefix: 'NAMESPACE'},
{ value: 4, label: 'Topic', prefix: 'TOPIC' }, {value: 4, label: 'Topic', prefix: 'TOPIC'},
{ value: 5, label: 'Group', prefix: 'GROUP' }, {value: 5, label: 'Group', prefix: 'GROUP'},
]; ];
const ResourceInput = ({ value = [], onChange }) => { const ResourceInput = ({value = [], onChange}) => {
// 确保 value 始终是数组 // 确保 value 始终是数组
const safeValue = Array.isArray(value) ? value : []; const safeValue = Array.isArray(value) ? value : [];
@@ -96,7 +96,7 @@ const ResourceInput = ({ value = [], onChange }) => {
<Space> <Space>
<Select <Select
value={selectedType} value={selectedType}
style={{ width: 120 }} style={{width: 120}}
onChange={handleTypeChange} onChange={handleTypeChange}
> >
{resourceTypes.map(type => ( {resourceTypes.map(type => (
@@ -107,7 +107,7 @@ const ResourceInput = ({ value = [], onChange }) => {
</Select> </Select>
<Input <Input
ref={inputRef} ref={inputRef}
style={{ width: 180 }} style={{width: 180}}
value={resourceName} value={resourceName}
onChange={handleNameChange} onChange={handleNameChange}
onPressEnter={handleAddResource} onPressEnter={handleAddResource}
@@ -116,8 +116,8 @@ const ResourceInput = ({ value = [], onChange }) => {
/> />
</Space> </Space>
) : ( ) : (
<Tag onClick={showInput} style={{ background: '#fff', borderStyle: 'dashed' }}> <Tag onClick={showInput} style={{background: '#fff', borderStyle: 'dashed'}}>
<PlusOutlined /> 添加资源 <PlusOutlined/> 添加资源
</Tag> </Tag>
)} )}
</Space> </Space>

View File

@@ -15,27 +15,27 @@
* limitations under the License. * limitations under the License.
*/ */
import { Input, Select } from 'antd'; import {Input, Select} from 'antd';
import React, { useState, useEffect } from 'react'; import React, {useEffect, useState} from 'react';
const { Option } = Select; const {Option} = Select;
// Subject 类型枚举 // Subject 类型枚举
const subjectTypes = [ const subjectTypes = [
{ value: 'User', label: 'User' }, {value: 'User', label: 'User'},
]; ];
const SubjectInput = ({ value, onChange, disabled }) => { const SubjectInput = ({value, onChange, disabled}) => {
// 解析传入的 value将其拆分为 type 和 name // 解析传入的 value将其拆分为 type 和 name
const parseValue = (val) => { const parseValue = (val) => {
if (!val || typeof val !== 'string') { if (!val || typeof val !== 'string') {
return { type: subjectTypes[0].value, name: '' }; // 默认值 return {type: subjectTypes[0].value, name: ''}; // 默认值
} }
const parts = val.split(':'); const parts = val.split(':');
if (parts.length === 2 && subjectTypes.some(t => t.value === parts[0])) { if (parts.length === 2 && subjectTypes.some(t => t.value === parts[0])) {
return { type: parts[0], name: parts[1] }; return {type: parts[0], name: parts[1]};
} }
return { type: subjectTypes[0].value, name: val }; // 如果格式不匹配,将整个值作为 name类型设为默认 return {type: subjectTypes[0].value, name: val}; // 如果格式不匹配,将整个值作为 name类型设为默认
}; };
const [currentType, setCurrentType] = useState(() => parseValue(value).type); const [currentType, setCurrentType] = useState(() => parseValue(value).type);
@@ -76,7 +76,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
return ( return (
<Input.Group compact> <Input.Group compact>
<Select <Select
style={{ width: '30%' }} style={{width: '30%'}}
value={currentType} value={currentType}
onChange={onTypeChange} onChange={onTypeChange}
disabled={disabled} disabled={disabled}
@@ -88,7 +88,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
))} ))}
</Select> </Select>
<Input <Input
style={{ width: '70%' }} style={{width: '70%'}}
value={currentName} value={currentName}
onChange={onNameChange} onChange={onNameChange}
placeholder="请输入名称 (例如: yourUsername)" placeholder="请输入名称 (例如: yourUsername)"

View File

@@ -15,13 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useState, useEffect } from 'react'; import React, {useEffect, useState} from 'react';
import { Modal, Table, Spin } from 'antd'; import {Modal, Spin, Table} from 'antd';
import { remoteApi } from '../../api/remoteApi/remoteApi'; import {remoteApi} from '../../api/remoteApi/remoteApi';
import { useLanguage } from '../../i18n/LanguageContext'; import {useLanguage} from '../../i18n/LanguageContext';
const ClientInfoModal = ({ visible, group, address, onCancel }) => { const ClientInfoModal = ({visible, group, address, onCancel}) => {
const { t } = useLanguage(); const {t} = useLanguage();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [connectionData, setConnectionData] = useState(null); const [connectionData, setConnectionData] = useState(null);
const [subscriptionData, setSubscriptionData] = useState(null); const [subscriptionData, setSubscriptionData] = useState(null);
@@ -46,15 +46,15 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
}, [visible, group, address]); }, [visible, group, address]);
const connectionColumns = [ const connectionColumns = [
{ title: 'ClientId', dataIndex: 'clientId' }, {title: 'ClientId', dataIndex: 'clientId'},
{ title: 'ClientAddr', dataIndex: 'clientAddr' }, {title: 'ClientAddr', dataIndex: 'clientAddr'},
{ title: 'Language', dataIndex: 'language' }, {title: 'Language', dataIndex: 'language'},
{ title: 'Version', dataIndex: 'versionDesc' }, {title: 'Version', dataIndex: 'versionDesc'},
]; ];
const subscriptionColumns = [ const subscriptionColumns = [
{ title: 'Topic', dataIndex: 'topic' }, {title: 'Topic', dataIndex: 'topic'},
{ title: 'SubExpression', dataIndex: 'subString' }, {title: 'SubExpression', dataIndex: 'subString'},
]; ];
return ( return (
@@ -88,7 +88,7 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
rowKey="topic" rowKey="topic"
pagination={false} pagination={false}
locale={{ locale={{
emptyText: loading ? <Spin size="small" /> : t.NO_DATA emptyText: loading ? <Spin size="small"/> : t.NO_DATA
}} }}
/> />
<p>ConsumeType: {connectionData.consumeType}</p> <p>ConsumeType: {connectionData.consumeType}</p>

View File

@@ -15,13 +15,23 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { Button, Descriptions, Form, Input, Select, Switch, message } from 'antd'; import {Button, Descriptions, Form, Input, message, Select, Switch} from 'antd';
import { remoteApi } from '../../api/remoteApi/remoteApi'; // 确保路径正确 import {remoteApi} from '../../api/remoteApi/remoteApi'; // 确保路径正确
const { Option } = Select; const {Option} = Select;
const ConsumerConfigItem = ({ initialConfig, isAddConfig, group, brokerName, allBrokerList, allClusterNames,onCancel, onSuccess, t }) => { const ConsumerConfigItem = ({
initialConfig,
isAddConfig,
group,
brokerName,
allBrokerList,
allClusterNames,
onCancel,
onSuccess,
t
}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [currentBrokerName, setCurrentBrokerName] = useState(brokerName); const [currentBrokerName, setCurrentBrokerName] = useState(brokerName);

View File

@@ -34,7 +34,7 @@ const ConsumerConfigModal = ({visible, isAddConfig, group, onCancel, setIsAddCon
setLoading(true); setLoading(true);
try { try {
// Fetch cluster list for broker names and cluster names // Fetch cluster list for broker names and cluster names
if(isAddConfig) { if (isAddConfig) {
const clusterResponse = await remoteApi.getClusterList(); const clusterResponse = await remoteApi.getClusterList();
if (clusterResponse.status === 0 && clusterResponse.data) { if (clusterResponse.status === 0 && clusterResponse.data) {
const clusterInfo = clusterResponse.data.clusterInfo; const clusterInfo = clusterResponse.data.clusterInfo;

View File

@@ -15,13 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useState, useEffect } from 'react'; import React, {useEffect, useState} from 'react';
import { Modal, Table, Spin } from 'antd'; import {Modal, Spin, Table} from 'antd';
import { remoteApi } from '../../api/remoteApi/remoteApi'; import {remoteApi} from '../../api/remoteApi/remoteApi';
import { useLanguage } from '../../i18n/LanguageContext'; import {useLanguage} from '../../i18n/LanguageContext';
const ConsumerDetailModal = ({ visible, group, address, onCancel }) => { const ConsumerDetailModal = ({visible, group, address, onCancel}) => {
const { t } = useLanguage(); const {t} = useLanguage();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [details, setDetails] = useState([]); const [details, setDetails] = useState([]);
@@ -44,12 +44,12 @@ const ConsumerDetailModal = ({ visible, group, address, onCancel }) => {
}, [visible, group, address]); }, [visible, group, address]);
const queueColumns = [ const queueColumns = [
{ title: 'Broker', dataIndex: 'brokerName' }, {title: 'Broker', dataIndex: 'brokerName'},
{ title: 'Queue', dataIndex: 'queueId' }, {title: 'Queue', dataIndex: 'queueId'},
{ title: 'BrokerOffset', dataIndex: 'brokerOffset' }, {title: 'BrokerOffset', dataIndex: 'brokerOffset'},
{ title: 'ConsumerOffset', dataIndex: 'consumerOffset' }, {title: 'ConsumerOffset', dataIndex: 'consumerOffset'},
{ title: 'DiffTotal', dataIndex: 'diffTotal' }, {title: 'DiffTotal', dataIndex: 'diffTotal'},
{ title: 'LastTimestamp', dataIndex: 'lastTimestamp' }, {title: 'LastTimestamp', dataIndex: 'lastTimestamp'},
]; ];
return ( return (

View File

@@ -15,13 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useState, useEffect } from 'react'; import React, {useEffect, useState} from 'react';
import { Modal, Spin, Checkbox, Button, notification } from 'antd'; import {Button, Checkbox, Modal, notification, Spin} from 'antd';
import { remoteApi } from '../../api/remoteApi/remoteApi'; import {remoteApi} from '../../api/remoteApi/remoteApi';
import { useLanguage } from '../../i18n/LanguageContext'; import {useLanguage} from '../../i18n/LanguageContext';
const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => { const DeleteConsumerModal = ({visible, group, onCancel, onSuccess}) => {
const { t } = useLanguage(); const {t} = useLanguage();
const [brokerList, setBrokerList] = useState([]); const [brokerList, setBrokerList] = useState([]);
const [selectedBrokers, setSelectedBrokers] = useState([]); const [selectedBrokers, setSelectedBrokers] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -48,7 +48,7 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
// 处理删除提交 // 处理删除提交
const handleDelete = async () => { const handleDelete = async () => {
if (selectedBrokers.length === 0) { if (selectedBrokers.length === 0) {
notification.warning({ message: t.PLEASE_SELECT_BROKER }); notification.warning({message: t.PLEASE_SELECT_BROKER});
return; return;
} }
@@ -60,7 +60,7 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
); );
if (response.status === 0) { if (response.status === 0) {
notification.success({ message: t.DELETE_SUCCESS }); notification.success({message: t.DELETE_SUCCESS});
onSuccess(); onSuccess();
onCancel(); onCancel();
} }
@@ -90,9 +90,9 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
]} ]}
> >
<Spin spinning={loading}> <Spin spinning={loading}>
<div style={{ marginBottom: 16 }}>{t.SELECT_DELETE_BROKERS}:</div> <div style={{marginBottom: 16}}>{t.SELECT_DELETE_BROKERS}:</div>
<Checkbox.Group <Checkbox.Group
style={{ width: '100%' }} style={{width: '100%'}}
value={selectedBrokers} value={selectedBrokers}
onChange={values => setSelectedBrokers(values)} onChange={values => setSelectedBrokers(values)}
> >

View File

@@ -15,10 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, DatePicker, Form, Modal, Select } from "antd"; import {Button, DatePicker, Form, Modal, Select} from "antd";
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from "react";
const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupList, handleResetOffset, t }) => { const ConsumerResetOffsetDialog = ({visible, onClose, topic, allConsumerGroupList, handleResetOffset, t}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]); const [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]);
const [selectedTime, setSelectedTime] = useState(null); const [selectedTime, setSelectedTime] = useState(null);
@@ -49,14 +49,14 @@ const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupLi
</Button>, </Button>,
]} ]}
> >
<Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> <Form form={form} layout="horizontal" labelCol={{span: 6}} wrapperCol={{span: 18}}>
<Form.Item label={t.SUBSCRIPTION_GROUP} required> <Form.Item label={t.SUBSCRIPTION_GROUP} required>
<Select <Select
mode="multiple" mode="multiple"
placeholder={t.SELECT_CONSUMER_GROUP} placeholder={t.SELECT_CONSUMER_GROUP}
value={selectedConsumerGroup} value={selectedConsumerGroup}
onChange={setSelectedConsumerGroup} onChange={setSelectedConsumerGroup}
options={allConsumerGroupList.map(group => ({ value: group, label: group }))} options={allConsumerGroupList.map(group => ({value: group, label: group}))}
/> />
</Form.Item> </Form.Item>
<Form.Item label={t.TIME} required> <Form.Item label={t.TIME} required>
@@ -65,7 +65,7 @@ const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupLi
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
value={selectedTime} value={selectedTime}
onChange={setSelectedTime} onChange={setSelectedTime}
style={{ width: '100%' }} style={{width: '100%'}}
/> />
</Form.Item> </Form.Item>
</Form> </Form>

View File

@@ -19,13 +19,13 @@ import moment from "moment/moment";
import {Button, Modal, Table} from "antd"; import {Button, Modal, Table} from "antd";
import React from "react"; import React from "react";
const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGroupCount, t }) => { const ConsumerViewDialog = ({visible, onClose, topic, consumerData, consumerGroupCount, t}) => {
const columns = [ const columns = [
{ title: t.BROKER, dataIndex: 'brokerName', key: 'brokerName', align: 'center' }, {title: t.BROKER, dataIndex: 'brokerName', key: 'brokerName', align: 'center'},
{ title: t.QUEUE, dataIndex: 'queueId', key: 'queueId', align: 'center' }, {title: t.QUEUE, dataIndex: 'queueId', key: 'queueId', align: 'center'},
{ title: t.CONSUMER_CLIENT, dataIndex: 'clientInfo', key: 'clientInfo', align: 'center' }, {title: t.CONSUMER_CLIENT, dataIndex: 'clientInfo', key: 'clientInfo', align: 'center'},
{ title: t.BROKER_OFFSET, dataIndex: 'brokerOffset', key: 'brokerOffset', align: 'center' }, {title: t.BROKER_OFFSET, dataIndex: 'brokerOffset', key: 'brokerOffset', align: 'center'},
{ title: t.CONSUMER_OFFSET, dataIndex: 'consumerOffset', key: 'consumerOffset', align: 'center' }, {title: t.CONSUMER_OFFSET, dataIndex: 'consumerOffset', key: 'consumerOffset', align: 'center'},
{ {
title: t.DIFF_TOTAL, title: t.DIFF_TOTAL,
dataIndex: 'diffTotal', dataIndex: 'diffTotal',
@@ -58,15 +58,19 @@ const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGro
<div>{t.NO_DATA} {t.SUBSCRIPTION_GROUP}</div> <div>{t.NO_DATA} {t.SUBSCRIPTION_GROUP}</div>
) : ( ) : (
consumerData && Object.entries(consumerData).map(([consumerGroup, consumeDetail]) => ( consumerData && Object.entries(consumerData).map(([consumerGroup, consumeDetail]) => (
<div key={consumerGroup} style={{ marginBottom: '24px' }}> <div key={consumerGroup} style={{marginBottom: '24px'}}>
<Table <Table
bordered bordered
pagination={false} pagination={false}
showHeader={false} showHeader={false}
dataSource={[{ consumerGroup, diffTotal: consumeDetail.diffTotal, lastTimestamp: consumeDetail.lastTimestamp }]} dataSource={[{
consumerGroup,
diffTotal: consumeDetail.diffTotal,
lastTimestamp: consumeDetail.lastTimestamp
}]}
columns={[ columns={[
{ title: t.SUBSCRIPTION_GROUP, dataIndex: 'consumerGroup', key: 'consumerGroup' }, {title: t.SUBSCRIPTION_GROUP, dataIndex: 'consumerGroup', key: 'consumerGroup'},
{ title: t.DELAY, dataIndex: 'diffTotal', key: 'diffTotal' }, {title: t.DELAY, dataIndex: 'diffTotal', key: 'diffTotal'},
{ {
title: t.LAST_CONSUME_TIME, title: t.LAST_CONSUME_TIME,
dataIndex: 'lastTimestamp', dataIndex: 'lastTimestamp',
@@ -76,7 +80,7 @@ const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGro
]} ]}
rowKey="consumerGroup" rowKey="consumerGroup"
size="small" size="small"
style={{ marginBottom: '12px' }} style={{marginBottom: '12px'}}
/> />
<Table <Table
bordered bordered

View File

@@ -18,7 +18,7 @@
import {Button, Modal, Table} from "antd"; import {Button, Modal, Table} from "antd";
import React from "react"; import React from "react";
const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => { const ResetOffsetResultDialog = ({visible, onClose, result, t}) => {
return ( return (
<Modal <Modal
title="ResetResult" title="ResetResult"
@@ -31,12 +31,12 @@ const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
]} ]}
> >
{result && Object.entries(result).map(([groupName, groupData]) => ( {result && Object.entries(result).map(([groupName, groupData]) => (
<div key={groupName} style={{ marginBottom: '16px', border: '1px solid #f0f0f0', padding: '10px' }}> <div key={groupName} style={{marginBottom: '16px', border: '1px solid #f0f0f0', padding: '10px'}}>
<Table <Table
dataSource={[{ groupName, status: groupData.status }]} dataSource={[{groupName, status: groupData.status}]}
columns={[ columns={[
{ title: 'GroupName', dataIndex: 'groupName', key: 'groupName' }, {title: 'GroupName', dataIndex: 'groupName', key: 'groupName'},
{ title: 'State', dataIndex: 'status', key: 'status' }, {title: 'State', dataIndex: 'status', key: 'status'},
]} ]}
pagination={false} pagination={false}
rowKey="groupName" rowKey="groupName"
@@ -47,8 +47,8 @@ const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
<div>You Should Check It Yourself</div> <div>You Should Check It Yourself</div>
) : ( ) : (
<Table <Table
dataSource={groupData.rollbackStatsList.map((item, index) => ({ key: index, item }))} dataSource={groupData.rollbackStatsList.map((item, index) => ({key: index, item}))}
columns={[{ dataIndex: 'item', key: 'item' }]} columns={[{dataIndex: 'item', key: 'item'}]}
pagination={false} pagination={false}
rowKey="key" rowKey="key"
size="small" size="small"

View File

@@ -15,10 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Modal, Table } from "antd"; import {Button, Modal, Table} from "antd";
import React from "react"; import React from "react";
const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => { const RouterViewDialog = ({visible, onClose, topic, routeData, t}) => {
const brokerColumns = [ const brokerColumns = [
{ {
title: 'Broker', title: 'Broker',
@@ -30,10 +30,14 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
key: 'brokerAddrs', key: 'brokerAddrs',
render: (_, record) => ( render: (_, record) => (
<Table <Table
dataSource={Object.entries(record.brokerAddrs || []).map(([key, value]) => ({ key, idx: key, address: value }))} dataSource={Object.entries(record.brokerAddrs || []).map(([key, value]) => ({
key,
idx: key,
address: value
}))}
columns={[ columns={[
{ title: 'Index', dataIndex: 'idx', key: 'idx' }, {title: 'Index', dataIndex: 'idx', key: 'idx'},
{ title: 'Address', dataIndex: 'address', key: 'address' }, {title: 'Address', dataIndex: 'address', key: 'address'},
]} ]}
pagination={false} pagination={false}
bordered bordered
@@ -82,7 +86,7 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
<div> <div>
<h3>Broker Datas:</h3> <h3>Broker Datas:</h3>
{routeData?.brokerDatas?.map((item, index) => ( {routeData?.brokerDatas?.map((item, index) => (
<div key={index} style={{ marginBottom: '15px', border: '1px solid #d9d9d9', padding: '10px' }}> <div key={index} style={{marginBottom: '15px', border: '1px solid #d9d9d9', padding: '10px'}}>
<Table <Table
dataSource={[item]} dataSource={[item]}
columns={brokerColumns} columns={brokerColumns}
@@ -93,7 +97,7 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
</div> </div>
))} ))}
</div> </div>
<div style={{ marginTop: '20px' }}> <div style={{marginTop: '20px'}}>
<h3>{t.QUEUE_DATAS}:</h3> <h3>{t.QUEUE_DATAS}:</h3>
<Table <Table
dataSource={routeData?.queueDatas || []} dataSource={routeData?.queueDatas || []}

View File

@@ -18,7 +18,7 @@
import {Button, Form, Modal, Table} from "antd"; import {Button, Form, Modal, Table} from "antd";
import React from "react"; import React from "react";
const SendResultDialog = ({ visible, onClose, result, t }) => { const SendResultDialog = ({visible, onClose, result, t}) => {
return ( return (
<Modal <Modal
title="SendResult" title="SendResult"
@@ -43,11 +43,11 @@ const SendResultDialog = ({ visible, onClose, result, t }) => {
: [] : []
} }
columns={[ columns={[
{ dataIndex: 'label', key: 'label' }, {dataIndex: 'label', key: 'label'},
{ {
dataIndex: 'value', dataIndex: 'value',
key: 'value', key: 'value',
render: (text) => <pre style={{ whiteSpace: 'pre-wrap', margin: 0 }}>{text}</pre>, render: (text) => <pre style={{whiteSpace: 'pre-wrap', margin: 0}}>{text}</pre>,
}, },
]} ]}
pagination={false} pagination={false}
@@ -61,5 +61,4 @@ const SendResultDialog = ({ visible, onClose, result, t }) => {
}; };
export default SendResultDialog; export default SendResultDialog;

View File

@@ -76,24 +76,24 @@ const SendTopicMessageDialog = ({
</Button>, </Button>,
]} ]}
> >
<Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> <Form form={form} layout="horizontal" labelCol={{span: 6}} wrapperCol={{span: 18}}>
<Form.Item label={t.TOPIC} name="topic"> <Form.Item label={t.TOPIC} name="topic">
<Input disabled /> <Input disabled/>
</Form.Item> </Form.Item>
<Form.Item label={t.TAG} name="tag"> <Form.Item label={t.TAG} name="tag">
<Input /> <Input/>
</Form.Item> </Form.Item>
<Form.Item label={t.KEY} name="key"> <Form.Item label={t.KEY} name="key">
<Input /> <Input/>
</Form.Item> </Form.Item>
<Form.Item label={t.MESSAGE_BODY} name="messageBody" rules={[{ required: true, message: t.REQUIRED }]}> <Form.Item label={t.MESSAGE_BODY} name="messageBody" rules={[{required: true, message: t.REQUIRED}]}>
<Input.TextArea <Input.TextArea
style={{ maxHeight: '200px', minHeight: '200px', resize: 'none' }} style={{maxHeight: '200px', minHeight: '200px', resize: 'none'}}
rows={8} rows={8}
/> />
</Form.Item> </Form.Item>
<Form.Item label={t.ENABLE_MESSAGE_TRACE} name="traceEnabled" valuePropName="checked"> <Form.Item label={t.ENABLE_MESSAGE_TRACE} name="traceEnabled" valuePropName="checked">
<Checkbox /> <Checkbox/>
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>

View File

@@ -15,10 +15,17 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Form, message, Modal, Select } from "antd"; import {Button, Form, message, Modal, Select} from "antd";
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from "react";
const SkipMessageAccumulateDialog = ({ visible, onClose, topic, allConsumerGroupList, handleSkipMessageAccumulate, t }) => { const SkipMessageAccumulateDialog = ({
visible,
onClose,
topic,
allConsumerGroupList,
handleSkipMessageAccumulate,
t
}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]); const [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]);
@@ -52,14 +59,14 @@ const SkipMessageAccumulateDialog = ({ visible, onClose, topic, allConsumerGroup
</Button>, </Button>,
]} ]}
> >
<Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> <Form form={form} layout="horizontal" labelCol={{span: 6}} wrapperCol={{span: 18}}>
<Form.Item label={t.SUBSCRIPTION_GROUP} required> <Form.Item label={t.SUBSCRIPTION_GROUP} required>
<Select <Select
mode="multiple" mode="multiple"
placeholder={t.SELECT_CONSUMER_GROUP} placeholder={t.SELECT_CONSUMER_GROUP}
value={selectedConsumerGroup} value={selectedConsumerGroup}
onChange={setSelectedConsumerGroup} onChange={setSelectedConsumerGroup}
options={allConsumerGroupList.map(group => ({ value: group, label: group }))} options={allConsumerGroupList.map(group => ({value: group, label: group}))}
/> />
</Form.Item> </Form.Item>
</Form> </Form>

View File

@@ -19,11 +19,11 @@ import moment from "moment/moment";
import {Button, Modal, Table} from "antd"; import {Button, Modal, Table} from "antd";
import React from "react"; import React from "react";
const StatsViewDialog = ({ visible, onClose, topic, statsData, t }) => { const StatsViewDialog = ({visible, onClose, topic, statsData, t}) => {
const columns = [ const columns = [
{ title: t.QUEUE, dataIndex: 'queue', key: 'queue', align: 'center' }, {title: t.QUEUE, dataIndex: 'queue', key: 'queue', align: 'center'},
{ title: t.MIN_OFFSET, dataIndex: 'minOffset', key: 'minOffset', align: 'center' }, {title: t.MIN_OFFSET, dataIndex: 'minOffset', key: 'minOffset', align: 'center'},
{ title: t.MAX_OFFSET, dataIndex: 'maxOffset', key: 'maxOffset', align: 'center' }, {title: t.MAX_OFFSET, dataIndex: 'maxOffset', key: 'maxOffset', align: 'center'},
{ {
title: t.LAST_UPDATE_TIME_STAMP, title: t.LAST_UPDATE_TIME_STAMP,
dataIndex: 'lastUpdateTimestamp', dataIndex: 'lastUpdateTimestamp',

View File

@@ -16,7 +16,7 @@
*/ */
// TopicModifyDialog.js // TopicModifyDialog.js
import { Button, Modal } from "antd"; import {Button, Modal} from "antd";
import React from "react"; import React from "react";
import TopicSingleModifyForm from './TopicSingleModifyForm'; import TopicSingleModifyForm from './TopicSingleModifyForm';
@@ -43,7 +43,7 @@ const TopicModifyDialog = ({
{t.CLOSE} {t.CLOSE}
</Button>, </Button>,
]} ]}
Style={{ maxHeight: '70vh', overflowY: 'auto' }} Style={{maxHeight: '70vh', overflowY: 'auto'}}
> >
{initialData.map((data, index) => ( {initialData.map((data, index) => (
<TopicSingleModifyForm <TopicSingleModifyForm

View File

@@ -16,8 +16,8 @@
*/ */
// TopicSingleModifyForm.js // TopicSingleModifyForm.js
import React, { useEffect } from "react"; import React, {useEffect} from "react";
import {Button, Form, Input, Select, Divider, Row, Col} from "antd"; import {Button, Col, Divider, Form, Input, Row, Select} from "antd";
const TopicSingleModifyForm = ({ const TopicSingleModifyForm = ({
initialData, initialData,
@@ -42,9 +42,9 @@ const TopicSingleModifyForm = ({
const handleFormSubmit = () => { const handleFormSubmit = () => {
form.validateFields() form.validateFields()
.then(values => { .then(values => {
const updatedValues = { ...values }; const updatedValues = {...values};
// 提交时,如果 clusterNameList 或 brokerNameList 为空,则填充所有可用的名称 // 提交时,如果 clusterNameList 或 brokerNameList 为空,则填充所有可用的名称
if(!bIsUpdate){ if (!bIsUpdate) {
if (!updatedValues.clusterNameList || updatedValues.clusterNameList.length === 0) { if (!updatedValues.clusterNameList || updatedValues.clusterNameList.length === 0) {
updatedValues.clusterNameList = allClusterNameList; updatedValues.clusterNameList = allClusterNameList;
} }
@@ -60,29 +60,30 @@ const TopicSingleModifyForm = ({
}; };
const messageTypeOptions = [ const messageTypeOptions = [
{ value: 'TRANSACTION', label: 'TRANSACTION' }, {value: 'TRANSACTION', label: 'TRANSACTION'},
{ value: 'FIFO', label: 'FIFO' }, {value: 'FIFO', label: 'FIFO'},
{ value: 'DELAY', label: 'DELAY' }, {value: 'DELAY', label: 'DELAY'},
{ value: 'NORMAL', label: 'NORMAL' }, {value: 'NORMAL', label: 'NORMAL'},
]; ];
return ( return (
<div style={{ paddingBottom: 24 }}> <div style={{paddingBottom: 24}}>
{bIsUpdate && <Divider orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>} {bIsUpdate && <Divider
orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>}
<Row justify="center"> {/* 使用 Row 居中内容 */} <Row justify="center"> {/* 使用 Row 居中内容 */}
<Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */} <Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */}
<Form <Form
form={form} form={form}
layout="horizontal" layout="horizontal"
labelCol={{ span: 8 }} labelCol={{span: 8}}
wrapperCol={{ span: 16 }} wrapperCol={{span: 16}}
> >
<Form.Item label={t.CLUSTER_NAME} name="clusterNameList"> <Form.Item label={t.CLUSTER_NAME} name="clusterNameList">
<Select <Select
mode="multiple" mode="multiple"
disabled={bIsUpdate} disabled={bIsUpdate}
placeholder={t.SELECT_CLUSTER_NAME} placeholder={t.SELECT_CLUSTER_NAME}
options={allClusterNameList.map(name => ({ value: name, label: name }))} options={allClusterNameList.map(name => ({value: name, label: name}))}
/> />
</Form.Item> </Form.Item>
<Form.Item label="BROKER_NAME" name="brokerNameList"> <Form.Item label="BROKER_NAME" name="brokerNameList">
@@ -90,15 +91,15 @@ const TopicSingleModifyForm = ({
mode="multiple" mode="multiple"
disabled={bIsUpdate} disabled={bIsUpdate}
placeholder={t.SELECT_BROKER_NAME} placeholder={t.SELECT_BROKER_NAME}
options={allBrokerNameList.map(name => ({ value: name, label: name }))} options={allBrokerNameList.map(name => ({value: name, label: name}))}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t.TOPIC_NAME} label={t.TOPIC_NAME}
name="topicName" name="topicName"
rules={[{ required: true, message: `${t.TOPIC_NAME}${t.CANNOT_BE_EMPTY}` }]} rules={[{required: true, message: `${t.TOPIC_NAME}${t.CANNOT_BE_EMPTY}`}]}
> >
<Input disabled={bIsUpdate} /> <Input disabled={bIsUpdate}/>
</Form.Item> </Form.Item>
<Form.Item label={t.MESSAGE_TYPE} name="messageType"> <Form.Item label={t.MESSAGE_TYPE} name="messageType">
<Select <Select
@@ -109,26 +110,26 @@ const TopicSingleModifyForm = ({
<Form.Item <Form.Item
label={t.WRITE_QUEUE_NUMS} label={t.WRITE_QUEUE_NUMS}
name="writeQueueNums" name="writeQueueNums"
rules={[{ required: true, message: `${t.WRITE_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}` }]} rules={[{required: true, message: `${t.WRITE_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}`}]}
> >
<Input disabled={!writeOperationEnabled} /> <Input disabled={!writeOperationEnabled}/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t.READ_QUEUE_NUMS} label={t.READ_QUEUE_NUMS}
name="readQueueNums" name="readQueueNums"
rules={[{ required: true, message: `${t.READ_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}` }]} rules={[{required: true, message: `${t.READ_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}`}]}
> >
<Input disabled={!writeOperationEnabled} /> <Input disabled={!writeOperationEnabled}/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t.PERM} label={t.PERM}
name="perm" name="perm"
rules={[{ required: true, message: `${t.PERM}${t.CANNOT_BE_EMPTY}` }]} rules={[{required: true, message: `${t.PERM}${t.CANNOT_BE_EMPTY}`}]}
> >
<Input disabled={!writeOperationEnabled} /> <Input disabled={!writeOperationEnabled}/>
</Form.Item> </Form.Item>
{!initialData.sysFlag && writeOperationEnabled && ( {!initialData.sysFlag && writeOperationEnabled && (
<Form.Item wrapperCol={{ offset: 8, span: 16 }}> <Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" onClick={handleFormSubmit}> <Button type="primary" onClick={handleFormSubmit}>
{t.COMMIT} {t.COMMIT}
</Button> </Button>

View File

@@ -15,20 +15,21 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { createContext, useState, useContext } from 'react'; import React, {createContext, useContext, useState} from 'react';
import { translations } from '../i18n'; import {translations} from '../i18n';
const LanguageContext = createContext({ const LanguageContext = createContext({
lang: 'en', lang: 'en',
setLang: () => {}, setLang: () => {
},
t: translations['en'], // 当前语言的文本资源 t: translations['en'], // 当前语言的文本资源
}); });
export const LanguageProvider = ({ children }) => { export const LanguageProvider = ({children}) => {
const [lang, setLang] = useState('en'); const [lang, setLang] = useState('en');
const t = translations[lang] || translations['en']; const t = translations[lang] || translations['en'];
return ( return (
<LanguageContext.Provider value={{ lang, setLang, t }}> <LanguageContext.Provider value={{lang, setLang, t}}>
{children} {children}
</LanguageContext.Provider> </LanguageContext.Provider>
); );

View File

@@ -47,10 +47,10 @@ export const translations = {
"FETCH_TOPIC_FAILED": "获取主题列表失败", "FETCH_TOPIC_FAILED": "获取主题列表失败",
"CONFIRM_DELETE": "确认删除", "CONFIRM_DELETE": "确认删除",
"CANCEL": "取消", "CANCEL": "取消",
"SELECT_DELETE_BROKERS":"请选择在哪个Broker删除消费者组", "SELECT_DELETE_BROKERS": "请选择在哪个Broker删除消费者组",
"DELETE_CONSUMER_GROUP":"删除消费者组", "DELETE_CONSUMER_GROUP": "删除消费者组",
"ENGLISH": "英文", "ENGLISH": "英文",
"ADD_CONSUMER":"添加消费者", "ADD_CONSUMER": "添加消费者",
"CHINESE": "简体中文", "CHINESE": "简体中文",
"CANNOT_BE_EMPTY": "不能为空", "CANNOT_BE_EMPTY": "不能为空",
"TITLE": "RocketMQ仪表板", "TITLE": "RocketMQ仪表板",
@@ -70,16 +70,16 @@ export const translations = {
"CLUSTER_DETAIL": "集群详情", "CLUSTER_DETAIL": "集群详情",
"COMMIT": "提交", "COMMIT": "提交",
"TOPIC": "主题", "TOPIC": "主题",
"SUBSCRIPTION_GROUP":"订阅组", "SUBSCRIPTION_GROUP": "订阅组",
"PRODUCER_GROUP":"生产组", "PRODUCER_GROUP": "生产组",
"CONSUMER":"消费者", "CONSUMER": "消费者",
"PRODUCER":"生产者", "PRODUCER": "生产者",
"MESSAGE":"消息", "MESSAGE": "消息",
"MESSAGE_DETAIL":"消息详情", "MESSAGE_DETAIL": "消息详情",
"RESEND_MESSAGE":"重新发送", "RESEND_MESSAGE": "重新发送",
"VIEW_EXCEPTION":"查看异常", "VIEW_EXCEPTION": "查看异常",
"DLQ_MESSAGE":"死信消息", "DLQ_MESSAGE": "死信消息",
"MESSAGETRACE":"消息轨迹", "MESSAGETRACE": "消息轨迹",
"OPERATION": "操作", "OPERATION": "操作",
"ADD": "新增", "ADD": "新增",
"UPDATE": "更新", "UPDATE": "更新",
@@ -89,7 +89,7 @@ export const translations = {
"CONFIG": "配置", "CONFIG": "配置",
"SEND_MSG": "发送消息", "SEND_MSG": "发送消息",
"RESET_CUS_OFFSET": "重置消费位点", "RESET_CUS_OFFSET": "重置消费位点",
"SKIP_MESSAGE_ACCUMULATE":"跳过堆积", "SKIP_MESSAGE_ACCUMULATE": "跳过堆积",
"DELETE": "删除", "DELETE": "删除",
"CHANGE_LANG": "更换语言", "CHANGE_LANG": "更换语言",
"CHANGE_VERSION": "更换版本", "CHANGE_VERSION": "更换版本",
@@ -100,73 +100,72 @@ export const translations = {
"TRANSACTION": "事务", "TRANSACTION": "事务",
"UNSPECIFIED": "未指定", "UNSPECIFIED": "未指定",
"DLQ": "死信", "DLQ": "死信",
"QUANTITY":"数量", "QUANTITY": "数量",
"TYPE":"类型", "TYPE": "类型",
"MODE":"模式", "MODE": "模式",
"DELAY":"延迟", "DELAY": "延迟",
"DASHBOARD":"驾驶舱", "DASHBOARD": "驾驶舱",
"CONSUME_DETAIL":"消费详情", "CONSUME_DETAIL": "消费详情",
"CLIENT":"终端", "CLIENT": "终端",
"LAST_CONSUME_TIME":"最后消费时间", "LAST_CONSUME_TIME": "最后消费时间",
"TIME":"时间点", "TIME": "时间点",
"RESET":"重置", "RESET": "重置",
"DATE":"日期", "DATE": "日期",
"NO_DATA":"暂无数据", "NO_DATA": "暂无数据",
"SEARCH":"搜索", "SEARCH": "搜索",
"BEGIN":"开始", "BEGIN": "开始",
"END":"结束", "END": "结束",
"TOPIC_CHANGE":"修改主题", "TOPIC_CHANGE": "修改主题",
"SEND":"发送", "SEND": "发送",
"SUBSCRIPTION_CHANGE":"修改订阅", "SUBSCRIPTION_CHANGE": "修改订阅",
"QUEUE":"队列", "QUEUE": "队列",
"MIN_OFFSET":"最小位点", "MIN_OFFSET": "最小位点",
"MAX_OFFSET":"最大位点", "MAX_OFFSET": "最大位点",
"LAST_UPDATE_TIME_STAMP":"上次更新时间", "LAST_UPDATE_TIME_STAMP": "上次更新时间",
"QUEUE_DATAS":"队列信息", "QUEUE_DATAS": "队列信息",
"READ_QUEUE_NUMS":"读队列数量", "READ_QUEUE_NUMS": "读队列数量",
"WRITE_QUEUE_NUMS":"写队列数量", "WRITE_QUEUE_NUMS": "写队列数量",
"PERM":"perm", "PERM": "perm",
"TAG":"标签", "TAG": "标签",
"KEY":"值", "KEY": "值",
"MESSAGE_BODY":"消息主体", "MESSAGE_BODY": "消息主体",
"TOPIC_NAME":"主题名", "TOPIC_NAME": "主题名",
"ORDER":"顺序", "ORDER": "顺序",
"CONSUMER_CLIENT":"消费者终端", "CONSUMER_CLIENT": "消费者终端",
"BROKER_OFFSET":"代理者位点", "BROKER_OFFSET": "代理者位点",
"CONSUMER_OFFSET":"消费者位点", "CONSUMER_OFFSET": "消费者位点",
"DIFF_TOTAL":"差值", "DIFF_TOTAL": "差值",
"LAST_TIME_STAMP":"上次时间", "LAST_TIME_STAMP": "上次时间",
"RESET_OFFSET":"重置位点", "RESET_OFFSET": "重置位点",
"CLUSTER_NAME":"集群名", "CLUSTER_NAME": "集群名",
"OPS":"运维", "OPS": "运维",
"PROXY":"代理", "PROXY": "代理",
"AUTO_REFRESH":"自动刷新", "AUTO_REFRESH": "自动刷新",
"REFRESH":"刷新", "REFRESH": "刷新",
"LOGOUT":"退出", "LOGOUT": "退出",
"LOGIN":"登录", "LOGIN": "登录",
"USER_NAME":"用户名", "USER_NAME": "用户名",
"PASSWORD":"密码", "PASSWORD": "密码",
"SYSTEM":"系统", "SYSTEM": "系统",
"WELCOME":"您好欢迎使用RocketMQ仪表盘", "WELCOME": "您好欢迎使用RocketMQ仪表盘",
"ENABLE_MESSAGE_TRACE":"开启消息轨迹", "ENABLE_MESSAGE_TRACE": "开启消息轨迹",
"MESSAGE_TRACE_DETAIL":"消息轨迹详情", "MESSAGE_TRACE_DETAIL": "消息轨迹详情",
"TRACE_TOPIC":"消息轨迹主题", "TRACE_TOPIC": "消息轨迹主题",
"SELECT_TRACE_TOPIC":"选择消息轨迹主题", "SELECT_TRACE_TOPIC": "选择消息轨迹主题",
"EXPORT": "导出", "EXPORT": "导出",
"NO_MATCH_RESULT": "没有查到符合条件的结果", "NO_MATCH_RESULT": "没有查到符合条件的结果",
"BATCH_RESEND": "批量重发", "BATCH_RESEND": "批量重发",
"BATCH_EXPORT": "批量导出", "BATCH_EXPORT": "批量导出",
"WHITE_LIST":"白名单", "ACCOUNT_INFO": "账户信息",
"ACCOUNT_INFO":"账户信息", "IS_ADMIN": "是否管理员",
"IS_ADMIN":"是否管理员", "DEFAULT_TOPIC_PERM": "topic默认权限",
"DEFAULT_TOPIC_PERM":"topic默认权限", "DEFAULT_GROUP_PERM": "消费组默认权限",
"DEFAULT_GROUP_PERM":"消费组默认权限", "TOPIC_PERM": "topic权限",
"TOPIC_PERM":"topic权限", "GROUP_PERM": "消费组权限",
"GROUP_PERM":"消费组权限", "SYNCHRONIZE": "同步",
"SYNCHRONIZE":"同步", "SHOW": "显示",
"SHOW":"显示", "HIDE": "隐藏",
"HIDE":"隐藏", "MESSAGE_TYPE": "消息类型",
"MESSAGE_TYPE":"消息类型",
"MESSAGE_TYPE_UNSPECIFIED": "未指定,为普通消息", "MESSAGE_TYPE_UNSPECIFIED": "未指定,为普通消息",
"MESSAGE_TYPE_NORMAL": "普通消息", "MESSAGE_TYPE_NORMAL": "普通消息",
"MESSAGE_TYPE_FIFO": "顺序消息", "MESSAGE_TYPE_FIFO": "顺序消息",
@@ -294,7 +293,7 @@ export const translations = {
"SELECT_TOPIC_PLACEHOLDER": "Please select topic", "SELECT_TOPIC_PLACEHOLDER": "Please select topic",
"MESSAGE_ID_TOPIC_HINT": "Message ID Topic", "MESSAGE_ID_TOPIC_HINT": "Message ID Topic",
"TOPIC_ADD": "Add Topic", "TOPIC_ADD": "Add Topic",
"SKIP_MESSAGE_ACCUMULATE":"Skip Message Accumulate", "SKIP_MESSAGE_ACCUMULATE": "Skip Message Accumulate",
"OPERATION_FAILED": "Operation Failed", "OPERATION_FAILED": "Operation Failed",
"FORM_VALIDATION_FAILED": "Form Validation Failed", "FORM_VALIDATION_FAILED": "Form Validation Failed",
"ADD_CONSUMER": "Add Consumer", "ADD_CONSUMER": "Add Consumer",
@@ -325,7 +324,7 @@ export const translations = {
"ADDRESS": "Address", "ADDRESS": "Address",
"VERSION": "Version", "VERSION": "Version",
"PRO_MSG_TPS": "Produce Message TPS", "PRO_MSG_TPS": "Produce Message TPS",
"CUS_MSG_TPS": "Consume Message TPS", "CUS_MSG_TPS": "Consumer Message TPS",
"YESTERDAY_PRO_COUNT": "Yesterday Produce Count", "YESTERDAY_PRO_COUNT": "Yesterday Produce Count",
"YESTERDAY_CUS_COUNT": "Yesterday Consume Count", "YESTERDAY_CUS_COUNT": "Yesterday Consume Count",
"TODAY_PRO_COUNT": "Today Produce Count", "TODAY_PRO_COUNT": "Today Produce Count",
@@ -335,16 +334,16 @@ export const translations = {
"CLUSTER": "Cluster", "CLUSTER": "Cluster",
"CLUSTER_DETAIL": "Cluster Detail", "CLUSTER_DETAIL": "Cluster Detail",
"TOPIC": "Topic", "TOPIC": "Topic",
"SUBSCRIPTION_GROUP":"SubscriptionGroup", "SUBSCRIPTION_GROUP": "SubscriptionGroup",
"PRODUCER_GROUP":"ProducerGroup", "PRODUCER_GROUP": "ProducerGroup",
"CONSUMER":"Consumer", "CONSUMER": "Consumer",
"PRODUCER":"Producer", "PRODUCER": "Producer",
"MESSAGE":"Message", "MESSAGE": "Message",
"MESSAGE_DETAIL":"Message Detail", "MESSAGE_DETAIL": "Message Detail",
"RESEND_MESSAGE":"Resend Message", "RESEND_MESSAGE": "Resend Message",
"VIEW_EXCEPTION":"View Exception", "VIEW_EXCEPTION": "View Exception",
"MESSAGETRACE":"MessageTrace", "MESSAGETRACE": "MessageTrace",
"DLQ_MESSAGE":"DLQMessage", "DLQ_MESSAGE": "DLQMessage",
"COMMIT": "Commit", "COMMIT": "Commit",
"OPERATION": "Operation", "OPERATION": "Operation",
"ADD": "Add", "ADD": "Add",
@@ -365,73 +364,72 @@ export const translations = {
"TRANSACTION": "TRANSACTION", "TRANSACTION": "TRANSACTION",
"UNSPECIFIED": "UNSPECIFIED", "UNSPECIFIED": "UNSPECIFIED",
"DLQ": "DLQ", "DLQ": "DLQ",
"QUANTITY":"Quantity", "QUANTITY": "Quantity",
"TYPE":"Type", "TYPE": "Type",
"MODE":"Mode", "MODE": "Mode",
"DELAY":"Delay", "DELAY": "Delay",
"DASHBOARD":"Dashboard", "DASHBOARD": "Dashboard",
"CONSUME_DETAIL":"CONSUME DETAIL", "CONSUME_DETAIL": "CONSUME DETAIL",
"CLIENT":"CLIENT", "CLIENT": "CLIENT",
"LAST_CONSUME_TIME":"LastConsumeTime", "LAST_CONSUME_TIME": "LastConsumeTime",
"TIME":"Time", "TIME": "Time",
"RESET":"RESET", "RESET": "RESET",
"DATE":"Date", "DATE": "Date",
"NO_DATA":"NO DATA", "NO_DATA": "NO DATA",
"SEARCH":"Search", "SEARCH": "Search",
"BEGIN":"Begin", "BEGIN": "Begin",
"END":"End", "END": "End",
"TOPIC_CHANGE":"Topic Change", "TOPIC_CHANGE": "Topic Change",
"SEND":"Send", "SEND": "Send",
"SUBSCRIPTION_CHANGE":"Subscription Change", "SUBSCRIPTION_CHANGE": "Subscription Change",
"QUEUE":"Queue", "QUEUE": "Queue",
"MIN_OFFSET":"minOffset", "MIN_OFFSET": "minOffset",
"MAX_OFFSET":"maxOffset", "MAX_OFFSET": "maxOffset",
"LAST_UPDATE_TIME_STAMP":"lastUpdateTimeStamp", "LAST_UPDATE_TIME_STAMP": "lastUpdateTimeStamp",
"QUEUE_DATAS":"queueDatas", "QUEUE_DATAS": "queueDatas",
"READ_QUEUE_NUMS":"readQueueNums", "READ_QUEUE_NUMS": "readQueueNums",
"WRITE_QUEUE_NUMS":"writeQueueNums", "WRITE_QUEUE_NUMS": "writeQueueNums",
"PERM":"perm", "PERM": "perm",
"TAG":"Tag", "TAG": "Tag",
"KEY":"Key", "KEY": "Key",
"MESSAGE_BODY":"Message Body", "MESSAGE_BODY": "Message Body",
"TOPIC_NAME":"topicName", "TOPIC_NAME": "topicName",
"ORDER":"order", "ORDER": "order",
"CONSUMER_CLIENT":"consumerClient", "CONSUMER_CLIENT": "consumerClient",
"BROKER_OFFSET":"brokerOffset", "BROKER_OFFSET": "brokerOffset",
"CONSUMER_OFFSET":"consumerOffset", "CONSUMER_OFFSET": "consumerOffset",
"DIFF_TOTAL":"diffTotal", "DIFF_TOTAL": "diffTotal",
"LAST_TIME_STAMP":"lastTimeStamp", "LAST_TIME_STAMP": "lastTimeStamp",
"RESET_OFFSET":"resetOffset", "RESET_OFFSET": "resetOffset",
"CLUSTER_NAME":"clusterName", "CLUSTER_NAME": "clusterName",
"OPS":"OPS", "OPS": "OPS",
"PROXY":"Proxy", "PROXY": "Proxy",
"AUTO_REFRESH":"AUTO_REFRESH", "AUTO_REFRESH": "AUTO_REFRESH",
"REFRESH":"REFRESH", "REFRESH": "REFRESH",
"LOGOUT":"Logout", "LOGOUT": "Logout",
"LOGIN":"Login", "LOGIN": "Login",
"USER_NAME":"Username", "USER_NAME": "Username",
"PASSWORD":"Password", "PASSWORD": "Password",
"SYSTEM":"SYSTEM", "SYSTEM": "SYSTEM",
"WELCOME":"Hi, welcome using RocketMQ Dashboard", "WELCOME": "Hi, welcome using RocketMQ Dashboard",
"ENABLE_MESSAGE_TRACE":"Enable Message Trace", "ENABLE_MESSAGE_TRACE": "Enable Message Trace",
"MESSAGE_TRACE_DETAIL":"Message Trace Detail", "MESSAGE_TRACE_DETAIL": "Message Trace Detail",
"TRACE_TOPIC":"TraceTopic", "TRACE_TOPIC": "TraceTopic",
"SELECT_TRACE_TOPIC":"selectTraceTopic", "SELECT_TRACE_TOPIC": "selectTraceTopic",
"EXPORT": "export", "EXPORT": "export",
"NO_MATCH_RESULT": "no match result", "NO_MATCH_RESULT": "no match result",
"BATCH_RESEND": "batchReSend", "BATCH_RESEND": "batchReSend",
"BATCH_EXPORT": "batchExport", "BATCH_EXPORT": "batchExport",
"WHITE_LIST":"White List", "ACCOUNT_INFO": "Account Info",
"ACCOUNT_INFO":"Account Info", "IS_ADMIN": "Is Admin",
"IS_ADMIN":"Is Admin", "DEFAULT_TOPIC_PERM": "Default Topic Permission",
"DEFAULT_TOPIC_PERM":"Default Topic Permission", "DEFAULT_GROUP_PERM": "Default Group Permission",
"DEFAULT_GROUP_PERM":"Default Group Permission", "TOPIC_PERM": "Topic Permission",
"TOPIC_PERM":"Topic Permission", "GROUP_PERM": "Group Permission",
"GROUP_PERM":"Group Permission", "SYNCHRONIZE": "Synchronize Data",
"SYNCHRONIZE":"Synchronize Data", "SHOW": "Show",
"SHOW":"Show", "HIDE": "Hide",
"HIDE":"Hide", "MESSAGE_TYPE": "messageType",
"MESSAGE_TYPE":"messageType",
"MESSAGE_TYPE_UNSPECIFIED": "UNSPECIFIED, is NORMAL", "MESSAGE_TYPE_UNSPECIFIED": "UNSPECIFIED, is NORMAL",
"MESSAGE_TYPE_NORMAL": "NORMAL", "MESSAGE_TYPE_NORMAL": "NORMAL",
"MESSAGE_TYPE_FIFO": "FIFO", "MESSAGE_TYPE_FIFO": "FIFO",

View File

@@ -19,7 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import './index.css'; import './index.css';
import App from './App'; import App from './App';
import { App as AntdApp } from 'antd'; import {App as AntdApp} from 'antd';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
import {LanguageProvider} from "./i18n/LanguageContext"; import {LanguageProvider} from "./i18n/LanguageContext";
import {Provider} from "react-redux"; import {Provider} from "react-redux";
@@ -27,7 +27,6 @@ import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root')); const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( root.render(
<LanguageProvider> <LanguageProvider>
<React.StrictMode> <React.StrictMode>
<AntdApp> <AntdApp>
@@ -37,7 +36,6 @@ root.render(
</AntdApp> </AntdApp>
</React.StrictMode> </React.StrictMode>
</LanguageProvider> </LanguageProvider>
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

View File

@@ -44,7 +44,7 @@ const ConsumerGroupList = () => {
const [isAddConfig, setIsAddConfig] = useState(false); const [isAddConfig, setIsAddConfig] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [messageApi, msgContextHolder] = message.useMessage(); const [messageApi, msgContextHolder] = message.useMessage();
const [notificationApi,notificationContextHolder] = notification.useNotification(); const [notificationApi, notificationContextHolder] = notification.useNotification();
const [paginationConf, setPaginationConf] = useState({ const [paginationConf, setPaginationConf] = useState({
current: 1, current: 1,
@@ -63,9 +63,9 @@ const ConsumerGroupList = () => {
const response = await remoteApi.queryConsumerGroupList(false); const response = await remoteApi.queryConsumerGroupList(false);
if (response.status === 0) { if (response.status === 0) {
setAllConsumerGroupList(response.data); setAllConsumerGroupList(response.data);
if(currentPage!=null){ if (currentPage != null) {
filterList(currentPage, response.data); filterList(currentPage, response.data);
}else{ } else {
filterList(1, response.data); filterList(1, response.data);
} }
} else { } else {
@@ -380,7 +380,7 @@ const ConsumerGroupList = () => {
filterList(pagination.current, allConsumerGroupList); filterList(pagination.current, allConsumerGroupList);
}; };
const closeConfigModal = () =>{ const closeConfigModal = () => {
setShowConfig(false); setShowConfig(false);
setIsAddConfig(false); setIsAddConfig(false);
} }

View File

@@ -16,17 +16,17 @@
*/ */
import React from 'react'; import React from 'react';
import { Form, Input, Button, message, Typography } from 'antd'; import {Button, Form, Input, message, Typography} from 'antd';
import {remoteApi} from "../../api/remoteApi/remoteApi"; import {remoteApi} from "../../api/remoteApi/remoteApi";
const { Title } = Typography; const {Title} = Typography;
const Login = () => { const Login = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [messageApi, msgContextHolder] = message.useMessage(); const [messageApi, msgContextHolder] = message.useMessage();
const onFinish = async (values) => { const onFinish = async (values) => {
const { username, password } = values; const {username, password} = values;
remoteApi.login(username, password).then((res) => { remoteApi.login(username, password).then((res) => {
if (res.status === 0) { if (res.status === 0) {
messageApi.success('登录成功'); messageApi.success('登录成功');

View File

@@ -15,12 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useState, useEffect } from 'react'; import React, {useEffect, useState} from 'react';
import { Select, Button, Switch, Input, Typography, Space, message } from 'antd'; import {Button, Input, message, Select, Space, Switch, Typography} from 'antd';
import {remoteApi} from '../../api/remoteApi/remoteApi'; import {remoteApi} from '../../api/remoteApi/remoteApi';
const { Title } = Typography; const {Title} = Typography;
const { Option } = Select; const {Option} = Select;
const Ops = () => { const Ops = () => {
const [namesrvAddrList, setNamesrvAddrList] = useState([]); const [namesrvAddrList, setNamesrvAddrList] = useState([]);

View File

@@ -15,16 +15,16 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useState, useEffect } from 'react'; import React, {useEffect, useState} from 'react';
import { Modal, Button, Select, Input, Card, Row, Col, notification, Spin } from 'antd'; import {Button, Card, Col, Input, Modal, notification, Row, Select, Spin} from 'antd';
import { useLanguage } from '../../i18n/LanguageContext'; import {useLanguage} from '../../i18n/LanguageContext';
import { remoteApi } from "../../api/remoteApi/remoteApi"; import {remoteApi} from "../../api/remoteApi/remoteApi";
const { Option } = Select; const {Option} = Select;
const ProxyManager = () => { const ProxyManager = () => {
const { t } = useLanguage(); const {t} = useLanguage();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [proxyAddrList, setProxyAddrList] = useState([]); const [proxyAddrList, setProxyAddrList] = useState([]);
@@ -47,7 +47,7 @@ const ProxyManager = () => {
remoteApi.queryProxyHomePage((resp) => { remoteApi.queryProxyHomePage((resp) => {
setLoading(false); setLoading(false);
if (resp.status === 0) { if (resp.status === 0) {
const { proxyAddrList, currentProxyAddr } = resp.data; const {proxyAddrList, currentProxyAddr} = resp.data;
setProxyAddrList(proxyAddrList || []); setProxyAddrList(proxyAddrList || []);
setSelectedProxy(currentProxyAddr || (proxyAddrList && proxyAddrList.length > 0 ? proxyAddrList[0] : '')); setSelectedProxy(currentProxyAddr || (proxyAddrList && proxyAddrList.length > 0 ? proxyAddrList[0] : ''));
@@ -58,7 +58,7 @@ const ProxyManager = () => {
} }
} else { } else {
notificationApi.error({ message: resp.errMsg || t.FETCH_PROXY_LIST_FAILED, duration: 2 }); notificationApi.error({message: resp.errMsg || t.FETCH_PROXY_LIST_FAILED, duration: 2});
} }
}); });
}, [t]); }, [t]);
@@ -71,7 +71,10 @@ const ProxyManager = () => {
const handleAddProxyAddr = () => { const handleAddProxyAddr = () => {
if (!newProxyAddr.trim()) { if (!newProxyAddr.trim()) {
notificationApi.warning({ message: t.INPUT_PROXY_ADDR_REQUIRED || "Please input a new proxy address.", duration: 2 }); notificationApi.warning({
message: t.INPUT_PROXY_ADDR_REQUIRED || "Please input a new proxy address.",
duration: 2
});
return; return;
} }
setLoading(true); setLoading(true);
@@ -82,28 +85,28 @@ const ProxyManager = () => {
setProxyAddrList(prevList => [...prevList, newProxyAddr.trim()]); setProxyAddrList(prevList => [...prevList, newProxyAddr.trim()]);
} }
setNewProxyAddr(''); setNewProxyAddr('');
notificationApi.info({ message: t.SUCCESS || "SUCCESS", duration: 2 }); notificationApi.info({message: t.SUCCESS || "SUCCESS", duration: 2});
} else { } else {
notificationApi.error({ message: resp.errMsg || t.ADD_PROXY_FAILED, duration: 2 }); notificationApi.error({message: resp.errMsg || t.ADD_PROXY_FAILED, duration: 2});
} }
}); });
}; };
return ( return (
<Spin spinning={loading} tip={t.LOADING}> <Spin spinning={loading} tip={t.LOADING}>
<div className="container-fluid" style={{ padding: '24px' }} id="deployHistoryList"> <div className="container-fluid" style={{padding: '24px'}} id="deployHistoryList">
<Card <Card
title={ title={
<div style={{ fontSize: '20px', fontWeight: 'bold' }}> <div style={{fontSize: '20px', fontWeight: 'bold'}}>
ProxyServerAddressList ProxyServerAddressList
</div> </div>
} }
bordered={false} bordered={false}
> >
<Row gutter={[16, 16]} align="middle"> <Row gutter={[16, 16]} align="middle">
<Col flex="auto" style={{ minWidth: 300, maxWidth: 500 }}> <Col flex="auto" style={{minWidth: 300, maxWidth: 500}}>
<Select <Select
style={{ width: '100%' }} style={{width: '100%'}}
value={selectedProxy} value={selectedProxy}
onChange={handleSelectChange} onChange={handleSelectChange}
placeholder={t.SELECT} placeholder={t.SELECT}
@@ -122,14 +125,14 @@ const ProxyManager = () => {
</Row> </Row>
{writeOperationEnabled && ( {writeOperationEnabled && (
<Row gutter={[16, 16]} align="middle" style={{ marginTop: 16 }}> <Row gutter={[16, 16]} align="middle" style={{marginTop: 16}}>
<Col> <Col>
<label htmlFor="newProxyAddrInput">ProxyAddr:</label> <label htmlFor="newProxyAddrInput">ProxyAddr:</label>
</Col> </Col>
<Col> <Col>
<Input <Input
id="newProxyAddrInput" id="newProxyAddrInput"
style={{ width: 300 }} style={{width: 300}}
value={newProxyAddr} value={newProxyAddr}
onChange={(e) => setNewProxyAddr(e.target.value)} onChange={(e) => setNewProxyAddr(e.target.value)}
placeholder={t.INPUT_PROXY_ADDR} placeholder={t.INPUT_PROXY_ADDR}
@@ -149,25 +152,26 @@ const ProxyManager = () => {
onCancel={() => setShowModal(false)} onCancel={() => setShowModal(false)}
title={`${t.PROXY_CONFIG} [${selectedProxy}]`} title={`${t.PROXY_CONFIG} [${selectedProxy}]`}
footer={ footer={
<div style={{ textAlign: 'center' }}> <div style={{textAlign: 'center'}}>
<Button onClick={() => setShowModal(false)}>{t.CLOSE}</Button> <Button onClick={() => setShowModal(false)}>{t.CLOSE}</Button>
</div> </div>
} }
width={800} width={800}
bodyStyle={{ maxHeight: '60vh', overflowY: 'auto' }} bodyStyle={{maxHeight: '60vh', overflowY: 'auto'}}
> >
<table className="table table-bordered" style={{ width: '100%' }}> <table className="table table-bordered" style={{width: '100%'}}>
<tbody> <tbody>
{Object.entries(allProxyConfig).length > 0 ? ( {Object.entries(allProxyConfig).length > 0 ? (
Object.entries(allProxyConfig).map(([key, value]) => ( Object.entries(allProxyConfig).map(([key, value]) => (
<tr key={key}> <tr key={key}>
<td style={{ fontWeight: 500, width: '30%' }}>{key}</td> <td style={{fontWeight: 500, width: '30%'}}>{key}</td>
<td>{value}</td> <td>{value}</td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr>
<td colSpan="2" style={{ textAlign: 'center' }}>{t.NO_CONFIG_DATA || "No configuration data available."}</td> <td colSpan="2"
style={{textAlign: 'center'}}>{t.NO_CONFIG_DATA || "No configuration data available."}</td>
</tr> </tr>
)} )}
</tbody> </tbody>

View File

@@ -173,7 +173,6 @@ const DeployHistoryList = () => {
}; };
const filterList = (currentPage) => { const filterList = (currentPage) => {
const lowExceptStr = filterStr.toLowerCase(); const lowExceptStr = filterStr.toLowerCase();
const canShowList = allTopicList.filter((topic, index) => { const canShowList = allTopicList.filter((topic, index) => {
@@ -257,7 +256,7 @@ const DeployHistoryList = () => {
return; return;
} }
if(!isUpdate){ if (!isUpdate) {
const clusterResult = await remoteApi.getClusterList(); const clusterResult = await remoteApi.getClusterList();
if (clusterResult.status === 0) { if (clusterResult.status === 0) {
setAllClusterNameList(Object.keys(clusterResult.data.clusterInfo.clusterAddrTable)); setAllClusterNameList(Object.keys(clusterResult.data.clusterInfo.clusterAddrTable));
@@ -276,7 +275,7 @@ const DeployHistoryList = () => {
if (result.status === 0) { if (result.status === 0) {
messageApi.success(t.TOPIC_OPERATION_SUCCESS); messageApi.success(t.TOPIC_OPERATION_SUCCESS);
closeAddUpdateDialog(); closeAddUpdateDialog();
if(!isUpdateMode) { if (!isUpdateMode) {
await getTopicList() await getTopicList()
} }
} else { } else {

View File

@@ -17,7 +17,7 @@
const reportWebVitals = onPerfEntry => { const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
getCLS(onPerfEntry); getCLS(onPerfEntry);
getFID(onPerfEntry); getFID(onPerfEntry);
getFCP(onPerfEntry); getFCP(onPerfEntry);

View File

@@ -64,7 +64,7 @@ const AppRouter = () => {
useEffect(() => { useEffect(() => {
remoteApi.setRedirectHandler(() => { remoteApi.setRedirectHandler(() => {
navigate('/login', { replace: true }); navigate('/login', {replace: true});
}); });
}, [navigate]); }, [navigate]);

View File

@@ -14,10 +14,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { useEffect } from 'react'; import {useEffect} from 'react';
import { useSelector, useDispatch } from 'react-redux'; import {useDispatch, useSelector} from 'react-redux';
import { themes, defaultTheme } from '../../assets/styles/theme'; import {defaultTheme, themes} from '../../assets/styles/theme';
import { setTheme } from '../actions/themeActions'; import {setTheme} from '../actions/themeActions';
export const useTheme = () => { export const useTheme = () => {
// 从 Redux store 中取出 currentThemeName // 从 Redux store 中取出 currentThemeName

View File

@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { createStore,combineReducers } from 'redux'; import {combineReducers, createStore} from 'redux';
import themeReducer from './reducers/themeReducer'; import themeReducer from './reducers/themeReducer';
// 组合所有的 reducers // 组合所有的 reducers

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { SET_THEME } from '../actions/themeActions'; import {SET_THEME} from '../actions/themeActions';
const getInitialTheme = () => { const getInitialTheme = () => {
return localStorage.getItem('appTheme') || 'default'; return localStorage.getItem('appTheme') || 'default';

View File

@@ -16,7 +16,8 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<groupId>org.apache</groupId> <groupId>org.apache</groupId>
@@ -432,7 +433,7 @@
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<id>install node </id> <id>install node</id>
<goals> <goals>
<goal>install-node-and-npm</goal> <goal>install-node-and-npm</goal>
</goals> </goals>
@@ -469,7 +470,7 @@
<configuration> <configuration>
<target> <target>
<copy todir="${project.build.directory}/classes/public"> <copy todir="${project.build.directory}/classes/public">
<fileset dir="${project.basedir}/frontend-new/build" /> <fileset dir="${project.basedir}/frontend-new/build"/>
</copy> </copy>
</target> </target>
</configuration> </configuration>

View File

@@ -1,25 +0,0 @@
/*
* 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.
*/
package org.apache.rocketmq.dashboard.admin;
import org.apache.rocketmq.tools.admin.MQAdminExt;
@FunctionalInterface
public interface MQAdminExtCallback<T> {
T doInMQAdminExt(MQAdminExt mqAdminExt) throws Exception;
}

View File

@@ -91,6 +91,10 @@ public class RMQConfigure {
@Getter @Getter
private Integer clientCallbackExecutorThreads = 4; private Integer clientCallbackExecutorThreads = 4;
@Setter
@Getter
private String authMode = "file";
public void setProxyAddrs(List<String> proxyAddrs) { public void setProxyAddrs(List<String> proxyAddrs) {
this.proxyAddrs = proxyAddrs; this.proxyAddrs = proxyAddrs;
if (CollectionUtils.isNotEmpty(proxyAddrs)) { if (CollectionUtils.isNotEmpty(proxyAddrs)) {
@@ -112,10 +116,12 @@ public class RMQConfigure {
logger.info("setNameSrvAddrByProperty nameSrvAddr={}", namesrvAddr); logger.info("setNameSrvAddrByProperty nameSrvAddr={}", namesrvAddr);
} }
} }
public boolean isACLEnabled() { public boolean isACLEnabled() {
return !(StringUtils.isAnyBlank(this.accessKey, this.secretKey) || return !(StringUtils.isAnyBlank(this.accessKey, this.secretKey) ||
StringUtils.isAnyEmpty(this.accessKey, this.secretKey)); StringUtils.isAnyEmpty(this.accessKey, this.secretKey));
} }
public String getRocketMqDashboardDataPath() { public String getRocketMqDashboardDataPath() {
return dataPath; return dataPath;
} }

View File

@@ -47,7 +47,7 @@ public class DashboardController {
if (Strings.isNullOrEmpty(topicName)) { if (Strings.isNullOrEmpty(topicName)) {
return dashboardService.queryTopicData(date); return dashboardService.queryTopicData(date);
} }
return dashboardService.queryTopicData(date,topicName); return dashboardService.queryTopicData(date, topicName);
} }
@RequestMapping(value = "/topicCurrent.query", method = RequestMethod.GET) @RequestMapping(value = "/topicCurrent.query", method = RequestMethod.GET)

View File

@@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
public class ProxyController { public class ProxyController {
@Resource @Resource
private ProxyService proxyService; private ProxyService proxyService;
@RequestMapping(value = "/homePage.query", method = RequestMethod.GET) @RequestMapping(value = "/homePage.query", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Object homePage() { public Object homePage() {

View File

@@ -72,7 +72,8 @@ public class TestController {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override public void run() { @Override
public void run() {
int i = 0; int i = 0;
while (true) { while (true) {
@@ -85,13 +86,11 @@ public class TestController {
Thread.sleep(1000L); Thread.sleep(1000L);
SendResult sendResult = producer.send(msg); SendResult sendResult = producer.send(msg);
logger.info("sendMessage={}", JsonUtil.obj2String(sendResult)); logger.info("sendMessage={}", JsonUtil.obj2String(sendResult));
} } catch (Exception e) {
catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} } catch (Exception ignore) {
catch (Exception ignore) {
} }
} }
} }

View File

@@ -75,7 +75,7 @@ public class TopicController {
} }
@RequestMapping(value = "/createOrUpdate.do", method = { RequestMethod.POST}) @RequestMapping(value = "/createOrUpdate.do", method = {RequestMethod.POST})
@ResponseBody @ResponseBody
public Object topicCreateOrUpdateRequest(@RequestBody TopicConfigInfo topicCreateOrUpdateRequest) { public Object topicCreateOrUpdateRequest(@RequestBody TopicConfigInfo topicCreateOrUpdateRequest) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(topicCreateOrUpdateRequest.getBrokerNameList()) || CollectionUtils.isNotEmpty(topicCreateOrUpdateRequest.getClusterNameList()), Preconditions.checkArgument(CollectionUtils.isNotEmpty(topicCreateOrUpdateRequest.getBrokerNameList()) || CollectionUtils.isNotEmpty(topicCreateOrUpdateRequest.getClusterNameList()),

View File

@@ -29,7 +29,6 @@ import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
@WebFilter(urlPatterns = "/*", filterName = "httpBasicAuthorizedFilter") @WebFilter(urlPatterns = "/*", filterName = "httpBasicAuthorizedFilter")
public class HttpBasicAuthorizedFilter implements Filter { public class HttpBasicAuthorizedFilter implements Filter {

View File

@@ -25,7 +25,9 @@ import java.util.Map;
public class MessageView { public class MessageView {
/** from MessageExt **/ /**
* from MessageExt
**/
private int queueId; private int queueId;
private int storeSize; private int storeSize;
private long queueOffset; private long queueOffset;
@@ -41,13 +43,17 @@ public class MessageView {
private long preparedTransactionOffset; private long preparedTransactionOffset;
/**from MessageExt**/ /**from MessageExt**/
/** from Message **/ /**
* from Message
**/
private String topic; private String topic;
private int flag; private int flag;
private Map<String, String> properties; private Map<String, String> properties;
private String messageBody; // body private String messageBody; // body
/** from Message **/ /**
* from Message
**/
public static MessageView fromMessageExt(MessageExt messageExt) { public static MessageView fromMessageExt(MessageExt messageExt) {
MessageView messageView = new MessageView(); MessageView messageView = new MessageView();

View File

@@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.rocketmq.dashboard.model.request; package org.apache.rocketmq.dashboard.model.request;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import java.util.List; import java.util.List;
@@ -24,7 +25,9 @@ public class TopicConfigInfo {
private List<String> clusterNameList; private List<String> clusterNameList;
private List<String> brokerNameList; private List<String> brokerNameList;
/** topicConfig */ /**
* topicConfig
*/
private String topicName; private String topicName;
private int writeQueueNums; private int writeQueueNums;
private int readQueueNums; private int readQueueNums;
@@ -32,6 +35,7 @@ public class TopicConfigInfo {
private boolean order; private boolean order;
private String messageType; private String messageType;
public List<String> getClusterNameList() { public List<String> getClusterNameList() {
return clusterNameList; return clusterNameList;
} }
@@ -40,8 +44,9 @@ public class TopicConfigInfo {
this.clusterNameList = clusterNameList; this.clusterNameList = clusterNameList;
} }
/** topicConfig */ /**
* topicConfig
*/
public List<String> getBrokerNameList() { public List<String> getBrokerNameList() {
@@ -102,8 +107,6 @@ public class TopicConfigInfo {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) if (this == o)
@@ -121,7 +124,7 @@ public class TopicConfigInfo {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(topicName, writeQueueNums, readQueueNums, perm, order,messageType); return Objects.hashCode(topicName, writeQueueNums, readQueueNums, perm, order, messageType);
} }
} }

View File

@@ -29,6 +29,7 @@ import java.util.Set;
public abstract class AbstractCommonService { public abstract class AbstractCommonService {
@Resource @Resource
protected MQAdminExt mqAdminExt; protected MQAdminExt mqAdminExt;
protected final Set<String> changeToBrokerNameSet(Map<String, Set<String>> clusterAddrTable, protected final Set<String> changeToBrokerNameSet(Map<String, Set<String>> clusterAddrTable,
List<String> clusterNameList, List<String> brokerNameList) { List<String> clusterNameList, List<String> brokerNameList) {
Set<String> finalBrokerNameList = Sets.newHashSet(); Set<String> finalBrokerNameList = Sets.newHashSet();
@@ -37,8 +38,7 @@ public abstract class AbstractCommonService {
for (String clusterName : clusterNameList) { for (String clusterName : clusterNameList) {
finalBrokerNameList.addAll(clusterAddrTable.get(clusterName)); finalBrokerNameList.addAll(clusterAddrTable.get(clusterName));
} }
} } catch (Exception e) {
catch (Exception e) {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -31,7 +31,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
public interface ConsumerService { public interface ConsumerService {
List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup,String address); List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup, String address);
GroupConsumeInfo queryGroup(String consumerGroup, String address); GroupConsumeInfo queryGroup(String consumerGroup, String address);

View File

@@ -39,8 +39,7 @@ public interface MessageService {
/** /**
* @param topic * @param topic
* @param begin * @param begin
* @param end * @param end org.apache.rocketmq.tools.command.message.PrintMessageSubCommand
* org.apache.rocketmq.tools.command.message.PrintMessageSubCommand
*/ */
List<MessageView> queryMessageByTopic(final String topic, final long begin, List<MessageView> queryMessageByTopic(final String topic, final long begin,
final long end); final long end);
@@ -54,6 +53,4 @@ public interface MessageService {
MessagePage queryMessageByPage(MessageQuery query); MessagePage queryMessageByPage(MessageQuery query);
} }

View File

@@ -27,7 +27,7 @@ public interface OpsService {
String getNameSvrList(); String getNameSvrList();
Map<CheckerType,Object> rocketMqStatusCheck(); Map<CheckerType, Object> rocketMqStatusCheck();
boolean updateIsVIPChannel(String useVIPChannel); boolean updateIsVIPChannel(String useVIPChannel);

View File

@@ -94,7 +94,8 @@ public class MQAdminExtImpl implements MQAdminExt {
private Logger logger = LoggerFactory.getLogger(MQAdminExtImpl.class); private Logger logger = LoggerFactory.getLogger(MQAdminExtImpl.class);
public MQAdminExtImpl() {} public MQAdminExtImpl() {
}
@Override @Override
@@ -134,8 +135,7 @@ public class MQAdminExtImpl implements MQAdminExt {
RemotingCommand response = null; RemotingCommand response = null;
try { try {
response = remotingClient.invokeSync(addr, request, 8000); response = remotingClient.invokeSync(addr, request, 8000);
} } catch (Exception err) {
catch (Exception err) {
Throwables.throwIfUnchecked(err); Throwables.throwIfUnchecked(err);
throw new RuntimeException(err); throw new RuntimeException(err);
} }
@@ -566,17 +566,20 @@ public class MQAdminExtImpl implements MQAdminExt {
} }
// 4.0.0 added // 4.0.0 added
@Override public void updateNameServerConfig(Properties properties, @Override
public void updateNameServerConfig(Properties properties,
List<String> list) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException { List<String> list) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException {
} }
@Override public Map<String, Properties> getNameServerConfig( @Override
public Map<String, Properties> getNameServerConfig(
List<String> list) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException { List<String> list) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException {
return null; return null;
} }
@Override public QueryConsumeQueueResponseBody queryConsumeQueue(String brokerAddr, String topic, @Override
public QueryConsumeQueueResponseBody queryConsumeQueue(String brokerAddr, String topic,
int queueId, long index, int count, int queueId, long index, int count,
String consumerGroup) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { String consumerGroup) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException {
return null; return null;
@@ -588,7 +591,8 @@ public class MQAdminExtImpl implements MQAdminExt {
} }
@Override public boolean resumeCheckHalfMessage(String topic, @Override
public boolean resumeCheckHalfMessage(String topic,
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return false; return false;
} }

View File

@@ -212,7 +212,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
} else { } else {
consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL"); consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL");
} }
consumeInfo.setGroup(consumerGroup);
consumeInfo.setUpdateTime(new Date()); consumeInfo.setUpdateTime(new Date());
groupConsumeInfoList.add(consumeInfo); groupConsumeInfoList.add(consumeInfo);
} catch (Exception e) { } catch (Exception e) {
@@ -270,6 +269,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
logger.warn("examineConsumeStats or examineConsumerConnectionInfo exception, " logger.warn("examineConsumeStats or examineConsumerConnectionInfo exception, "
+ consumerGroup, e); + consumerGroup, e);
} }
groupConsumeInfo.setGroup(consumerGroup);
return groupConsumeInfo; return groupConsumeInfo;
} }

View File

@@ -96,6 +96,7 @@ public class DashboardCollectServiceImpl implements DashboardCollectService {
public LoadingCache<String, List<String>> getBrokerMap() { public LoadingCache<String, List<String>> getBrokerMap() {
return brokerMap; return brokerMap;
} }
@Override @Override
public LoadingCache<String, List<String>> getTopicMap() { public LoadingCache<String, List<String>> getTopicMap() {
return topicMap; return topicMap;
@@ -106,8 +107,7 @@ public class DashboardCollectServiceImpl implements DashboardCollectService {
List<String> strings; List<String> strings;
try { try {
strings = Files.readLines(file, Charsets.UTF_8); strings = Files.readLines(file, Charsets.UTF_8);
} } catch (IOException e) {
catch (IOException e) {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -34,6 +34,7 @@ public class DashboardServiceImpl implements DashboardService {
@Resource @Resource
private DashboardCollectService dashboardCollectService; private DashboardCollectService dashboardCollectService;
/** /**
* @param date format yyyy-MM-dd * @param date format yyyy-MM-dd
*/ */

View File

@@ -33,7 +33,6 @@ import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus; import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.message.MessageClientIDSetter; import org.apache.rocketmq.common.message.MessageClientIDSetter;
import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExt;
@@ -74,6 +73,9 @@ import java.util.stream.Collectors;
@Service @Service
public class MessageServiceImpl implements MessageService { public class MessageServiceImpl implements MessageService {
@Resource
private AutoCloseConsumerWrapper autoCloseConsumerWrapper;
private Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class); private Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
private static final Cache<String, List<QueueOffsetInfo>> CACHE = CacheBuilder.newBuilder() private static final Cache<String, List<QueueOffsetInfo>> CACHE = CacheBuilder.newBuilder()
@@ -128,8 +130,8 @@ public class MessageServiceImpl implements MessageService {
if (isEnableAcl) { if (isEnableAcl) {
rpcHook = new AclClientRPCHook(new SessionCredentials(configure.getAccessKey(), configure.getSecretKey())); rpcHook = new AclClientRPCHook(new SessionCredentials(configure.getAccessKey(), configure.getSecretKey()));
} }
AutoCloseConsumerWrapper consumerWrapper = new AutoCloseConsumerWrapper();
DefaultMQPullConsumer consumer = consumerWrapper.getConsumer(rpcHook, configure.isUseTLS()); DefaultMQPullConsumer consumer = autoCloseConsumerWrapper.getConsumer(rpcHook, configure.isUseTLS());
List<MessageView> messageViewList = Lists.newArrayList(); List<MessageView> messageViewList = Lists.newArrayList();
try { try {
String subExpression = "*"; String subExpression = "*";
@@ -262,8 +264,8 @@ public class MessageServiceImpl implements MessageService {
if (isEnableAcl) { if (isEnableAcl) {
rpcHook = new AclClientRPCHook(new SessionCredentials(configure.getAccessKey(), configure.getSecretKey())); rpcHook = new AclClientRPCHook(new SessionCredentials(configure.getAccessKey(), configure.getSecretKey()));
} }
AutoCloseConsumerWrapper consumerWrapper = new AutoCloseConsumerWrapper();
DefaultMQPullConsumer consumer = consumerWrapper.getConsumer(rpcHook, configure.isUseTLS()); DefaultMQPullConsumer consumer = autoCloseConsumerWrapper.getConsumer(rpcHook, configure.isUseTLS());
long total = 0; long total = 0;
List<QueueOffsetInfo> queueOffsetInfos = new ArrayList<>(); List<QueueOffsetInfo> queueOffsetInfos = new ArrayList<>();
@@ -402,8 +404,8 @@ public class MessageServiceImpl implements MessageService {
if (isEnableAcl) { if (isEnableAcl) {
rpcHook = new AclClientRPCHook(new SessionCredentials(configure.getAccessKey(), configure.getSecretKey())); rpcHook = new AclClientRPCHook(new SessionCredentials(configure.getAccessKey(), configure.getSecretKey()));
} }
AutoCloseConsumerWrapper consumerWrapper = new AutoCloseConsumerWrapper();
DefaultMQPullConsumer consumer = consumerWrapper.getConsumer(rpcHook, configure.isUseTLS()); DefaultMQPullConsumer consumer = autoCloseConsumerWrapper.getConsumer(rpcHook, configure.isUseTLS());
List<MessageView> messageViews = new ArrayList<>(); List<MessageView> messageViews = new ArrayList<>();
long offset = query.getPageNum() * query.getPageSize(); long offset = query.getPageNum() * query.getPageSize();
@@ -541,9 +543,9 @@ public class MessageServiceImpl implements MessageService {
} }
} }
public DefaultMQPullConsumer buildDefaultMQPullConsumer(RPCHook rpcHook, boolean useTLS) { // public DefaultMQPullConsumer buildDefaultMQPullConsumer(RPCHook rpcHook, boolean useTLS) {
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook); // DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook);
consumer.setUseTLS(useTLS); // consumer.setUseTLS(useTLS);
return consumer; // return consumer;
} // }
} }

View File

@@ -81,8 +81,7 @@ public class MonitorServiceImpl implements MonitorService {
private void writeDataJsonToFile(String path, String dataStr) { private void writeDataJsonToFile(String path, String dataStr) {
try { try {
MixAll.string2File(dataStr, path); MixAll.string2File(dataStr, path);
} } catch (Exception e) {
catch (Exception e) {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -33,8 +33,7 @@ public class ProducerServiceImpl implements ProducerService {
public ProducerConnection getProducerConnection(String producerGroup, String topic) { public ProducerConnection getProducerConnection(String producerGroup, String topic) {
try { try {
return mqAdminExt.examineProducerConnectionInfo(producerGroup, topic); return mqAdminExt.examineProducerConnectionInfo(producerGroup, topic);
} } catch (Exception e) {
catch (Exception e) {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -127,7 +127,7 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
try { try {
TopicConfigSerializeWrapper topicConfigSerializeWrapper = mqAdminExt.getAllTopicConfig(brokerAddr.getBrokerAddrs().get(0L), 10000L); TopicConfigSerializeWrapper topicConfigSerializeWrapper = mqAdminExt.getAllTopicConfig(brokerAddr.getBrokerAddrs().get(0L), 10000L);
for (TopicConfig topicConfig : topicConfigSerializeWrapper.getTopicConfigTable().values()) { for (TopicConfig topicConfig : topicConfigSerializeWrapper.getTopicConfigTable().values()) {
TopicTypeMeta topicType = classifyTopicType(topicConfig.getTopicName(), topicConfigSerializeWrapper.getTopicConfigTable().get(topicConfig.getTopicName()).getAttributes(),sysTopics.getTopicList()); TopicTypeMeta topicType = classifyTopicType(topicConfig.getTopicName(), topicConfigSerializeWrapper.getTopicConfigTable().get(topicConfig.getTopicName()).getAttributes(), sysTopics.getTopicList());
if (names.contains(topicType.getTopicName())) { if (names.contains(topicType.getTopicName())) {
continue; continue;
} }
@@ -149,7 +149,7 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
return new TopicTypeList(names, messageTypes); return new TopicTypeList(names, messageTypes);
} }
private TopicTypeMeta classifyTopicType(String topicName, Map<String,String> attributes, Set<String> sysTopics) { private TopicTypeMeta classifyTopicType(String topicName, Map<String, String> attributes, Set<String> sysTopics) {
TopicTypeMeta topicType = new TopicTypeMeta(); TopicTypeMeta topicType = new TopicTypeMeta();
topicType.setTopicName(topicName); topicType.setTopicName(topicName);

View File

@@ -17,29 +17,26 @@
package org.apache.rocketmq.dashboard.service.impl; package org.apache.rocketmq.dashboard.service.impl;
import jakarta.annotation.Resource;
import org.apache.rocketmq.auth.authentication.enums.UserType; import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.dashboard.admin.UserMQAdminPoolManager; import org.apache.rocketmq.dashboard.admin.UserMQAdminPoolManager;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.model.User; import org.apache.rocketmq.dashboard.model.User;
import org.apache.rocketmq.dashboard.service.UserService; import org.apache.rocketmq.dashboard.service.UserService;
import org.apache.rocketmq.dashboard.service.provider.UserInfoProvider; import org.apache.rocketmq.dashboard.service.strategy.UserContext;
import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.apache.rocketmq.tools.admin.MQAdminExt; import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service @Service
public class UserServiceImpl implements UserService { public class UserServiceImpl implements UserService {
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Resource
private RMQConfigure configure;
@Autowired @Autowired
private UserInfoProvider userInfoProvider; private UserContext userContext;
@Autowired @Autowired
private UserMQAdminPoolManager userMQAdminPoolManager; private UserMQAdminPoolManager userMQAdminPoolManager;
@@ -47,7 +44,7 @@ public class UserServiceImpl implements UserService {
@Override @Override
public User queryByName(String name) { public User queryByName(String name) {
UserInfo userInfo = userInfoProvider.getUserInfoByUsername(name); UserInfo userInfo = userContext.queryByUsername(name);
if (userInfo == null) { if (userInfo == null) {
return null; return null;
} }

View File

@@ -23,6 +23,7 @@ import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@@ -32,9 +33,10 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@Component
public class AutoCloseConsumerWrapper { public class AutoCloseConsumerWrapper {
private final Logger logger = LoggerFactory.getLogger(GlobalRestfulResponseBodyAdvice.class); private final Logger logger = LoggerFactory.getLogger(AutoCloseConsumerWrapper.class);
private static final AtomicReference<DefaultMQPullConsumer> CONSUMER_REF = new AtomicReference<>(); private static final AtomicReference<DefaultMQPullConsumer> CONSUMER_REF = new AtomicReference<>();
private final AtomicBoolean isTaskScheduled = new AtomicBoolean(false); private final AtomicBoolean isTaskScheduled = new AtomicBoolean(false);
@@ -77,7 +79,10 @@ public class AutoCloseConsumerWrapper {
protected DefaultMQPullConsumer createNewConsumer(RPCHook rpcHook, Boolean useTLS) { protected DefaultMQPullConsumer createNewConsumer(RPCHook rpcHook, Boolean useTLS) {
return new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook) { return new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook) {
{ setUseTLS(useTLS); } }; {
setUseTLS(useTLS);
}
};
} }
private void startIdleCheckTask() { private void startIdleCheckTask() {

View File

@@ -37,8 +37,7 @@ public class GlobalExceptionHandler {
if (ex instanceof ServiceException) { if (ex instanceof ServiceException) {
logger.error("Occur service exception: {}", ex.getMessage()); logger.error("Occur service exception: {}", ex.getMessage());
value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage()); value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage());
} } else {
else {
logger.error("op=global_exception_handler_print_error", ex); logger.error("op=global_exception_handler_print_error", ex);
value = new JsonResult<Object>(-1, ex.getMessage() == null ? ex.toString() : ex.getMessage()); value = new JsonResult<Object>(-1, ex.getMessage() == null ? ex.toString() : ex.getMessage());
} }

View File

@@ -46,9 +46,8 @@ public class GlobalRestfulResponseBodyAdvice implements ResponseBodyAdvice<Objec
} }
JsonResult value; JsonResult value;
if (obj instanceof JsonResult) { if (obj instanceof JsonResult) {
value = (JsonResult)obj; value = (JsonResult) obj;
} } else {
else {
value = new JsonResult(obj); value = new JsonResult(obj);
} }
return value; return value;

View File

@@ -87,8 +87,7 @@ public class DashboardCollectTask {
CollectTaskRunnble collectTask = new CollectTaskRunnble(topic, mqAdminExt, dashboardCollectService); CollectTaskRunnble collectTask = new CollectTaskRunnble(topic, mqAdminExt, dashboardCollectService);
collectExecutor.submit(collectTask); collectExecutor.submit(collectTask);
} }
} } catch (Exception err) {
catch (Exception err) {
Throwables.throwIfUnchecked(err); Throwables.throwIfUnchecked(err);
throw new RuntimeException(err); throw new RuntimeException(err);
} }
@@ -100,7 +99,6 @@ public class DashboardCollectTask {
} }
@Scheduled(cron = "0 0/1 * * * ?") @Scheduled(cron = "0 0/1 * * * ?")
public void collectBroker() { public void collectBroker() {
if (!rmqConfigure.isEnableDashBoardCollect()) { if (!rmqConfigure.isEnableDashBoardCollect()) {
@@ -139,8 +137,7 @@ public class DashboardCollectTask {
dashboardCollectService.getBrokerMap().put(entry.getValue(), list); dashboardCollectService.getBrokerMap().put(entry.getValue(), list);
} }
log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap())); log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
} } catch (Exception e) {
catch (Exception e) {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -152,12 +149,10 @@ public class DashboardCollectTask {
} }
try { try {
return mqAdminExt.fetchBrokerRuntimeStats(brokerAddr); return mqAdminExt.fetchBrokerRuntimeStats(brokerAddr);
} } catch (Exception e) {
catch (Exception e) {
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} } catch (InterruptedException e1) {
catch (InterruptedException e1) {
Throwables.throwIfUnchecked(e1); Throwables.throwIfUnchecked(e1);
throw new RuntimeException(e1); throw new RuntimeException(e1);
} }
@@ -189,16 +184,14 @@ public class DashboardCollectTask {
Map<String, List<String>> topicFileMap; Map<String, List<String>> topicFileMap;
if (brokerFile.exists()) { if (brokerFile.exists()) {
brokerFileMap = dashboardCollectService.jsonDataFile2map(brokerFile); brokerFileMap = dashboardCollectService.jsonDataFile2map(brokerFile);
} } else {
else {
brokerFileMap = Maps.newHashMap(); brokerFileMap = Maps.newHashMap();
Files.createParentDirs(brokerFile); Files.createParentDirs(brokerFile);
} }
if (topicFile.exists()) { if (topicFile.exists()) {
topicFileMap = dashboardCollectService.jsonDataFile2map(topicFile); topicFileMap = dashboardCollectService.jsonDataFile2map(topicFile);
} } else {
else {
topicFileMap = Maps.newHashMap(); topicFileMap = Maps.newHashMap();
Files.createParentDirs(topicFile); Files.createParentDirs(topicFile);
} }
@@ -211,8 +204,7 @@ public class DashboardCollectTask {
log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap())); log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
log.debug("Topic Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getTopicMap().asMap())); log.debug("Topic Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getTopicMap().asMap()));
} } catch (IOException e) {
catch (IOException e) {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -224,8 +216,7 @@ public class DashboardCollectTask {
Map<String, List<String>> resultMap = Maps.newHashMap(); Map<String, List<String>> resultMap = Maps.newHashMap();
if (fileMap.size() == 0) { if (fileMap.size() == 0) {
resultMap = newMap; resultMap = newMap;
} } else {
else {
for (Map.Entry<String, List<String>> entry : fileMap.entrySet()) { for (Map.Entry<String, List<String>> entry : fileMap.entrySet()) {
List<String> oldList = entry.getValue(); List<String> oldList = entry.getValue();
List<String> newList = newMap.get(entry.getKey()); List<String> newList = newMap.get(entry.getKey());

View File

@@ -38,7 +38,7 @@ public class MonitorTask {
@Resource @Resource
private ConsumerService consumerService; private ConsumerService consumerService;
// @Scheduled(cron = "* * * * * ?") // @Scheduled(cron = "* * * * * ?")
public void scanProblemConsumeGroup() { public void scanProblemConsumeGroup() {
for (Map.Entry<String, ConsumerMonitorConfig> configEntry : monitorService.queryConsumerMonitorConfig().entrySet()) { for (Map.Entry<String, ConsumerMonitorConfig> configEntry : monitorService.queryConsumerMonitorConfig().entrySet()) {
GroupConsumeInfo consumeInfo = consumerService.queryGroup(configEntry.getKey(), null); GroupConsumeInfo consumeInfo = consumerService.queryGroup(configEntry.getKey(), null);

View File

@@ -34,7 +34,7 @@ public class ExcelUtil {
String sheetName, Class clazz) throws Exception { String sheetName, Class clazz) throws Exception {
WriteCellStyle headWriteCellStyle = new WriteCellStyle(); WriteCellStyle headWriteCellStyle = new WriteCellStyle();
WriteFont writeFont = new WriteFont(); WriteFont writeFont = new WriteFont();
writeFont.setFontHeightInPoints((short)12); writeFont.setFontHeightInPoints((short) 12);
writeFont.setFontName("Microsoft YaHei UI"); writeFont.setFontName("Microsoft YaHei UI");
headWriteCellStyle.setWriteFont(writeFont); headWriteCellStyle.setWriteFont(writeFont);
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

View File

@@ -55,8 +55,7 @@ public class JsonUtil {
public static void writeValue(Writer writer, Object obj) { public static void writeValue(Writer writer, Object obj) {
try { try {
objectMapper.writeValue(writer, obj); objectMapper.writeValue(writer, obj);
} } catch (IOException e) {
catch (IOException e) {
Throwables.propagateIfPossible(e); Throwables.propagateIfPossible(e);
} }
} }
@@ -67,9 +66,8 @@ public class JsonUtil {
} }
try { try {
return src instanceof String ? (String)src : objectMapper.writeValueAsString(src); return src instanceof String ? (String) src : objectMapper.writeValueAsString(src);
} } catch (Exception e) {
catch (Exception e) {
logger.error("Parse Object to String error src=" + src, e); logger.error("Parse Object to String error src=" + src, e);
return null; return null;
} }
@@ -81,9 +79,8 @@ public class JsonUtil {
} }
try { try {
return src instanceof byte[] ? (byte[])src : objectMapper.writeValueAsBytes(src); return src instanceof byte[] ? (byte[]) src : objectMapper.writeValueAsBytes(src);
} } catch (Exception e) {
catch (Exception e) {
logger.error("Parse Object to byte[] error", e); logger.error("Parse Object to byte[] error", e);
return null; return null;
} }
@@ -95,9 +92,8 @@ public class JsonUtil {
} }
str = escapesSpecialChar(str); str = escapesSpecialChar(str);
try { try {
return clazz.equals(String.class) ? (T)str : objectMapper.readValue(str, clazz); return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
} } catch (Exception e) {
catch (Exception e) {
logger.error("Parse String to Object error\nString: {}\nClass<T>: {}\nError: {}", str, clazz.getName(), e); logger.error("Parse String to Object error\nString: {}\nClass<T>: {}\nError: {}", str, clazz.getName(), e);
return null; return null;
} }
@@ -108,9 +104,8 @@ public class JsonUtil {
return null; return null;
} }
try { try {
return clazz.equals(byte[].class) ? (T)bytes : objectMapper.readValue(bytes, clazz); return clazz.equals(byte[].class) ? (T) bytes : objectMapper.readValue(bytes, clazz);
} } catch (Exception e) {
catch (Exception e) {
logger.error("Parse byte[] to Object error\nbyte[]: {}\nClass<T>: {}\nError: {}", bytes, clazz.getName(), e); logger.error("Parse byte[] to Object error\nbyte[]: {}\nClass<T>: {}\nError: {}", bytes, clazz.getName(), e);
return null; return null;
} }
@@ -122,9 +117,8 @@ public class JsonUtil {
} }
str = escapesSpecialChar(str); str = escapesSpecialChar(str);
try { try {
return (T)(typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference)); return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
} } catch (Exception e) {
catch (Exception e) {
logger.error("Parse String to Object error\nString: {}\nTypeReference<T>: {}\nError: {}", str, logger.error("Parse String to Object error\nString: {}\nTypeReference<T>: {}\nError: {}", str,
typeReference.getType(), e); typeReference.getType(), e);
return null; return null;
@@ -136,10 +130,9 @@ public class JsonUtil {
return null; return null;
} }
try { try {
return (T)(typeReference.getType().equals(byte[].class) ? bytes : objectMapper.readValue(bytes, return (T) (typeReference.getType().equals(byte[].class) ? bytes : objectMapper.readValue(bytes,
typeReference)); typeReference));
} } catch (Exception e) {
catch (Exception e) {
logger.error("Parse byte[] to Object error\nbyte[]: {}\nTypeReference<T>: {}\nError: {}", bytes, logger.error("Parse byte[] to Object error\nbyte[]: {}\nTypeReference<T>: {}\nError: {}", bytes,
typeReference.getType(), e); typeReference.getType(), e);
return null; return null;

View File

@@ -43,8 +43,8 @@
</appender> </appender>
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT"/>
<appender-ref ref="FILE" /> <appender-ref ref="FILE"/>
</root> </root>
</configuration> </configuration>

View File

@@ -14,13 +14,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# This file supports hot change, any change will be auto-reloaded without Dashboard restarting. # This file supports hot change, any change will be auto-reloaded without Dashboard restarting.
# Format: a user per line, username=password[,N] #N is optional, 0 (Normal User); 1 (Admin) # Format: a user per line, username=password[,N] #N is optional, 0 (Normal User); 1 (Admin)
# Define Admin # Define Admin
admin=admin,1 super=admin,1
# Define Users # Define Users
user1=user1 user1=user
user2=user2 user2=user