Compare commits

...

4 Commits

Author SHA1 Message Date
Crazylychee
07793d8aae [ISSUE #329] Add Frontend Proxy Component Support (#336) 2025-07-05 20:53:57 +08:00
Crazylychee
4b9ed97f8f [ISSUE #332] Add configuration options for login information, supporting ACL or file storage (#333) 2025-07-05 20:51:42 +08:00
Crazylychee
ff73529a75 [ISSUE #330] Format code and update the doc (#334) 2025-07-05 20:50:36 +08:00
Crazylychee
706082c62f [ISSUE #331] Fix failing tests (#335) 2025-07-05 20:50:08 +08:00
129 changed files with 2675 additions and 2350 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,13 +18,13 @@
<!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>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -17,19 +17,19 @@
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";
function App() { function App() {
const {currentTheme} = useTheme(); const {currentTheme} = useTheme();
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

@@ -49,7 +49,7 @@ const remoteApi = {
if (_redirectHandler) { if (_redirectHandler) {
_redirectHandler(); // 如果设置了重定向处理函数,则调用它 _redirectHandler(); // 如果设置了重定向处理函数,则调用它
} }
return { __isRedirectHandled: true }; return {__isRedirectHandled: true};
} }
return response; return response;
@@ -77,24 +77,24 @@ const remoteApi = {
listUsers: async (brokerAddress) => { listUsers: async (brokerAddress) => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress); if (brokerAddress) params.append('brokerAddress', brokerAddress);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/listUsers?${params.toString()}`)); const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/acls.query?${params.toString()}`));
return await response.json(); return await response.json();
}, },
createUser: async (brokerAddress, userInfo) => { createUser: async (brokerAddress, userInfo) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser'), { const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser.do'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ brokerAddress, userInfo }) body: JSON.stringify({brokerAddress, userInfo})
}); });
return await response.json(); // 返回字符串消息 return await response.json(); // 返回字符串消息
}, },
updateUser: async (brokerAddress, userInfo) => { updateUser: async (brokerAddress, userInfo) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateUser'), { const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateUser.do'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ brokerAddress, userInfo }) body: JSON.stringify({brokerAddress, userInfo})
}); });
return await response.json(); return await response.json();
}, },
@@ -103,7 +103,7 @@ const remoteApi = {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress); if (brokerAddress) params.append('brokerAddress', brokerAddress);
params.append('username', username); params.append('username', username);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteUser?${params.toString()}`), { const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteUser.do?${params.toString()}`), {
method: 'DELETE' method: 'DELETE'
}); });
return await response.json(); return await response.json();
@@ -114,24 +114,24 @@ const remoteApi = {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress); if (brokerAddress) params.append('brokerAddress', brokerAddress);
if (searchParam) params.append('searchParam', searchParam); if (searchParam) params.append('searchParam', searchParam);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/listAcls?${params.toString()}`)); const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/acls.query?${params.toString()}`));
return await response.json(); return await response.json();
}, },
createAcl: async (brokerAddress, subject, policies) => { createAcl: async (brokerAddress, subject, policies) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createAcl'), { const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createAcl.do'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ brokerAddress, subject, policies }) body: JSON.stringify({brokerAddress, subject, policies})
}); });
return await response.json(); return await response.json();
}, },
updateAcl: async (brokerAddress, subject, policies) => { updateAcl: async (brokerAddress, subject, policies) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateAcl'), { const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateAcl.do'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ brokerAddress, subject, policies }) body: JSON.stringify({brokerAddress, subject, policies})
}); });
return await response.json(); return await response.json();
}, },
@@ -141,7 +141,7 @@ const remoteApi = {
if (brokerAddress) params.append('brokerAddress', brokerAddress); if (brokerAddress) params.append('brokerAddress', brokerAddress);
params.append('subject', subject); params.append('subject', subject);
if (resource) params.append('resource', resource); if (resource) params.append('resource', resource);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl?${params.toString()}`), { const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl.do?${params.toString()}`), {
method: 'DELETE' method: 'DELETE'
}); });
return await response.json(); return await response.json();
@@ -159,7 +159,7 @@ const remoteApi = {
return data return data
} catch (error) { } catch (error) {
console.error("Error querying message by ID:", error); console.error("Error querying message by ID:", error);
callback({ status: 1, errMsg: "Failed to query message by ID" }); callback({status: 1, errMsg: "Failed to query message by ID"});
} }
}, },
@@ -197,7 +197,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error("Error querying DLQ messages by consumer group:", error); console.error("Error querying DLQ messages by consumer group:", error);
return { status: 1, errMsg: "Failed to query DLQ messages by consumer group" }; return {status: 1, errMsg: "Failed to query DLQ messages by consumer group"};
} }
}, },
resendDlqMessage: async (msgId, consumerGroup, topic) => { resendDlqMessage: async (msgId, consumerGroup, topic) => {
@@ -217,7 +217,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error("Error resending DLQ message:", error); console.error("Error resending DLQ message:", error);
return { status: 1, errMsg: "Failed to resend DLQ message" }; return {status: 1, errMsg: "Failed to resend DLQ message"};
} }
}, },
exportDlqMessage: async (msgId, consumerGroup) => { exportDlqMessage: async (msgId, consumerGroup) => {
@@ -236,7 +236,7 @@ const remoteApi = {
if (!newWindow) { if (!newWindow) {
// 浏览器可能会阻止弹窗,需要用户允许 // 浏览器可能会阻止弹窗,需要用户允许
return { status: 1, errMsg: "Failed to open new window. Please allow pop-ups for this site." }; return {status: 1, errMsg: "Failed to open new window. Please allow pop-ups for this site."};
} }
// 2. 将 JSON 数据格式化后写入新窗口 // 2. 将 JSON 数据格式化后写入新窗口
@@ -247,10 +247,10 @@ const remoteApi = {
newWindow.document.write('</body></html>'); newWindow.document.write('</body></html>');
newWindow.document.close(); // 关闭文档流,确保内容显示 newWindow.document.close(); // 关闭文档流,确保内容显示
return { status: 0, msg: "导出请求成功,内容已在新页面显示" }; return {status: 0, msg: "导出请求成功,内容已在新页面显示"};
} catch (error) { } catch (error) {
console.error("Error exporting DLQ message:", error); console.error("Error exporting DLQ message:", error);
return { status: 1, errMsg: "Failed to export DLQ message: " + error.message }; return {status: 1, errMsg: "Failed to export DLQ message: " + error.message};
} }
}, },
@@ -267,7 +267,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error("Error batch resending DLQ messages:", error); console.error("Error batch resending DLQ messages:", error);
return { status: 1, errMsg: "Failed to batch resend DLQ messages" }; return {status: 1, errMsg: "Failed to batch resend DLQ messages"};
} }
}, },
@@ -372,18 +372,18 @@ const remoteApi = {
callback(data); callback(data);
} catch (error) { } catch (error) {
console.error("Error fetching producer connection list:", error); console.error("Error fetching producer connection list:", error);
callback({ status: 1, errMsg: "Failed to fetch producer connection list" }); // Simulate error response callback({status: 1, errMsg: "Failed to fetch producer connection list"}); // Simulate error response
} }
}, },
queryConsumerGroupList: async (skipSysGroup = false) => { queryConsumerGroupList: async (skipSysGroup, address) => {
try { try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/groupList.query?skipSysGroup=${skipSysGroup}`)); const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/groupList.query?skipSysGroup=${skipSysGroup}&address=${address}`));
const data = await response.json(); const data = await response.json();
return data; return data;
} catch (error) { } catch (error) {
console.error("Error fetching consumer group list:", error); console.error("Error fetching consumer group list:", error);
return { status: 1, errMsg: "Failed to fetch consumer group list" }; return {status: 1, errMsg: "Failed to fetch consumer group list"};
} }
}, },
@@ -394,7 +394,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error refreshing consumer group ${consumerGroup}:`, error); console.error(`Error refreshing consumer group ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to refresh consumer group ${consumerGroup}` }; return {status: 1, errMsg: `Failed to refresh consumer group ${consumerGroup}`};
} }
}, },
@@ -405,7 +405,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error("Error refreshing all consumer groups:", error); console.error("Error refreshing all consumer groups:", error);
return { status: 1, errMsg: "Failed to refresh all consumer groups" }; return {status: 1, errMsg: "Failed to refresh all consumer groups"};
} }
}, },
@@ -416,7 +416,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching monitor config for ${consumeGroupName}:`, error); console.error(`Error fetching monitor config for ${consumeGroupName}:`, error);
return { status: 1, errMsg: `Failed to fetch monitor config for ${consumeGroupName}` }; return {status: 1, errMsg: `Failed to fetch monitor config for ${consumeGroupName}`};
} }
}, },
@@ -428,13 +428,13 @@ const remoteApi = {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ consumeGroupName, minCount, maxDiffTotal }) body: JSON.stringify({consumeGroupName, minCount, maxDiffTotal})
}); });
const data = await response.json(); const data = await response.json();
return data; return data;
} catch (error) { } catch (error) {
console.error("Error creating or updating consumer monitor:", error); console.error("Error creating or updating consumer monitor:", error);
return { status: 1, errMsg: "Failed to create or update consumer monitor" }; return {status: 1, errMsg: "Failed to create or update consumer monitor"};
} }
}, },
@@ -445,7 +445,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching broker name list for ${consumerGroup}:`, error); console.error(`Error fetching broker name list for ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch broker name list for ${consumerGroup}` }; return {status: 1, errMsg: `Failed to fetch broker name list for ${consumerGroup}`};
} }
}, },
@@ -456,13 +456,13 @@ const remoteApi = {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ groupName, brokerNameList }) body: JSON.stringify({groupName, brokerNameList})
}); });
const data = await response.json(); const data = await response.json();
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error deleting consumer group ${groupName}:`, error); console.error(`Error deleting consumer group ${groupName}:`, error);
return { status: 1, errMsg: `Failed to delete consumer group ${groupName}` }; return {status: 1, errMsg: `Failed to delete consumer group ${groupName}`};
} }
}, },
@@ -473,7 +473,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching consumer config for ${consumerGroup}:`, error); console.error(`Error fetching consumer config for ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch consumer config for ${consumerGroup}` }; return {status: 1, errMsg: `Failed to fetch consumer config for ${consumerGroup}`};
} }
}, },
@@ -490,7 +490,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error("Error creating or updating consumer:", error); console.error("Error creating or updating consumer:", error);
return { status: 1, errMsg: "Failed to create or update consumer" }; return {status: 1, errMsg: "Failed to create or update consumer"};
} }
}, },
@@ -501,7 +501,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching topics for consumer group ${consumerGroup}:`, error); console.error(`Error fetching topics for consumer group ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch topics for consumer group ${consumerGroup}` }; return {status: 1, errMsg: `Failed to fetch topics for consumer group ${consumerGroup}`};
} }
}, },
@@ -512,7 +512,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching consumer connections for ${consumerGroup}:`, error); console.error(`Error fetching consumer connections for ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch consumer connections for ${consumerGroup}` }; return {status: 1, errMsg: `Failed to fetch consumer connections for ${consumerGroup}`};
} }
}, },
@@ -523,7 +523,7 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching running info for client ${clientId} in group ${consumerGroup}:`, error); console.error(`Error fetching running info for client ${clientId} in group ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch running info for client ${clientId} in group ${consumerGroup}` }; return {status: 1, errMsg: `Failed to fetch running info for client ${clientId} in group ${consumerGroup}`};
} }
}, },
queryTopicList: async () => { queryTopicList: async () => {
@@ -532,7 +532,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching topic list:", error); console.error("Error fetching topic list:", error);
return { status: 1, errMsg: "Failed to fetch topic list" }; return {status: 1, errMsg: "Failed to fetch topic list"};
} }
}, },
@@ -549,7 +549,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error deleting topic:", error); console.error("Error deleting topic:", error);
return { status: 1, errMsg: "Failed to delete topic" }; return {status: 1, errMsg: "Failed to delete topic"};
} }
}, },
@@ -559,7 +559,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching topic stats:", error); console.error("Error fetching topic stats:", error);
return { status: 1, errMsg: "Failed to fetch topic stats" }; return {status: 1, errMsg: "Failed to fetch topic stats"};
} }
}, },
@@ -569,7 +569,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching topic route:", error); console.error("Error fetching topic route:", error);
return { status: 1, errMsg: "Failed to fetch topic route" }; return {status: 1, errMsg: "Failed to fetch topic route"};
} }
}, },
@@ -579,7 +579,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching topic consumers:", error); console.error("Error fetching topic consumers:", error);
return { status: 1, errMsg: "Failed to fetch topic consumers" }; return {status: 1, errMsg: "Failed to fetch topic consumers"};
} }
}, },
@@ -589,7 +589,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching consumer groups:", error); console.error("Error fetching consumer groups:", error);
return { status: 1, errMsg: "Failed to fetch consumer groups" }; return {status: 1, errMsg: "Failed to fetch consumer groups"};
} }
}, },
@@ -599,7 +599,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching topic config:", error); console.error("Error fetching topic config:", error);
return { status: 1, errMsg: "Failed to fetch topic config" }; return {status: 1, errMsg: "Failed to fetch topic config"};
} }
}, },
@@ -609,7 +609,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching cluster list:", error); console.error("Error fetching cluster list:", error);
return { status: 1, errMsg: "Failed to fetch cluster list" }; return {status: 1, errMsg: "Failed to fetch cluster list"};
} }
}, },
@@ -625,7 +625,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error creating/updating topic:", error); console.error("Error creating/updating topic:", error);
return { status: 1, errMsg: "Failed to create/update topic" }; return {status: 1, errMsg: "Failed to create/update topic"};
} }
}, },
@@ -641,7 +641,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error resetting consumer offset:", error); console.error("Error resetting consumer offset:", error);
return { status: 1, errMsg: "Failed to reset consumer offset" }; return {status: 1, errMsg: "Failed to reset consumer offset"};
} }
}, },
@@ -657,7 +657,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error skipping message accumulate:", error); console.error("Error skipping message accumulate:", error);
return { status: 1, errMsg: "Failed to skip message accumulate" }; return {status: 1, errMsg: "Failed to skip message accumulate"};
} }
}, },
@@ -673,7 +673,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error sending topic message:", error); console.error("Error sending topic message:", error);
return { status: 1, errMsg: "Failed to send topic message" }; return {status: 1, errMsg: "Failed to send topic message"};
} }
}, },
@@ -684,12 +684,12 @@ const remoteApi = {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ brokerName, topic }) body: JSON.stringify({brokerName, topic})
}); });
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error deleting topic by broker:", error); console.error("Error deleting topic by broker:", error);
return { status: 1, errMsg: "Failed to delete topic by broker" }; return {status: 1, errMsg: "Failed to delete topic by broker"};
} }
}, },
@@ -700,7 +700,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error fetching ops home page data:", error); console.error("Error fetching ops home page data:", error);
return { status: 1, errMsg: "Failed to fetch ops home page data" }; return {status: 1, errMsg: "Failed to fetch ops home page data"};
} }
}, },
@@ -712,7 +712,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error updating NameServer address:", error); console.error("Error updating NameServer address:", error);
return { status: 1, errMsg: "Failed to update NameServer address" }; return {status: 1, errMsg: "Failed to update NameServer address"};
} }
}, },
@@ -724,7 +724,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error adding NameServer address:", error); console.error("Error adding NameServer address:", error);
return { status: 1, errMsg: "Failed to add NameServer address" }; return {status: 1, errMsg: "Failed to add NameServer address"};
} }
}, },
@@ -736,7 +736,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error updating VIP Channel status:", error); console.error("Error updating VIP Channel status:", error);
return { status: 1, errMsg: "Failed to update VIP Channel status" }; return {status: 1, errMsg: "Failed to update VIP Channel status"};
} }
}, },
@@ -748,7 +748,7 @@ const remoteApi = {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Error updating TLS status:", error); console.error("Error updating TLS status:", error);
return { status: 1, errMsg: "Failed to update TLS status" }; return {status: 1, errMsg: "Failed to update TLS status"};
} }
}, },
@@ -759,7 +759,7 @@ const remoteApi = {
callback(data); callback(data);
} catch (error) { } catch (error) {
console.error("Error fetching cluster list:", error); console.error("Error fetching cluster list:", error);
callback({ status: 1, errMsg: "Failed to fetch cluster list" }); callback({status: 1, errMsg: "Failed to fetch cluster list"});
} }
}, },
@@ -767,16 +767,16 @@ const remoteApi = {
try { try {
const url = new URL(remoteApi.buildUrl('/dashboard/broker.query')); const url = new URL(remoteApi.buildUrl('/dashboard/broker.query'));
url.searchParams.append('date', date); url.searchParams.append('date', date);
const response = await remoteApi._fetch(url.toString(), { signal: AbortSignal.timeout(15000) }); // 15s timeout const response = await remoteApi._fetch(url.toString(), {signal: AbortSignal.timeout(15000)}); // 15s timeout
const data = await response.json(); const data = await response.json();
callback(data); callback(data);
} catch (error) { } catch (error) {
if (error.name === 'TimeoutError') { if (error.name === 'TimeoutError') {
console.error("Broker history data request timed out:", error); console.error("Broker history data request timed out:", error);
callback({ status: 1, errMsg: "Request timed out for broker history data" }); callback({status: 1, errMsg: "Request timed out for broker history data"});
} else { } else {
console.error("Error fetching broker history data:", error); console.error("Error fetching broker history data:", error);
callback({ status: 1, errMsg: "Failed to fetch broker history data" }); callback({status: 1, errMsg: "Failed to fetch broker history data"});
} }
} }
}, },
@@ -786,32 +786,32 @@ const remoteApi = {
const url = new URL(remoteApi.buildUrl('/dashboard/topic.query')); const url = new URL(remoteApi.buildUrl('/dashboard/topic.query'));
url.searchParams.append('date', date); url.searchParams.append('date', date);
url.searchParams.append('topicName', topicName); url.searchParams.append('topicName', topicName);
const response = await remoteApi._fetch(url.toString(), { signal: AbortSignal.timeout(15000) }); // 15s timeout const response = await remoteApi._fetch(url.toString(), {signal: AbortSignal.timeout(15000)}); // 15s timeout
const data = await response.json(); const data = await response.json();
callback(data); callback(data);
} catch (error) { } catch (error) {
if (error.name === 'TimeoutError') { if (error.name === 'TimeoutError') {
console.error("Topic history data request timed out:", error); console.error("Topic history data request timed out:", error);
callback({ status: 1, errMsg: "Request timed out for topic history data" }); callback({status: 1, errMsg: "Request timed out for topic history data"});
} else { } else {
console.error("Error fetching topic history data:", error); console.error("Error fetching topic history data:", error);
callback({ status: 1, errMsg: "Failed to fetch topic history data" }); callback({status: 1, errMsg: "Failed to fetch topic history data"});
} }
} }
}, },
queryTopicCurrentData: async (callback) => { queryTopicCurrentData: async (callback) => {
try { try {
const response = await remoteApi._fetch(remoteApi.buildUrl('/dashboard/topicCurrent.query'), { signal: AbortSignal.timeout(15000) }); // 15s timeout const response = await remoteApi._fetch(remoteApi.buildUrl('/dashboard/topicCurrent.query'), {signal: AbortSignal.timeout(15000)}); // 15s timeout
const data = await response.json(); const data = await response.json();
callback(data); callback(data);
} catch (error) { } catch (error) {
if (error.name === 'TimeoutError') { if (error.name === 'TimeoutError') {
console.error("Topic current data request timed out:", error); console.error("Topic current data request timed out:", error);
callback({ status: 1, errMsg: "Request timed out for topic current data" }); callback({status: 1, errMsg: "Request timed out for topic current data"});
} else { } else {
console.error("Error fetching topic current data:", error); console.error("Error fetching topic current data:", error);
callback({ status: 1, errMsg: "Failed to fetch topic current data" }); callback({status: 1, errMsg: "Failed to fetch topic current data"});
} }
} }
}, },
@@ -825,7 +825,7 @@ const remoteApi = {
callback(data); callback(data);
} catch (error) { } catch (error) {
console.error("Error fetching broker config:", error); console.error("Error fetching broker config:", error);
callback({ status: 1, errMsg: "Failed to fetch broker config" }); callback({status: 1, errMsg: "Failed to fetch broker config"});
} }
}, },
@@ -839,7 +839,7 @@ const remoteApi = {
callback(data); callback(data);
} catch (error) { } catch (error) {
console.error("Error fetching proxy home page:", error); console.error("Error fetching proxy home page:", error);
callback({ status: 1, errMsg: "Failed to fetch proxy home page" }); callback({status: 1, errMsg: "Failed to fetch proxy home page"});
} }
}, },
@@ -850,14 +850,14 @@ const remoteApi = {
try { try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/proxy/addProxyAddr.do"), { const response = await remoteApi._fetch(remoteApi.buildUrl("/proxy/addProxyAddr.do"), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({ newProxyAddr }).toString() body: new URLSearchParams({newProxyAddr}).toString()
}); });
const data = await response.json(); const data = await response.json();
callback(data); callback(data);
} catch (error) { } catch (error) {
console.error("Error adding proxy address:", error); console.error("Error adding proxy address:", error);
callback({ status: 1, errMsg: "Failed to add proxy address" }); callback({status: 1, errMsg: "Failed to add proxy address"});
} }
}, },
login: async (username, password) => { login: async (username, password) => {
@@ -882,19 +882,19 @@ const remoteApi = {
return data; return data;
} catch (error) { } catch (error) {
console.error("Error logging in:", error); console.error("Error logging in:", error);
return { status: 1, errMsg: "Failed to log in" }; return {status: 1, errMsg: "Failed to log in"};
} }
}, },
logout: async () => { logout: async () => {
try { try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/login/logout.do"),{ const response = await remoteApi._fetch(remoteApi.buildUrl("/login/logout.do"), {
method: 'POST' method: 'POST'
}); });
return await response.json() return await response.json()
}catch (error) { } catch (error) {
console.error("Error logging out:", error); console.error("Error logging out:", error);
return { status: 1, errMsg: "Failed to log out" }; return {status: 1, errMsg: "Failed to log out"};
} }
} }
}; };
@@ -939,4 +939,4 @@ const tools = {
} }
}; };
export { remoteApi, tools }; export {remoteApi, tools};

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,84 +60,85 @@ 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
<Row justify="center"> {/* 使用 Row 居中内容 */} orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>}
<Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */} <Row justify="center"> {/* 使用 Row 居中内容 */}
<Form <Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */}
form={form} <Form
layout="horizontal" form={form}
labelCol={{ span: 8 }} layout="horizontal"
wrapperCol={{ span: 16 }} labelCol={{span: 8}}
wrapperCol={{span: 16}}
>
<Form.Item label={t.CLUSTER_NAME} name="clusterNameList">
<Select
mode="multiple"
disabled={bIsUpdate}
placeholder={t.SELECT_CLUSTER_NAME}
options={allClusterNameList.map(name => ({value: name, label: name}))}
/>
</Form.Item>
<Form.Item label="BROKER_NAME" name="brokerNameList">
<Select
mode="multiple"
disabled={bIsUpdate}
placeholder={t.SELECT_BROKER_NAME}
options={allBrokerNameList.map(name => ({value: name, label: name}))}
/>
</Form.Item>
<Form.Item
label={t.TOPIC_NAME}
name="topicName"
rules={[{required: true, message: `${t.TOPIC_NAME}${t.CANNOT_BE_EMPTY}`}]}
> >
<Form.Item label={t.CLUSTER_NAME} name="clusterNameList"> <Input disabled={bIsUpdate}/>
<Select </Form.Item>
mode="multiple" <Form.Item label={t.MESSAGE_TYPE} name="messageType">
disabled={bIsUpdate} <Select
placeholder={t.SELECT_CLUSTER_NAME} disabled={bIsUpdate}
options={allClusterNameList.map(name => ({ value: name, label: name }))} options={messageTypeOptions}
/> />
</Form.Item>
<Form.Item
label={t.WRITE_QUEUE_NUMS}
name="writeQueueNums"
rules={[{required: true, message: `${t.WRITE_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}`}]}
>
<Input disabled={!writeOperationEnabled}/>
</Form.Item>
<Form.Item
label={t.READ_QUEUE_NUMS}
name="readQueueNums"
rules={[{required: true, message: `${t.READ_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}`}]}
>
<Input disabled={!writeOperationEnabled}/>
</Form.Item>
<Form.Item
label={t.PERM}
name="perm"
rules={[{required: true, message: `${t.PERM}${t.CANNOT_BE_EMPTY}`}]}
>
<Input disabled={!writeOperationEnabled}/>
</Form.Item>
{!initialData.sysFlag && writeOperationEnabled && (
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" onClick={handleFormSubmit}>
{t.COMMIT}
</Button>
</Form.Item> </Form.Item>
<Form.Item label="BROKER_NAME" name="brokerNameList"> )}
<Select </Form>
mode="multiple" </Col>
disabled={bIsUpdate} </Row>
placeholder={t.SELECT_BROKER_NAME} </div>
options={allBrokerNameList.map(name => ({ value: name, label: name }))}
/>
</Form.Item>
<Form.Item
label={t.TOPIC_NAME}
name="topicName"
rules={[{ required: true, message: `${t.TOPIC_NAME}${t.CANNOT_BE_EMPTY}` }]}
>
<Input disabled={bIsUpdate} />
</Form.Item>
<Form.Item label={t.MESSAGE_TYPE} name="messageType">
<Select
disabled={bIsUpdate}
options={messageTypeOptions}
/>
</Form.Item>
<Form.Item
label={t.WRITE_QUEUE_NUMS}
name="writeQueueNums"
rules={[{ required: true, message: `${t.WRITE_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}` }]}
>
<Input disabled={!writeOperationEnabled} />
</Form.Item>
<Form.Item
label={t.READ_QUEUE_NUMS}
name="readQueueNums"
rules={[{ required: true, message: `${t.READ_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}` }]}
>
<Input disabled={!writeOperationEnabled} />
</Form.Item>
<Form.Item
label={t.PERM}
name="perm"
rules={[{ required: true, message: `${t.PERM}${t.CANNOT_BE_EMPTY}` }]}
>
<Input disabled={!writeOperationEnabled} />
</Form.Item>
{!initialData.sysFlag && writeOperationEnabled && (
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" onClick={handleFormSubmit}>
{t.COMMIT}
</Button>
</Form.Item>
)}
</Form>
</Col>
</Row>
</div>
); );
}; };

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": "顺序消息",
@@ -279,6 +278,12 @@ export const translations = {
"ENTER_IP_HINT": "请输入 IP 地址,按回车键添加,支持 IPv4、IPv6 和 CIDR", "ENTER_IP_HINT": "请输入 IP 地址,按回车键添加,支持 IPv4、IPv6 和 CIDR",
"PLEASE_ENTER_DECISION": "请输入决策!", "PLEASE_ENTER_DECISION": "请输入决策!",
"MENU": "菜单", "MENU": "菜单",
"SELECT_PROXY": "选择代理",
"ENABLE_PROXY": "启用代理",
"PROXY_DISABLED": "代理禁用",
"PROXY_ENABLED": "代理启用",
"BROKER_OVERVIEW": "Broker概览",
"TOTAL_MSG_RECEIVED_TODAY": "今天接收的总消息数",
}, },
en: { en: {
"DEFAULT": "Default", "DEFAULT": "Default",
@@ -294,7 +299,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 +330,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 +340,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 +370,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",
@@ -536,6 +540,13 @@ export const translations = {
"ENTER_IP_HINT": "Please enter IP address, press Enter to add. Supports IPv4, IPv6, and CIDR.", "ENTER_IP_HINT": "Please enter IP address, press Enter to add. Supports IPv4, IPv6, and CIDR.",
"PLEASE_ENTER_DECISION": "Please enter decision!", "PLEASE_ENTER_DECISION": "Please enter decision!",
"MENU": "Menu", "MENU": "Menu",
"SELECT_PROXY": "Select Proxy",
"ENABLE_PROXY": "Enable Proxy",
"PROXY_DISABLED": "Proxy Disabled",
"PROXY_ENABLED": "Proxy Enabled",
"BROKER_OVERVIEW": "Broker Overview",
"TOTAL_MSG_RECEIVED_TODAY": "Total messages received today",
} }

View File

@@ -16,15 +16,15 @@
*/ */
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace; monospace;
} }

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,17 +27,15 @@ 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>
<Provider store={store}> <Provider store={store}>
<App/> <App/>
</Provider> </Provider>
</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

@@ -16,7 +16,7 @@
*/ */
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, useEffect, useState} from 'react';
import {Button, Checkbox, Input, message, notification, Spin, Table} from 'antd'; import {Button, Checkbox, Input, message, notification, Select, Spin, Switch, Table} from 'antd';
import {useLanguage} from '../../i18n/LanguageContext'; import {useLanguage} from '../../i18n/LanguageContext';
import {remoteApi} from '../../api/remoteApi/remoteApi'; import {remoteApi} from '../../api/remoteApi/remoteApi';
import ClientInfoModal from "../../components/consumer/ClientInfoModal"; import ClientInfoModal from "../../components/consumer/ClientInfoModal";
@@ -44,8 +44,29 @@ 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 [proxyEnabled, setProxyEnabled] = useState(() => {
try {
const storedValue = localStorage.getItem('proxyEnabled');
return storedValue ? JSON.parse(storedValue) : false;
} catch (error) {
console.error("Failed to read proxyEnabled from localStorage:", error);
return false;
}
});
const [selectedProxy, setSelectedProxy] = useState(() => {
try {
const storedValue = localStorage.getItem('selectedProxy');
return storedValue || undefined;
} catch (error) {
console.error("Failed to read selectedProxy from localStorage:", error);
return undefined;
}
});
const [proxyOptions ,setProxyOptions]= useState([]);
const [paginationConf, setPaginationConf] = useState({ const [paginationConf, setPaginationConf] = useState({
current: 1, current: 1,
pageSize: 10, pageSize: 10,
@@ -60,12 +81,17 @@ const ConsumerGroupList = () => {
const loadConsumerGroups = useCallback(async (currentPage) => { const loadConsumerGroups = useCallback(async (currentPage) => {
setLoading(true); setLoading(true);
try { try {
const response = await remoteApi.queryConsumerGroupList(false); var response;
if(!proxyEnabled){
response = await remoteApi.queryConsumerGroupList(false);
}else{
response = await remoteApi.queryConsumerGroupList(false, selectedProxy);
}
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 {
@@ -87,7 +113,6 @@ const ConsumerGroupList = () => {
}; };
const filterList = useCallback((currentPage, data) => { const filterList = useCallback((currentPage, data) => {
// 排序处理
let sortedData = [...data]; let sortedData = [...data];
if (sortConfig.sortKey) { if (sortConfig.sortKey) {
sortedData.sort((a, b) => { sortedData.sort((a, b) => {
@@ -153,6 +178,48 @@ const ConsumerGroupList = () => {
filterList(paginationConf.current, sortedList); filterList(paginationConf.current, sortedList);
}, [sortConfig, allConsumerGroupList, paginationConf.current]); }, [sortConfig, allConsumerGroupList, paginationConf.current]);
const fetchProxyList = useCallback(async () => {
remoteApi.queryProxyHomePage((resp) => {
setLoading(false);
if (resp.status === 0) {
const {proxyAddrList, currentProxyAddr} = resp.data;
const options = proxyAddrList.map(proxyAddress => ({
label: proxyAddress,
value: proxyAddress,
}));
setProxyOptions(options || []);
setSelectedProxy(prevSelectedProxy => {
if (prevSelectedProxy) {
return prevSelectedProxy;
}
if (options.length > 0) {
return options[0].value;
}
return undefined;
});
} else {
notificationApi.error({message: resp.errMsg || t.FETCH_PROXY_LIST_FAILED, duration: 2});
}
});
}, [t]);
useEffect(() => {
localStorage.setItem('proxyEnabled', JSON.stringify(proxyEnabled));
}, [proxyEnabled]);
useEffect(() => {
if (selectedProxy) {
localStorage.setItem('selectedProxy', selectedProxy);
} else {
localStorage.removeItem('selectedProxy');
}
}, [selectedProxy]);
useEffect(() => {
fetchProxyList();
}, []);
useEffect(() => { useEffect(() => {
loadConsumerGroups(); loadConsumerGroups();
}, [loadConsumerGroups]); }, [loadConsumerGroups]);
@@ -380,7 +447,7 @@ const ConsumerGroupList = () => {
filterList(pagination.current, allConsumerGroupList); filterList(pagination.current, allConsumerGroupList);
}; };
const closeConfigModal = () =>{ const closeConfigModal = () => {
setShowConfig(false); setShowConfig(false);
setIsAddConfig(false); setIsAddConfig(false);
} }
@@ -389,16 +456,18 @@ const ConsumerGroupList = () => {
<> <>
{msgContextHolder} {msgContextHolder}
{notificationContextHolder} {notificationContextHolder}
<div style={{padding: '20px'}}> <div style={{ padding: '20px' }}>
<Spin spinning={loading} tip={t.LOADING}> <Spin spinning={loading} tip={t.LOADING}>
<div style={{marginBottom: '20px'}}> <div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{display: 'flex', alignItems: 'center', gap: '15px'}}> {/* 左侧:筛选和操作按钮 */}
<div style={{display: 'flex', alignItems: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', gap: '15px', flexWrap: 'wrap' }}>
<label style={{marginRight: '8px'}}>{t.SUBSCRIPTION_GROUP}:</label> <div style={{ display: 'flex', alignItems: 'center' }}>
<label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.SUBSCRIPTION_GROUP}:</label>
<Input <Input
style={{width: '200px'}} style={{ width: '200px' }}
value={filterStr} value={filterStr}
onChange={(e) => handleFilterInputChange(e.target.value)} onChange={(e) => handleFilterInputChange(e.target.value)}
placeholder="输入订阅组名称"
/> />
</div> </div>
<Checkbox checked={filterNormal} <Checkbox checked={filterNormal}
@@ -423,12 +492,35 @@ const ConsumerGroupList = () => {
<Button type="primary" onClick={handleRefreshConsumerData}> <Button type="primary" onClick={handleRefreshConsumerData}>
{t.REFRESH} {t.REFRESH}
</Button> </Button>
{/*<Switch*/} </div>
{/* checked={intervalProcessSwitch}*/}
{/* onChange={(checked) => setIntervalProcessSwitch(checked)}*/} {/* 右侧:代理选项 */}
{/* checkedChildren={t.AUTO_REFRESH}*/} <div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
{/* unCheckedChildren={t.AUTO_REFRESH}*/} <label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.SELECT_PROXY}:</label>
{/*/>*/} <Select
style={{ width: '220px' }}
placeholder={t.SELECT_PROXY}
onChange={(value) => setSelectedProxy(value)}
value={selectedProxy}
options={proxyOptions}
disabled={!proxyEnabled}
allowClear
/>
<label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.ENABLE_PROXY}:</label>
<Switch
checked={proxyEnabled}
onChange={(checked) => {
setProxyEnabled(checked);
if (!checked) {
setSelectedProxy(undefined);
messageApi.info(t.PROXY_DISABLED);
} else {
messageApi.info(t.PROXY_ENABLED);
}
}}
checkedChildren={t.ENABLED}
unCheckedChildren={t.DISABLED}
/>
</div> </div>
</div> </div>
@@ -443,6 +535,7 @@ const ConsumerGroupList = () => {
/> />
</Spin> </Spin>
{/* 模态框组件保持不变 */}
<ClientInfoModal <ClientInfoModal
visible={showClientInfo} visible={showClientInfo}
group={selectedGroup} group={selectedGroup}

View File

@@ -250,17 +250,18 @@ const DashboardPage = () => {
const brokerAddrTable = resp.data.clusterInfo.brokerAddrTable; // Corrected to brokerAddrTable const brokerAddrTable = resp.data.clusterInfo.brokerAddrTable; // Corrected to brokerAddrTable
const brokerDetail = resp.data.brokerServer; const brokerDetail = resp.data.brokerServer;
const clusterMap = tools.generateBrokerMap(brokerDetail, clusterAddrTable, brokerAddrTable); const clusterMap = tools.generateBrokerMap(brokerDetail, clusterAddrTable, brokerAddrTable);
console.log(brokerAddrTable)
let brokerArray = []; let brokerArray = [];
Object.values(clusterMap).forEach(brokersInCluster => { Object.values(clusterMap).forEach(brokersInCluster => {
brokerArray = brokerArray.concat(brokersInCluster); brokerArray = brokerArray.concat(brokersInCluster);
}); });
// Update broker table data const newData = brokerArray.map(broker => ({
setBrokerTableData(brokerArray.map(broker => ({
...broker, ...broker,
key: broker.brokerName // Ant Design Table needs a unique key key: broker.brokerName,
}))); }));
console.log("即将设置的数据:", newData); // 先打印
setBrokerTableData(newData); // 再设置状态
brokerArray.sort((firstBroker, lastBroker) => { brokerArray.sort((firstBroker, lastBroker) => {
const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0); const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0);
@@ -347,7 +348,7 @@ const DashboardPage = () => {
const brokerColumns = [ const brokerColumns = [
{title: t.BROKER_NAME, dataIndex: 'brokerName', key: 'brokerName'}, {title: t.BROKER_NAME, dataIndex: 'brokerName', key: 'brokerName'},
{title: t.BROKER_ADDR, dataIndex: 'brokerAddress', key: 'brokerAddress'}, {title: t.BROKER_ADDR, dataIndex: 'address', key: 'address'},
{ {
title: t.TOTAL_MSG_RECEIVED_TODAY, title: t.TOTAL_MSG_RECEIVED_TODAY,
dataIndex: 'msgGetTotalTodayNow', dataIndex: 'msgGetTotalTodayNow',

View File

@@ -179,7 +179,6 @@ const DlqMessageQueryPage = () => {
return; return;
} }
setLoading(true); setLoading(true);
// console.log("根据Message ID查询DLQ消息:", { msgId: messageId, consumerGroup: selectedConsumerGroup });
try { try {
const resp = await remoteApi.viewMessage(messageId, DLQ_GROUP_TOPIC_PREFIX + selectedConsumerGroup); const resp = await remoteApi.viewMessage(messageId, DLQ_GROUP_TOPIC_PREFIX + selectedConsumerGroup);
if (resp.status === 0) { if (resp.status === 0) {
@@ -323,7 +322,6 @@ const DlqMessageQueryPage = () => {
msgId: message.properties.ORIGIN_MESSAGE_ID, msgId: message.properties.ORIGIN_MESSAGE_ID,
consumerGroup: selectedConsumerGroup, consumerGroup: selectedConsumerGroup,
})); }));
// console.log(`批量重发DLQ消息到 ${selectedConsumerGroup}:`, messagesToResend);
try { try {
const resp = await remoteApi.batchResendDlqMessage(messagesToResend); const resp = await remoteApi.batchResendDlqMessage(messagesToResend);
if (resp.status === 0) { if (resp.status === 0) {
@@ -355,7 +353,6 @@ const DlqMessageQueryPage = () => {
message: t.ERROR, message: t.ERROR,
description: t.BATCH_RESEND_FAILED, description: t.BATCH_RESEND_FAILED,
}); });
console.error("批量重发失败:", error);
} finally { } finally {
setLoading(false); setLoading(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

@@ -146,7 +146,6 @@ const MessageQueryPage = () => {
message: t.ERROR, message: t.ERROR,
description: t.QUERY_FAILED, description: t.QUERY_FAILED,
}); });
console.error("查询失败:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -182,7 +181,6 @@ const MessageQueryPage = () => {
message: t.ERROR, message: t.ERROR,
description: t.QUERY_FAILED, description: t.QUERY_FAILED,
}); });
console.error("查询失败:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -241,7 +239,6 @@ const MessageQueryPage = () => {
message: t.ERROR, message: t.ERROR,
description: t.RESEND_FAILED, description: t.RESEND_FAILED,
}); });
console.error("重发失败:", error);
} finally { } finally {
setLoading(false); setLoading(false);
// Optionally, you might want to refresh the message detail after resend // Optionally, you might want to refresh the message detail after resend
@@ -455,7 +452,6 @@ const MessageQueryPage = () => {
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>
{/* Message ID 查询结果通常直接弹窗显示,这里不需要表格 */}
</div> </div>
</TabPane> </TabPane>
</Tabs> </Tabs>

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) => {
@@ -223,7 +222,7 @@ const DeployHistoryList = () => {
try { try {
if (isUpdate) { if (isUpdate) {
// topic 已经是字符串 // topic 已经是字符串
const configResult = await remoteApi.getTopicConfig(topic); const configResult = await remoteApi.getTopicConfig(topic);
if (configResult.status === 0) { if (configResult.status === 0) {
const dataToSet = Array.isArray(configResult.data) ? configResult.data : [configResult.data]; const dataToSet = Array.isArray(configResult.data) ? configResult.data : [configResult.data];
@@ -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

@@ -16,15 +16,15 @@
*/ */
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);
getLCP(onPerfEntry); getLCP(onPerfEntry);
getTTFB(onPerfEntry); getTTFB(onPerfEntry);
}); });
} }
}; };
export default reportWebVitals; export default reportWebVitals;

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

@@ -32,7 +32,7 @@ public class MQAdminPooledObjectFactory implements PooledObjectFactory<MQAdminEx
@Override @Override
public PooledObject<MQAdminExt> makeObject() throws Exception { public PooledObject<MQAdminExt> makeObject() throws Exception {
DefaultPooledObject<MQAdminExt> pooledObject = new DefaultPooledObject<>( DefaultPooledObject<MQAdminExt> pooledObject = new DefaultPooledObject<>(
mqAdminFactory.getInstance()); mqAdminFactory.getInstance());
return pooledObject; return pooledObject;
} }

View File

@@ -40,8 +40,8 @@ public class MqAdminExtObjectPool {
MQAdminFactory mqAdminFactory = new MQAdminFactory(rmqConfigure); MQAdminFactory mqAdminFactory = new MQAdminFactory(rmqConfigure);
mqAdminPooledObjectFactory.setMqAdminFactory(mqAdminFactory); mqAdminPooledObjectFactory.setMqAdminFactory(mqAdminFactory);
GenericObjectPool<MQAdminExt> genericObjectPool = new GenericObjectPool<MQAdminExt>( GenericObjectPool<MQAdminExt> genericObjectPool = new GenericObjectPool<MQAdminExt>(
mqAdminPooledObjectFactory, mqAdminPooledObjectFactory,
genericObjectPoolConfig); genericObjectPoolConfig);
return genericObjectPool; return genericObjectPool;
} }
} }

View File

@@ -41,20 +41,20 @@ public class CollectExecutorConfig {
@Bean(name = "collectExecutor") @Bean(name = "collectExecutor")
public ExecutorService collectExecutor(CollectExecutorConfig collectExecutorConfig) { public ExecutorService collectExecutor(CollectExecutorConfig collectExecutorConfig) {
ExecutorService collectExecutor = new ThreadPoolExecutor( ExecutorService collectExecutor = new ThreadPoolExecutor(
collectExecutorConfig.getCoreSize(), collectExecutorConfig.getCoreSize(),
collectExecutorConfig.getMaxSize(), collectExecutorConfig.getMaxSize(),
collectExecutorConfig.getKeepAliveTime(), collectExecutorConfig.getKeepAliveTime(),
TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(collectExecutorConfig.getQueueSize()), new LinkedBlockingDeque<>(collectExecutorConfig.getQueueSize()),
new ThreadFactory() { new ThreadFactory() {
private final AtomicLong threadIndex = new AtomicLong(0); private final AtomicLong threadIndex = new AtomicLong(0);
@Override @Override
public Thread newThread(Runnable r) { public Thread newThread(Runnable r) {
return new Thread(r, "collectTopicThread_" + this.threadIndex.incrementAndGet()); return new Thread(r, "collectTopicThread_" + this.threadIndex.incrementAndGet());
} }
}, },
new ThreadPoolExecutor.DiscardOldestPolicy() new ThreadPoolExecutor.DiscardOldestPolicy()
); );
return collectExecutor; return collectExecutor;
} }

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

@@ -23,6 +23,7 @@ import org.apache.rocketmq.dashboard.model.request.UserUpdateRequest;
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl; import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -31,24 +32,23 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
@RestController @Controller
@RequestMapping("/acl") @RequestMapping("/acl")
public class AclController { public class AclController {
@Autowired @Autowired
private AclServiceImpl aclService; private AclServiceImpl aclService;
@GetMapping("/listUsers") @GetMapping("/users.query")
@ResponseBody @ResponseBody
public List<UserInfo> listUsers(@RequestParam(required = false) String brokerAddress) { public List<UserInfo> listUsers(@RequestParam(required = false) String brokerAddress) {
return aclService.listUsers(brokerAddress); return aclService.listUsers(brokerAddress);
} }
@GetMapping("/listAcls") @GetMapping("/acls.query")
@ResponseBody @ResponseBody
public Object listAcls( public Object listAcls(
@RequestParam(required = false) String brokerAddress, @RequestParam(required = false) String brokerAddress,
@@ -56,34 +56,34 @@ public class AclController {
return aclService.listAcls(brokerAddress, searchParam); return aclService.listAcls(brokerAddress, searchParam);
} }
@PostMapping("/createAcl") @PostMapping("/createAcl.do")
@ResponseBody @ResponseBody
public Object createAcl(@RequestBody PolicyRequest request) { public Object createAcl(@RequestBody PolicyRequest request) {
aclService.createAcl(request); aclService.createAcl(request);
return true; return true;
} }
@DeleteMapping("/deleteUser") @DeleteMapping("/deleteUser.do")
@ResponseBody @ResponseBody
public Object deleteUser(@RequestParam(required = false) String brokerAddress, @RequestParam String username) { public Object deleteUser(@RequestParam(required = false) String brokerAddress, @RequestParam String username) {
aclService.deleteUser(brokerAddress, username); aclService.deleteUser(brokerAddress, username);
return true; return true;
} }
@RequestMapping(value = "/updateUser", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") @RequestMapping(value = "/updateUser.do", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
@ResponseBody @ResponseBody
public Object updateUser(@RequestBody UserUpdateRequest request) { public Object updateUser(@RequestBody UserUpdateRequest request) {
aclService.updateUser(request.getBrokerAddress(), request.getUserInfo()); aclService.updateUser(request.getBrokerAddress(), request.getUserInfo());
return true; return true;
} }
@PostMapping("/createUser") @PostMapping("/createUser.do")
public Object createUser(@RequestBody UserCreateRequest request) { public Object createUser(@RequestBody UserCreateRequest request) {
aclService.createUser(request.getBrokerAddress(), request.getUserInfo()); aclService.createUser(request.getBrokerAddress(), request.getUserInfo());
return true; return true;
} }
@DeleteMapping("/deleteAcl") @DeleteMapping("/deleteAcl.do")
public Object deleteAcl( public Object deleteAcl(
@RequestParam(required = false) String brokerAddress, @RequestParam(required = false) String brokerAddress,
@RequestParam String subject, @RequestParam String subject,
@@ -92,7 +92,7 @@ public class AclController {
return true; return true;
} }
@RequestMapping(value = "/updateAcl", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") @RequestMapping(value = "/updateAcl.do", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
@ResponseBody @ResponseBody
public Object updateAcl(@RequestBody PolicyRequest request) { public Object updateAcl(@RequestBody PolicyRequest request) {
aclService.updateAcl(request); aclService.updateAcl(request);

View File

@@ -54,7 +54,7 @@ public class ConsumerController {
@RequestMapping(value = "/group.refresh") @RequestMapping(value = "/group.refresh")
@ResponseBody @ResponseBody
public Object refresh(String address, public Object refresh(String address,
String consumerGroup) { String consumerGroup) {
return consumerService.refreshGroup(address, consumerGroup); return consumerService.refreshGroup(address, consumerGroup);
} }
@@ -100,7 +100,7 @@ public class ConsumerController {
@ResponseBody @ResponseBody
public Object consumerCreateOrUpdateRequest(@RequestBody ConsumerConfigInfo consumerConfigInfo) { public Object consumerCreateOrUpdateRequest(@RequestBody ConsumerConfigInfo consumerConfigInfo) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(consumerConfigInfo.getBrokerNameList()) || CollectionUtils.isNotEmpty(consumerConfigInfo.getClusterNameList()), Preconditions.checkArgument(CollectionUtils.isNotEmpty(consumerConfigInfo.getBrokerNameList()) || CollectionUtils.isNotEmpty(consumerConfigInfo.getClusterNameList()),
"clusterName or brokerName can not be all blank"); "clusterName or brokerName can not be all blank");
return consumerService.createAndUpdateSubscriptionGroupConfig(consumerConfigInfo); return consumerService.createAndUpdateSubscriptionGroupConfig(consumerConfigInfo);
} }
@@ -127,7 +127,7 @@ public class ConsumerController {
@RequestMapping(value = "/consumerRunningInfo.query") @RequestMapping(value = "/consumerRunningInfo.query")
@ResponseBody @ResponseBody
public Object getConsumerRunningInfo(@RequestParam String consumerGroup, @RequestParam String clientId, public Object getConsumerRunningInfo(@RequestParam String consumerGroup, @RequestParam String clientId,
@RequestParam boolean jstack) { @RequestParam boolean jstack) {
return consumerService.getConsumerRunningInfo(consumerGroup, clientId, jstack); return consumerService.getConsumerRunningInfo(consumerGroup, clientId, jstack);
} }
} }

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

@@ -64,7 +64,7 @@ public class MessageTraceController {
@RequestMapping(value = "/viewMessageTraceGraph.query", method = RequestMethod.GET) @RequestMapping(value = "/viewMessageTraceGraph.query", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public MessageTraceGraph viewMessageTraceGraph(@RequestParam String msgId, public MessageTraceGraph viewMessageTraceGraph(@RequestParam String msgId,
@RequestParam(required = false) String traceTopic) { @RequestParam(required = false) String traceTopic) {
return messageTraceService.queryMessageTraceGraph(msgId, traceTopic); return messageTraceService.queryMessageTraceGraph(msgId, traceTopic);
} }
} }

View File

@@ -40,7 +40,7 @@ public class MonitorController {
@RequestMapping(value = "/createOrUpdateConsumerMonitor.do", method = {RequestMethod.POST}) @RequestMapping(value = "/createOrUpdateConsumerMonitor.do", method = {RequestMethod.POST})
@ResponseBody @ResponseBody
public Object createOrUpdateConsumerMonitor(@RequestParam String consumeGroupName, @RequestParam int minCount, public Object createOrUpdateConsumerMonitor(@RequestParam String consumeGroupName, @RequestParam int minCount,
@RequestParam int maxDiffTotal) { @RequestParam int maxDiffTotal) {
return monitorService.createOrUpdateConsumerMonitor(consumeGroupName, new ConsumerMonitorConfig(minCount, maxDiffTotal)); return monitorService.createOrUpdateConsumerMonitor(consumeGroupName, new ConsumerMonitorConfig(minCount, maxDiffTotal));
} }

View File

@@ -52,7 +52,7 @@ public class OpsController {
@ResponseBody @ResponseBody
public Object addNameSvrAddr(@RequestParam String newNamesrvAddr) { public Object addNameSvrAddr(@RequestParam String newNamesrvAddr) {
Preconditions.checkArgument(StringUtils.isNotEmpty(newNamesrvAddr), Preconditions.checkArgument(StringUtils.isNotEmpty(newNamesrvAddr),
"namesrvAddr can not be blank"); "namesrvAddr can not be blank");
opsService.addNameSvrAddr(newNamesrvAddr); opsService.addNameSvrAddr(newNamesrvAddr);
return true; return true;
} }

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

@@ -59,7 +59,7 @@ public class TestController {
@Override @Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) { ConsumeConcurrentlyContext context) {
logger.info("receiveMessage msgSize={}", msgs.size()); logger.info("receiveMessage msgSize={}", msgs.size());
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} }
@@ -72,26 +72,25 @@ 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) {
try { try {
Message msg = new Message(testTopic, Message msg = new Message(testTopic,
"TagA" + i, "TagA" + i,
"KEYS" + i, "KEYS" + i,
("Hello RocketMQ " + i).getBytes() ("Hello RocketMQ " + i).getBytes()
); );
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

@@ -51,7 +51,7 @@ public class TopicController {
@RequestMapping(value = "/list.query", method = RequestMethod.GET) @RequestMapping(value = "/list.query", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Object list(@RequestParam(value = "skipSysProcess", required = false) boolean skipSysProcess, public Object list(@RequestParam(value = "skipSysProcess", required = false) boolean skipSysProcess,
@RequestParam(value = "skipRetryAndDlq", required = false) boolean skipRetryAndDlq) { @RequestParam(value = "skipRetryAndDlq", required = false) boolean skipRetryAndDlq) {
return topicService.fetchAllTopicList(skipSysProcess, skipRetryAndDlq); return topicService.fetchAllTopicList(skipSysProcess, skipRetryAndDlq);
} }
@@ -75,11 +75,11 @@ 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()),
"clusterName or brokerName can not be all blank"); "clusterName or brokerName can not be all blank");
logger.info("op=look topicCreateOrUpdateRequest={}", JsonUtil.obj2String(topicCreateOrUpdateRequest)); logger.info("op=look topicCreateOrUpdateRequest={}", JsonUtil.obj2String(topicCreateOrUpdateRequest));
topicService.createOrUpdate(topicCreateOrUpdateRequest); topicService.createOrUpdate(topicCreateOrUpdateRequest);
return true; return true;
@@ -100,14 +100,14 @@ public class TopicController {
@RequestMapping(value = "/examineTopicConfig.query") @RequestMapping(value = "/examineTopicConfig.query")
@ResponseBody @ResponseBody
public Object examineTopicConfig(@RequestParam String topic, public Object examineTopicConfig(@RequestParam String topic,
@RequestParam(required = false) String brokerName) throws RemotingException, MQClientException, InterruptedException { @RequestParam(required = false) String brokerName) throws RemotingException, MQClientException, InterruptedException {
return topicService.examineTopicConfig(topic); return topicService.examineTopicConfig(topic);
} }
@RequestMapping(value = "/sendTopicMessage.do", method = {RequestMethod.POST}) @RequestMapping(value = "/sendTopicMessage.do", method = {RequestMethod.POST})
@ResponseBody @ResponseBody
public Object sendTopicMessage( public Object sendTopicMessage(
@RequestBody SendTopicMessageRequest sendTopicMessageRequest) throws RemotingException, MQClientException, InterruptedException { @RequestBody SendTopicMessageRequest sendTopicMessageRequest) throws RemotingException, MQClientException, InterruptedException {
return topicService.sendTopicMessageRequest(sendTopicMessageRequest); return topicService.sendTopicMessageRequest(sendTopicMessageRequest);
} }

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

@@ -19,8 +19,8 @@ package org.apache.rocketmq.dashboard.model;
import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.Range;
public class User { public class User {
public static final int ORDINARY = 0; public static final int SUPER = 0;
public static final int ADMIN = 1; public static final int NORMAL = 1;
private long id; private long id;
private String name; private String name;

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)
@@ -112,16 +115,16 @@ public class TopicConfigInfo {
return false; return false;
TopicConfigInfo that = (TopicConfigInfo) o; TopicConfigInfo that = (TopicConfigInfo) o;
return writeQueueNums == that.writeQueueNums && return writeQueueNums == that.writeQueueNums &&
readQueueNums == that.readQueueNums && readQueueNums == that.readQueueNums &&
perm == that.perm && perm == that.perm &&
order == that.order && order == that.order &&
Objects.equal(topicName, that.topicName) && Objects.equal(topicName, that.topicName) &&
Objects.equal(messageType, that.messageType); Objects.equal(messageType, that.messageType);
} }
@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

@@ -17,9 +17,11 @@
package org.apache.rocketmq.dashboard.model.request; package org.apache.rocketmq.dashboard.model.request;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@Data @Data
@AllArgsConstructor
public class UserInfoParam { public class UserInfoParam {
private String username; private String username;
private String password; private String password;

View File

@@ -29,16 +29,16 @@ 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();
if (CollectionUtils.isNotEmpty(clusterNameList)) { if (CollectionUtils.isNotEmpty(clusterNameList)) {
try { try {
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

@@ -39,5 +39,5 @@ public interface AclService {
void deleteAcl(String brokerAddress, String subject, String resource); void deleteAcl(String brokerAddress, String subject, String resource);
void updateAcl(PolicyRequest policyRequest); void updateAcl(PolicyRequest policyRequest);
} }

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

@@ -32,7 +32,7 @@ public interface DashboardService {
Map<String, List<String>> queryTopicData(String date); Map<String, List<String>> queryTopicData(String date);
/** /**
* @param date format yyyy-MM-dd * @param date format yyyy-MM-dd
* @param topicName * @param topicName
*/ */
List<String> queryTopicData(String date, String topicName); List<String> queryTopicData(String date, String topicName);

View File

@@ -39,21 +39,18 @@ 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);
List<MessageTrack> messageTrackDetail(MessageExt msg); List<MessageTrack> messageTrackDetail(MessageExt msg);
ConsumeMessageDirectlyResult consumeMessageDirectly(String topic, String msgId, String consumerGroup, ConsumeMessageDirectlyResult consumeMessageDirectly(String topic, String msgId, String consumerGroup,
String clientId); String clientId);
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

@@ -24,5 +24,5 @@ public interface ProxyService {
void updateProxyAddrList(String proxyAddr); void updateProxyAddrList(String proxyAddr);
Map<String, Object> getProxyHomePage(); Map<String, Object> getProxyHomePage();
} }

View File

@@ -94,19 +94,20 @@ 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
public void updateBrokerConfig(String brokerAddr, Properties properties) public void updateBrokerConfig(String brokerAddr, Properties properties)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
UnsupportedEncodingException, InterruptedException, MQBrokerException, MQClientException { UnsupportedEncodingException, InterruptedException, MQBrokerException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().updateBrokerConfig(brokerAddr, properties); MQAdminInstance.threadLocalMQAdminExt().updateBrokerConfig(brokerAddr, properties);
} }
@Override @Override
public void createAndUpdateTopicConfig(String addr, TopicConfig config) public void createAndUpdateTopicConfig(String addr, TopicConfig config)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createAndUpdateTopicConfig(addr, config); MQAdminInstance.threadLocalMQAdminExt().createAndUpdateTopicConfig(addr, config);
} }
@@ -118,7 +119,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void createAndUpdateSubscriptionGroupConfig(String addr, SubscriptionGroupConfig config) public void createAndUpdateSubscriptionGroupConfig(String addr, SubscriptionGroupConfig config)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createAndUpdateSubscriptionGroupConfig(addr, config); MQAdminInstance.threadLocalMQAdminExt().createAndUpdateSubscriptionGroupConfig(addr, config);
} }
@@ -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);
} }
@@ -178,7 +178,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public TopicStatsTable examineTopicStats(String topic) public TopicStatsTable examineTopicStats(String topic)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException { throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().examineTopicStats(topic); return MQAdminInstance.threadLocalMQAdminExt().examineTopicStats(topic);
} }
@@ -191,14 +191,14 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public KVTable fetchBrokerRuntimeStats(String brokerAddr) public KVTable fetchBrokerRuntimeStats(String brokerAddr)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
InterruptedException, MQBrokerException { InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().fetchBrokerRuntimeStats(brokerAddr); return MQAdminInstance.threadLocalMQAdminExt().fetchBrokerRuntimeStats(brokerAddr);
} }
@Override @Override
public ConsumeStats examineConsumeStats(String consumerGroup) public ConsumeStats examineConsumeStats(String consumerGroup)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException { throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(consumerGroup); return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(consumerGroup);
} }
@@ -209,7 +209,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public ConsumeStats examineConsumeStats(String consumerGroup, String topic) public ConsumeStats examineConsumeStats(String consumerGroup, String topic)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException { throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(consumerGroup, topic); return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(consumerGroup, topic);
} }
@@ -220,27 +220,27 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public ClusterInfo examineBrokerClusterInfo() public ClusterInfo examineBrokerClusterInfo()
throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException, throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException,
RemotingConnectException { RemotingConnectException {
return MQAdminInstance.threadLocalMQAdminExt().examineBrokerClusterInfo(); return MQAdminInstance.threadLocalMQAdminExt().examineBrokerClusterInfo();
} }
@Override @Override
public TopicRouteData examineTopicRouteInfo(String topic) public TopicRouteData examineTopicRouteInfo(String topic)
throws RemotingException, MQClientException, InterruptedException { throws RemotingException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().examineTopicRouteInfo(topic); return MQAdminInstance.threadLocalMQAdminExt().examineTopicRouteInfo(topic);
} }
@Override @Override
public ConsumerConnection examineConsumerConnectionInfo(String consumerGroup) public ConsumerConnection examineConsumerConnectionInfo(String consumerGroup)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
InterruptedException, MQBrokerException, RemotingException, MQClientException { InterruptedException, MQBrokerException, RemotingException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().examineConsumerConnectionInfo(consumerGroup); return MQAdminInstance.threadLocalMQAdminExt().examineConsumerConnectionInfo(consumerGroup);
} }
@Override @Override
public ProducerConnection examineProducerConnectionInfo(String producerGroup, String topic) public ProducerConnection examineProducerConnectionInfo(String producerGroup, String topic)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException { throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().examineProducerConnectionInfo(producerGroup, topic); return MQAdminInstance.threadLocalMQAdminExt().examineProducerConnectionInfo(producerGroup, topic);
} }
@@ -251,14 +251,14 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public int wipeWritePermOfBroker(String namesrvAddr, String brokerName) public int wipeWritePermOfBroker(String namesrvAddr, String brokerName)
throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException,
RemotingTimeoutException, InterruptedException, MQClientException { RemotingTimeoutException, InterruptedException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().wipeWritePermOfBroker(namesrvAddr, brokerName); return MQAdminInstance.threadLocalMQAdminExt().wipeWritePermOfBroker(namesrvAddr, brokerName);
} }
@Override @Override
public int addWritePermOfBroker(String namesrvAddr, public int addWritePermOfBroker(String namesrvAddr,
String brokerName) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException { String brokerName) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().addWritePermOfBroker(namesrvAddr, brokerName); return MQAdminInstance.threadLocalMQAdminExt().addWritePermOfBroker(namesrvAddr, brokerName);
} }
@@ -269,62 +269,62 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public String getKVConfig(String namespace, String key) public String getKVConfig(String namespace, String key)
throws RemotingException, MQClientException, InterruptedException { throws RemotingException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().getKVConfig(namespace, key); return MQAdminInstance.threadLocalMQAdminExt().getKVConfig(namespace, key);
} }
@Override @Override
public KVTable getKVListByNamespace(String namespace) public KVTable getKVListByNamespace(String namespace)
throws RemotingException, MQClientException, InterruptedException { throws RemotingException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().getKVListByNamespace(namespace); return MQAdminInstance.threadLocalMQAdminExt().getKVListByNamespace(namespace);
} }
@Override @Override
public void deleteTopicInBroker(Set<String> addrs, String topic) public void deleteTopicInBroker(Set<String> addrs, String topic)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
logger.info("addrs={} topic={}", JsonUtil.obj2String(addrs), topic); logger.info("addrs={} topic={}", JsonUtil.obj2String(addrs), topic);
MQAdminInstance.threadLocalMQAdminExt().deleteTopicInBroker(addrs, topic); MQAdminInstance.threadLocalMQAdminExt().deleteTopicInBroker(addrs, topic);
} }
@Override @Override
public void deleteTopicInNameServer(Set<String> addrs, String topic) public void deleteTopicInNameServer(Set<String> addrs, String topic)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().deleteTopicInNameServer(addrs, topic); MQAdminInstance.threadLocalMQAdminExt().deleteTopicInNameServer(addrs, topic);
} }
@Override @Override
public void deleteSubscriptionGroup(String addr, String groupName) public void deleteSubscriptionGroup(String addr, String groupName)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().deleteSubscriptionGroup(addr, groupName); MQAdminInstance.threadLocalMQAdminExt().deleteSubscriptionGroup(addr, groupName);
} }
@Override @Override
public void deleteSubscriptionGroup(String addr, String groupName, boolean removeOffset) public void deleteSubscriptionGroup(String addr, String groupName, boolean removeOffset)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().deleteSubscriptionGroup(addr, groupName, removeOffset); MQAdminInstance.threadLocalMQAdminExt().deleteSubscriptionGroup(addr, groupName, removeOffset);
} }
@Override @Override
public void createAndUpdateKvConfig(String namespace, String key, String value) public void createAndUpdateKvConfig(String namespace, String key, String value)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createAndUpdateKvConfig(namespace, key, value); MQAdminInstance.threadLocalMQAdminExt().createAndUpdateKvConfig(namespace, key, value);
} }
@Override @Override
public void deleteKvConfig(String namespace, String key) public void deleteKvConfig(String namespace, String key)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().deleteKvConfig(namespace, key); MQAdminInstance.threadLocalMQAdminExt().deleteKvConfig(namespace, key);
} }
@Override @Override
public List<RollbackStats> resetOffsetByTimestampOld(String consumerGroup, String topic, long timestamp, public List<RollbackStats> resetOffsetByTimestampOld(String consumerGroup, String topic, long timestamp,
boolean force) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { boolean force) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().resetOffsetByTimestampOld(consumerGroup, topic, timestamp, force); return MQAdminInstance.threadLocalMQAdminExt().resetOffsetByTimestampOld(consumerGroup, topic, timestamp, force);
} }
@Override @Override
public Map<MessageQueue, Long> resetOffsetByTimestamp(String topic, String group, long timestamp, public Map<MessageQueue, Long> resetOffsetByTimestamp(String topic, String group, long timestamp,
boolean isForce) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { boolean isForce) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().resetOffsetByTimestamp(topic, group, timestamp, isForce); return MQAdminInstance.threadLocalMQAdminExt().resetOffsetByTimestamp(topic, group, timestamp, isForce);
} }
@@ -335,59 +335,59 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void resetOffsetNew(String consumerGroup, String topic, long timestamp) public void resetOffsetNew(String consumerGroup, String topic, long timestamp)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().resetOffsetNew(consumerGroup, topic, timestamp); MQAdminInstance.threadLocalMQAdminExt().resetOffsetNew(consumerGroup, topic, timestamp);
} }
@Override @Override
public Map<String, Map<MessageQueue, Long>> getConsumeStatus(String topic, String group, public Map<String, Map<MessageQueue, Long>> getConsumeStatus(String topic, String group,
String clientAddr) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { String clientAddr) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().getConsumeStatus(topic, group, clientAddr); return MQAdminInstance.threadLocalMQAdminExt().getConsumeStatus(topic, group, clientAddr);
} }
@Override @Override
public void createOrUpdateOrderConf(String key, String value, boolean isCluster) public void createOrUpdateOrderConf(String key, String value, boolean isCluster)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException { throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createOrUpdateOrderConf(key, value, isCluster); MQAdminInstance.threadLocalMQAdminExt().createOrUpdateOrderConf(key, value, isCluster);
} }
@Override @Override
public GroupList queryTopicConsumeByWho(String topic) public GroupList queryTopicConsumeByWho(String topic)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
InterruptedException, MQBrokerException, RemotingException, MQClientException { InterruptedException, MQBrokerException, RemotingException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().queryTopicConsumeByWho(topic); return MQAdminInstance.threadLocalMQAdminExt().queryTopicConsumeByWho(topic);
} }
@Override @Override
public boolean cleanExpiredConsumerQueue(String cluster) public boolean cleanExpiredConsumerQueue(String cluster)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException,
InterruptedException { InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().cleanExpiredConsumerQueue(cluster); return MQAdminInstance.threadLocalMQAdminExt().cleanExpiredConsumerQueue(cluster);
} }
@Override @Override
public boolean cleanExpiredConsumerQueueByAddr(String addr) public boolean cleanExpiredConsumerQueueByAddr(String addr)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException,
InterruptedException { InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().cleanExpiredConsumerQueueByAddr(addr); return MQAdminInstance.threadLocalMQAdminExt().cleanExpiredConsumerQueueByAddr(addr);
} }
@Override @Override
public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack) public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack)
throws RemotingException, MQClientException, InterruptedException { throws RemotingException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().getConsumerRunningInfo(consumerGroup, clientId, jstack); return MQAdminInstance.threadLocalMQAdminExt().getConsumerRunningInfo(consumerGroup, clientId, jstack);
} }
@Override @Override
public List<MessageTrack> messageTrackDetail(MessageExt msg) public List<MessageTrack> messageTrackDetail(MessageExt msg)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException { throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().messageTrackDetail(msg); return MQAdminInstance.threadLocalMQAdminExt().messageTrackDetail(msg);
} }
@Override @Override
public void cloneGroupOffset(String srcGroup, String destGroup, String topic, boolean isOffline) public void cloneGroupOffset(String srcGroup, String destGroup, String topic, boolean isOffline)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException { throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
MQAdminInstance.threadLocalMQAdminExt().cloneGroupOffset(srcGroup, destGroup, topic, isOffline); MQAdminInstance.threadLocalMQAdminExt().cloneGroupOffset(srcGroup, destGroup, topic, isOffline);
} }
@@ -398,7 +398,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map<String, String> attributes) public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map<String, String> attributes)
throws MQClientException { throws MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createTopic(key, newTopic, queueNum, topicSysFlag, attributes); MQAdminInstance.threadLocalMQAdminExt().createTopic(key, newTopic, queueNum, topicSysFlag, attributes);
} }
@@ -424,7 +424,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end)
throws MQClientException, InterruptedException { throws MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().queryMessage(topic, key, maxNum, begin, end); return MQAdminInstance.threadLocalMQAdminExt().queryMessage(topic, key, maxNum, begin, end);
} }
@@ -444,7 +444,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public List<QueueTimeSpan> queryConsumeTimeSpan(String topic, public List<QueueTimeSpan> queryConsumeTimeSpan(String topic,
String group) throws InterruptedException, MQBrokerException, RemotingException, MQClientException { String group) throws InterruptedException, MQBrokerException, RemotingException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().queryConsumeTimeSpan(topic, group); return MQAdminInstance.threadLocalMQAdminExt().queryConsumeTimeSpan(topic, group);
} }
@@ -454,7 +454,7 @@ public class MQAdminExtImpl implements MQAdminExt {
//https://github.com/apache/incubator-rocketmq/pull/69 //https://github.com/apache/incubator-rocketmq/pull/69
@Override @Override
public MessageExt viewMessage(String topic, public MessageExt viewMessage(String topic,
String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
logger.info("MessageClientIDSetter.getNearlyTimeFromID(msgId)={} msgId={}", MessageClientIDSetter.getNearlyTimeFromID(msgId), msgId); logger.info("MessageClientIDSetter.getNearlyTimeFromID(msgId)={} msgId={}", MessageClientIDSetter.getNearlyTimeFromID(msgId), msgId);
MQAdminImpl mqAdminImpl = MQAdminInstance.threadLocalMqClientInstance().getMQAdminImpl(); MQAdminImpl mqAdminImpl = MQAdminInstance.threadLocalMqClientInstance().getMQAdminImpl();
Set<String> clusterList = MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic); Set<String> clusterList = MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic);
@@ -478,7 +478,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public ConsumeMessageDirectlyResult consumeMessageDirectly(String consumerGroup, String clientId, String topic, public ConsumeMessageDirectlyResult consumeMessageDirectly(String consumerGroup, String clientId, String topic,
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().consumeMessageDirectly(consumerGroup, clientId, topic, msgId); return MQAdminInstance.threadLocalMQAdminExt().consumeMessageDirectly(consumerGroup, clientId, topic, msgId);
} }
@@ -489,96 +489,99 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public Properties getBrokerConfig( public Properties getBrokerConfig(
String brokerAddr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, UnsupportedEncodingException, InterruptedException, MQBrokerException { String brokerAddr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, UnsupportedEncodingException, InterruptedException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().getBrokerConfig(brokerAddr); return MQAdminInstance.threadLocalMQAdminExt().getBrokerConfig(brokerAddr);
} }
@Override @Override
public TopicList fetchTopicsByCLuster( public TopicList fetchTopicsByCLuster(
String clusterName) throws RemotingException, MQClientException, InterruptedException { String clusterName) throws RemotingException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().fetchTopicsByCLuster(clusterName); return MQAdminInstance.threadLocalMQAdminExt().fetchTopicsByCLuster(clusterName);
} }
@Override @Override
public boolean cleanUnusedTopic( public boolean cleanUnusedTopic(
String cluster) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException { String cluster) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().cleanUnusedTopic(cluster); return MQAdminInstance.threadLocalMQAdminExt().cleanUnusedTopic(cluster);
} }
@Override @Override
public boolean cleanUnusedTopicByAddr( public boolean cleanUnusedTopicByAddr(
String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException { String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().cleanUnusedTopicByAddr(addr); return MQAdminInstance.threadLocalMQAdminExt().cleanUnusedTopicByAddr(addr);
} }
@Override @Override
public BrokerStatsData viewBrokerStatsData(String brokerAddr, String statsName, public BrokerStatsData viewBrokerStatsData(String brokerAddr, String statsName,
String statsKey) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException { String statsKey) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().viewBrokerStatsData(brokerAddr, statsName, statsKey); return MQAdminInstance.threadLocalMQAdminExt().viewBrokerStatsData(brokerAddr, statsName, statsKey);
} }
@Override @Override
public Set<String> getClusterList( public Set<String> getClusterList(
String topic) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException { String topic) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().getClusterList(topic); return MQAdminInstance.threadLocalMQAdminExt().getClusterList(topic);
} }
@Override @Override
public ConsumeStatsList fetchConsumeStatsInBroker(String brokerAddr, boolean isOrder, public ConsumeStatsList fetchConsumeStatsInBroker(String brokerAddr, boolean isOrder,
long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException { long timeoutMillis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
return MQAdminInstance.threadLocalMQAdminExt().fetchConsumeStatsInBroker(brokerAddr, isOrder, timeoutMillis); return MQAdminInstance.threadLocalMQAdminExt().fetchConsumeStatsInBroker(brokerAddr, isOrder, timeoutMillis);
} }
@Override @Override
public Set<String> getTopicClusterList( public Set<String> getTopicClusterList(
String topic) throws InterruptedException, MQBrokerException, MQClientException, RemotingException { String topic) throws InterruptedException, MQBrokerException, MQClientException, RemotingException {
return MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic); return MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic);
} }
@Override @Override
public SubscriptionGroupWrapper getAllSubscriptionGroup(String brokerAddr, public SubscriptionGroupWrapper getAllSubscriptionGroup(String brokerAddr,
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().getAllSubscriptionGroup(brokerAddr, timeoutMillis); return MQAdminInstance.threadLocalMQAdminExt().getAllSubscriptionGroup(brokerAddr, timeoutMillis);
} }
@Override @Override
public SubscriptionGroupWrapper getUserSubscriptionGroup(String brokerAddr, public SubscriptionGroupWrapper getUserSubscriptionGroup(String brokerAddr,
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().getUserSubscriptionGroup(brokerAddr, timeoutMillis); return MQAdminInstance.threadLocalMQAdminExt().getUserSubscriptionGroup(brokerAddr, timeoutMillis);
} }
@Override @Override
public TopicConfigSerializeWrapper getAllTopicConfig(String brokerAddr, public TopicConfigSerializeWrapper getAllTopicConfig(String brokerAddr,
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException { long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().getAllTopicConfig(brokerAddr, timeoutMillis); return MQAdminInstance.threadLocalMQAdminExt().getAllTopicConfig(brokerAddr, timeoutMillis);
} }
@Override @Override
public TopicConfigSerializeWrapper getUserTopicConfig(String brokerAddr, boolean specialTopic, public TopicConfigSerializeWrapper getUserTopicConfig(String brokerAddr, boolean specialTopic,
long timeoutMillis) throws InterruptedException, RemotingException, MQBrokerException, MQClientException { long timeoutMillis) throws InterruptedException, RemotingException, MQBrokerException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().getUserTopicConfig(brokerAddr, specialTopic, timeoutMillis); return MQAdminInstance.threadLocalMQAdminExt().getUserTopicConfig(brokerAddr, specialTopic, timeoutMillis);
} }
@Override @Override
public void updateConsumeOffset(String brokerAddr, String consumeGroup, MessageQueue mq, public void updateConsumeOffset(String brokerAddr, String consumeGroup, MessageQueue mq,
long offset) throws RemotingException, InterruptedException, MQBrokerException { long offset) throws RemotingException, InterruptedException, MQBrokerException {
MQAdminInstance.threadLocalMQAdminExt().updateConsumeOffset(brokerAddr, consumeGroup, mq, offset); MQAdminInstance.threadLocalMQAdminExt().updateConsumeOffset(brokerAddr, consumeGroup, mq, offset);
} }
// 4.0.0 added // 4.0.0 added
@Override public void updateNameServerConfig(Properties properties, @Override
List<String> list) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException { public void updateNameServerConfig(Properties properties,
List<String> list) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException {
} }
@Override public Map<String, Properties> getNameServerConfig( @Override
List<String> list) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException { public Map<String, Properties> getNameServerConfig(
List<String> list) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException {
return null; return null;
} }
@Override public QueryConsumeQueueResponseBody queryConsumeQueue(String brokerAddr, String topic, @Override
int queueId, long index, int count, public QueryConsumeQueueResponseBody queryConsumeQueue(String brokerAddr, String topic,
String consumerGroup) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException { int queueId, long index, int count,
String consumerGroup) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException {
return null; return null;
} }
@@ -588,8 +591,9 @@ public class MQAdminExtImpl implements MQAdminExt {
} }
@Override public boolean resumeCheckHalfMessage(String topic, @Override
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { public boolean resumeCheckHalfMessage(String topic,
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return false; return false;
} }
@@ -602,7 +606,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void removeBrokerFromContainer(String brokerContainerAddr, String clusterName, String brokerName, public void removeBrokerFromContainer(String brokerContainerAddr, String clusterName, String brokerName,
long brokerId) throws InterruptedException, MQBrokerException, RemotingTimeoutException, long brokerId) throws InterruptedException, MQBrokerException, RemotingTimeoutException,
RemotingSendRequestException, RemotingConnectException { RemotingSendRequestException, RemotingConnectException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'removeBrokerFromContainer'"); throw new UnsupportedOperationException("Unimplemented method 'removeBrokerFromContainer'");
@@ -624,7 +628,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public ConsumeStats examineConsumeStats(String brokerAddr, String consumerGroup, String topicName, public ConsumeStats examineConsumeStats(String brokerAddr, String consumerGroup, String topicName,
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException,
RemotingConnectException, MQBrokerException { RemotingConnectException, MQBrokerException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(brokerAddr, consumerGroup, topicName, timeoutMillis); return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(brokerAddr, consumerGroup, topicName, timeoutMillis);
@@ -717,7 +721,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack, public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack,
boolean metrics) throws RemotingException, MQClientException, InterruptedException { boolean metrics) throws RemotingException, MQClientException, InterruptedException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getConsumerRunningInfo'"); throw new UnsupportedOperationException("Unimplemented method 'getConsumerRunningInfo'");
} }
@@ -731,7 +735,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void setMessageRequestMode(String brokerAddr, String topic, String consumerGroup, MessageRequestMode mode, public void setMessageRequestMode(String brokerAddr, String topic, String consumerGroup, MessageRequestMode mode,
int popWorkGroupSize, long timeoutMillis) throws InterruptedException, RemotingTimeoutException, int popWorkGroupSize, long timeoutMillis) throws InterruptedException, RemotingTimeoutException,
RemotingSendRequestException, RemotingConnectException, MQClientException { RemotingSendRequestException, RemotingConnectException, MQClientException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'setMessageRequestMode'"); throw new UnsupportedOperationException("Unimplemented method 'setMessageRequestMode'");
@@ -746,14 +750,14 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void resetOffsetByQueueId(String brokerAddr, String consumerGroup, String topicName, int queueId, public void resetOffsetByQueueId(String brokerAddr, String consumerGroup, String topicName, int queueId,
long resetOffset) throws RemotingException, InterruptedException, MQBrokerException { long resetOffset) throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'resetOffsetByQueueId'"); throw new UnsupportedOperationException("Unimplemented method 'resetOffsetByQueueId'");
} }
@Override @Override
public void createStaticTopic(String addr, String defaultTopic, TopicConfig topicConfig, public void createStaticTopic(String addr, String defaultTopic, TopicConfig topicConfig,
TopicQueueMappingDetail mappingDetail, boolean force) TopicQueueMappingDetail mappingDetail, boolean force)
throws RemotingException, InterruptedException, MQBrokerException { throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'createStaticTopic'"); throw new UnsupportedOperationException("Unimplemented method 'createStaticTopic'");
@@ -761,7 +765,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public GroupForbidden updateAndGetGroupReadForbidden(String brokerAddr, String groupName, String topicName, public GroupForbidden updateAndGetGroupReadForbidden(String brokerAddr, String groupName, String topicName,
Boolean readable) throws RemotingException, InterruptedException, MQBrokerException { Boolean readable) throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'updateAndGetGroupReadForbidden'"); throw new UnsupportedOperationException("Unimplemented method 'updateAndGetGroupReadForbidden'");
} }
@@ -831,7 +835,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override @Override
public void cleanControllerBrokerData(String controllerAddr, String clusterName, String brokerName, public void cleanControllerBrokerData(String controllerAddr, String clusterName, String brokerName,
String brokerAddr, boolean isCleanLivingBroker) String brokerAddr, boolean isCleanLivingBroker)
throws RemotingException, InterruptedException, MQBrokerException { throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'cleanControllerBrokerData'"); throw new UnsupportedOperationException("Unimplemented method 'cleanControllerBrokerData'");

View File

@@ -58,7 +58,7 @@ public abstract class AbstractFileStore {
} }
} }
abstract void load(InputStream inputStream); protected abstract void load(InputStream inputStream);
private void load() { private void load() {
load(null); load(null);
@@ -66,7 +66,7 @@ public abstract class AbstractFileStore {
private boolean watch() { private boolean watch() {
try { try {
FileWatchService fileWatchService = new FileWatchService(new String[] {filePath}, new FileWatchService.Listener() { FileWatchService fileWatchService = new FileWatchService(new String[]{filePath}, new FileWatchService.Listener() {
@Override @Override
public void onChanged(String path) { public void onChanged(String path) {
log.info("The file changed, reload the context"); log.info("The file changed, reload the context");

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

@@ -51,51 +51,52 @@ public class DashboardCollectServiceImpl implements DashboardCollectService {
private final static Logger log = LoggerFactory.getLogger(DashboardCollectServiceImpl.class); private final static Logger log = LoggerFactory.getLogger(DashboardCollectServiceImpl.class);
private LoadingCache<String, List<String>> brokerMap = CacheBuilder.newBuilder() private LoadingCache<String, List<String>> brokerMap = CacheBuilder.newBuilder()
.maximumSize(1000) .maximumSize(1000)
.concurrencyLevel(10) .concurrencyLevel(10)
.recordStats() .recordStats()
.ticker(Ticker.systemTicker()) .ticker(Ticker.systemTicker())
.removalListener(new RemovalListener<Object, Object>() { .removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
log.debug(notification.getKey() + " was removed, cause is " + notification.getCause());
}
})
.build(
new CacheLoader<String, List<String>>() {
@Override @Override
public List<String> load(String key) { public void onRemoval(RemovalNotification<Object, Object> notification) {
List<String> list = Lists.newArrayList(); log.debug(notification.getKey() + " was removed, cause is " + notification.getCause());
return list;
} }
} })
); .build(
new CacheLoader<String, List<String>>() {
@Override
public List<String> load(String key) {
List<String> list = Lists.newArrayList();
return list;
}
}
);
private LoadingCache<String, List<String>> topicMap = CacheBuilder.newBuilder() private LoadingCache<String, List<String>> topicMap = CacheBuilder.newBuilder()
.maximumSize(1000) .maximumSize(1000)
.concurrencyLevel(10) .concurrencyLevel(10)
.recordStats() .recordStats()
.ticker(Ticker.systemTicker()) .ticker(Ticker.systemTicker())
.removalListener(new RemovalListener<Object, Object>() { .removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
log.debug(notification.getKey() + " was removed, cause is " + notification.getCause());
}
})
.build(
new CacheLoader<String, List<String>>() {
@Override @Override
public List<String> load(String key) { public void onRemoval(RemovalNotification<Object, Object> notification) {
List<String> list = Lists.newArrayList(); log.debug(notification.getKey() + " was removed, cause is " + notification.getCause());
return list;
} }
} })
); .build(
new CacheLoader<String, List<String>>() {
@Override
public List<String> load(String key) {
List<String> list = Lists.newArrayList();
return list;
}
}
);
@Override @Override
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
*/ */
@@ -48,7 +49,7 @@ public class DashboardServiceImpl implements DashboardService {
} }
/** /**
* @param date format yyyy-MM-dd * @param date format yyyy-MM-dd
* @param topicName * @param topicName
*/ */
@Override @Override

View File

@@ -60,7 +60,7 @@ public class DlqMessageServiceImpl implements DlqMessageService {
} catch (MQClientException e) { } catch (MQClientException e) {
// If the %DLQ%Group does not exist, the message returns null // If the %DLQ%Group does not exist, the message returns null
if (topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX) if (topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)
&& e.getResponseCode() == ResponseCode.TOPIC_NOT_EXIST) { && e.getResponseCode() == ResponseCode.TOPIC_NOT_EXIST) {
return new MessagePage(new PageImpl<>(messageViews, page, 0), query.getTaskId()); return new MessagePage(new PageImpl<>(messageViews, page, 0), query.getTaskId());
} else { } else {
Throwables.throwIfUnchecked(e); Throwables.throwIfUnchecked(e);
@@ -78,8 +78,8 @@ public class DlqMessageServiceImpl implements DlqMessageService {
List<DlqMessageResendResult> batchResendResults = new LinkedList<>(); List<DlqMessageResendResult> batchResendResults = new LinkedList<>();
for (DlqMessageRequest dlqMessage : dlqMessages) { for (DlqMessageRequest dlqMessage : dlqMessages) {
ConsumeMessageDirectlyResult result = messageService.consumeMessageDirectly(dlqMessage.getTopicName(), ConsumeMessageDirectlyResult result = messageService.consumeMessageDirectly(dlqMessage.getTopicName(),
dlqMessage.getMsgId(), dlqMessage.getConsumerGroup(), dlqMessage.getMsgId(), dlqMessage.getConsumerGroup(),
dlqMessage.getClientId()); dlqMessage.getClientId());
DlqMessageResendResult resendResult = new DlqMessageResendResult(result, dlqMessage.getMsgId()); DlqMessageResendResult resendResult = new DlqMessageResendResult(result, dlqMessage.getMsgId());
batchResendResults.add(resendResult); batchResendResults.add(resendResult);
} }

View File

@@ -17,13 +17,10 @@
package org.apache.rocketmq.dashboard.service.impl; package org.apache.rocketmq.dashboard.service.impl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.service.LoginService; import org.apache.rocketmq.dashboard.service.LoginService;
import org.apache.rocketmq.dashboard.service.UserService; import org.apache.rocketmq.dashboard.service.strategy.UserContext;
import org.apache.rocketmq.dashboard.service.provider.UserInfoProvider;
import org.apache.rocketmq.dashboard.util.UserInfoContext; import org.apache.rocketmq.dashboard.util.UserInfoContext;
import org.apache.rocketmq.dashboard.util.WebUtil; import org.apache.rocketmq.dashboard.util.WebUtil;
import org.apache.rocketmq.remoting.protocol.body.UserInfo; import org.apache.rocketmq.remoting.protocol.body.UserInfo;
@@ -33,34 +30,29 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Service @Service
public class LoginServiceImpl implements LoginService { public class LoginServiceImpl implements LoginService {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private RMQConfigure rmqConfigure;
@Autowired @Autowired
private UserService userService; private UserContext userContext;
@Autowired
private UserInfoProvider userInfoProvider;
@Override @Override
public boolean login(HttpServletRequest request, HttpServletResponse response) { public boolean login(HttpServletRequest request, HttpServletResponse response) {
String username = (String) WebUtil.getValueFromSession(request, WebUtil.USER_NAME); String username = (String) WebUtil.getValueFromSession(request, WebUtil.USER_NAME);
if (username != null) { if (username != null) {
UserInfo userInfo = userInfoProvider.getUserInfoByUsername(username); UserInfo userInfo = userContext.queryByUsername(username);
if (userInfo == null) { if (userInfo == null) {
auth(request, response); auth(request, response);
return false; return false;
} }
UserInfoContext.set(WebUtil.USER_NAME, userInfo); UserInfoContext.set(WebUtil.USER_NAME, userInfo);
return true; return true;
} }
auth(request, response); auth(request, response);
return false; return false;
@@ -69,11 +61,7 @@ public class LoginServiceImpl implements LoginService {
protected void auth(HttpServletRequest request, HttpServletResponse response) { protected void auth(HttpServletRequest request, HttpServletResponse response) {
try { try {
String url = WebUtil.getUrl(request); String url = WebUtil.getUrl(request);
try { url = URLEncoder.encode(url, StandardCharsets.UTF_8);
url = URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("url encode:{}", url, e);
}
logger.debug("redirect url : {}", url); logger.debug("redirect url : {}", url);
WebUtil.redirect(response, request, "/#/login?redirect=" + url); WebUtil.redirect(response, request, "/#/login?redirect=" + url);
} catch (IOException e) { } catch (IOException e) {

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

@@ -133,21 +133,21 @@ public class MessageTraceServiceImpl implements MessageTraceService {
} }
private List<SubscriptionNode> buildSubscriptionNodeList( private List<SubscriptionNode> buildSubscriptionNodeList(
Map<String, Pair<MessageTraceView, MessageTraceView>> requestIdTracePairMap) { Map<String, Pair<MessageTraceView, MessageTraceView>> requestIdTracePairMap) {
Map<String, List<TraceNode>> subscriptionTraceNodeMap = Maps.newHashMap(); Map<String, List<TraceNode>> subscriptionTraceNodeMap = Maps.newHashMap();
for (Pair<MessageTraceView, MessageTraceView> traceNodePair : requestIdTracePairMap.values()) { for (Pair<MessageTraceView, MessageTraceView> traceNodePair : requestIdTracePairMap.values()) {
List<TraceNode> traceNodeList = subscriptionTraceNodeMap List<TraceNode> traceNodeList = subscriptionTraceNodeMap
.computeIfAbsent(buildGroupName(traceNodePair), (o) -> Lists.newArrayList()); .computeIfAbsent(buildGroupName(traceNodePair), (o) -> Lists.newArrayList());
traceNodeList.add(buildConsumeMessageTraceNode(traceNodePair)); traceNodeList.add(buildConsumeMessageTraceNode(traceNodePair));
} }
return subscriptionTraceNodeMap.entrySet().stream() return subscriptionTraceNodeMap.entrySet().stream()
.map((Function<Map.Entry<String, List<TraceNode>>, SubscriptionNode>) subscriptionEntry -> { .map((Function<Map.Entry<String, List<TraceNode>>, SubscriptionNode>) subscriptionEntry -> {
List<TraceNode> traceNodeList = subscriptionEntry.getValue(); List<TraceNode> traceNodeList = subscriptionEntry.getValue();
SubscriptionNode subscriptionNode = new SubscriptionNode(); SubscriptionNode subscriptionNode = new SubscriptionNode();
subscriptionNode.setSubscriptionGroup(subscriptionEntry.getKey()); subscriptionNode.setSubscriptionGroup(subscriptionEntry.getKey());
subscriptionNode.setConsumeNodeList(sortTraceNodeListByBeginTimestamp(traceNodeList)); subscriptionNode.setConsumeNodeList(sortTraceNodeListByBeginTimestamp(traceNodeList));
return subscriptionNode; return subscriptionNode;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
private <E> E getTraceValue(Pair<MessageTraceView, MessageTraceView> traceNodePair, Function<MessageTraceView, E> function) { private <E> E getTraceValue(Pair<MessageTraceView, MessageTraceView> traceNodePair, Function<MessageTraceView, E> function) {
@@ -206,7 +206,7 @@ public class MessageTraceServiceImpl implements MessageTraceService {
private void putIntoMessageTraceViewGroupMap(MessageTraceView messageTraceView, private void putIntoMessageTraceViewGroupMap(MessageTraceView messageTraceView,
Map<String, Pair<MessageTraceView, MessageTraceView>> messageTraceViewGroupMap) { Map<String, Pair<MessageTraceView, MessageTraceView>> messageTraceViewGroupMap) {
Pair<MessageTraceView, MessageTraceView> messageTracePair = messageTraceViewGroupMap Pair<MessageTraceView, MessageTraceView> messageTracePair = messageTraceViewGroupMap
.computeIfAbsent(messageTraceView.getRequestId(), (o) -> new Pair<>(null, null)); .computeIfAbsent(messageTraceView.getRequestId(), (o) -> new Pair<>(null, null));
switch (TraceType.valueOf(messageTraceView.getTraceType())) { switch (TraceType.valueOf(messageTraceView.getTraceType())) {
case SubBefore: case SubBefore:
messageTracePair.setObject1(messageTraceView); messageTracePair.setObject1(messageTraceView);

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

@@ -0,0 +1,67 @@
/*
* 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.service.strategy;
import lombok.AllArgsConstructor;
import org.apache.rocketmq.dashboard.service.ClusterInfoService;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class AclUserStrategy implements UserStrategy {
private static final Logger log = LoggerFactory.getLogger(AclUserStrategy.class);
private final MQAdminExt mqAdminExt;
private final ClusterInfoService clusterInfoService;
@Override
public UserInfo getUserInfoByUsername(String username) {
ClusterInfo clusterInfo = clusterInfoService.get();
if (clusterInfo == null || clusterInfo.getBrokerAddrTable() == null || clusterInfo.getBrokerAddrTable().isEmpty()) {
log.warn("Cluster information is not available or has no broker addresses.");
return null;
}
for (BrokerData brokerLiveInfo : clusterInfo.getBrokerAddrTable().values()) {
if (brokerLiveInfo == null || brokerLiveInfo.getBrokerAddrs() == null || brokerLiveInfo.getBrokerAddrs().isEmpty()) {
continue;
}
String brokerAddr = brokerLiveInfo.getBrokerAddrs().get(0L); // Assuming 0L is the primary address
if (brokerAddr == null) {
continue;
}
try {
UserInfo userInfo = mqAdminExt.getUser(brokerAddr, username);
if (userInfo != null) {
return userInfo;
}
} catch (Exception e) {
log.warn("Failed to get user {} from broker {}. Trying next broker if available. Error: {}", username, brokerAddr, e.getMessage());
}
}
return null;
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.service.strategy;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.User;
import org.apache.rocketmq.dashboard.service.impl.AbstractFileStore;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import java.io.FileReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class FileUserStrategy implements UserStrategy, InitializingBean {
@Resource
private RMQConfigure configure;
private FileBasedUserInfoStore fileBasedUserInfoStore;
@Override
public void afterPropertiesSet() throws Exception {
if (configure.isLoginRequired()) {
fileBasedUserInfoStore = new FileBasedUserInfoStore(configure);
}
}
@Override
public UserInfo getUserInfoByUsername(String username) {
User user = fileBasedUserInfoStore.queryByUsernameAndPassword(username);
if (user != null) {
return UserInfo.of(user.getName(), user.getPassword(), user.getType() == 0 ? "normal" : "super");
}
return null;
}
public static class FileBasedUserInfoStore extends AbstractFileStore {
private static final String FILE_NAME = "users.properties";
private static Map<String, User> userMap = new ConcurrentHashMap<>();
public FileBasedUserInfoStore(RMQConfigure configure) {
super(configure, FILE_NAME);
}
@Override
public void load(InputStream inputStream) {
Properties prop = new Properties();
try {
if (inputStream == null) {
prop.load(new FileReader(filePath));
} else {
prop.load(inputStream);
}
} catch (Exception e) {
log.error("load user.properties failed", e);
throw new ServiceException(0, String.format("Failed to load loginUserInfo property file: %s", filePath));
}
Map<String, User> loadUserMap = new HashMap<>();
String[] arrs;
int role;
for (String key : prop.stringPropertyNames()) {
String v = prop.getProperty(key);
if (v == null)
continue;
arrs = v.split(",", 2);
if (arrs.length == 0) {
continue;
} else if (arrs.length == 1) {
role = 0;
} else {
role = Integer.parseInt(arrs[1].trim());
}
loadUserMap.put(key, new User(key, arrs[0].trim(), role));
}
userMap.clear();
userMap.putAll(loadUserMap);
}
public User queryByName(String name) {
return userMap.get(name);
}
public User queryByUsernameAndPassword(@NotNull String username) {
User user = queryByName(username);
if (user != null) {
return user.cloneOne();
}
return null;
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.service.strategy;
import jakarta.annotation.PostConstruct;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class UserContext {
private UserStrategy userStrategy;
@Autowired
private Map<String, UserStrategy> userStrategies;
@Value("${rocketmq.config.authMode}")
private String authMode;
@PostConstruct
public void init() {
switch (authMode.toLowerCase()) {
case "acl":
this.userStrategy = userStrategies.get("aclUserStrategy");
break;
case "file":
default:
this.userStrategy = userStrategies.get("fileUserStrategy");
break;
}
}
public UserInfo queryByUsername(String username) {
return userStrategy.getUserInfoByUsername(username);
}
}

View File

@@ -15,11 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.rocketmq.dashboard.admin; package org.apache.rocketmq.dashboard.service.strategy;
import org.apache.rocketmq.tools.admin.MQAdminExt; import org.apache.rocketmq.remoting.protocol.body.UserInfo;
@FunctionalInterface public interface UserStrategy {
public interface MQAdminExtCallback<T> { UserInfo getUserInfoByUsername(String username);
T doInMQAdminExt(MQAdminExt mqAdminExt) throws Exception;
} }

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

@@ -37,18 +37,17 @@ public class GlobalRestfulResponseBodyAdvice implements ResponseBodyAdvice<Objec
@Override @Override
public Object beforeBodyWrite( public Object beforeBodyWrite(
Object obj, MethodParameter methodParameter, MediaType mediaType, Object obj, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> converterType, Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
Annotation originalControllerReturnValue = methodParameter.getMethodAnnotation(OriginalControllerReturnValue.class); Annotation originalControllerReturnValue = methodParameter.getMethodAnnotation(OriginalControllerReturnValue.class);
if (originalControllerReturnValue != null) { if (originalControllerReturnValue != null) {
return obj; return obj;
} }
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

@@ -44,7 +44,7 @@ public class CollectTaskRunnble implements Runnable {
private DashboardCollectService dashboardCollectService; private DashboardCollectService dashboardCollectService;
public CollectTaskRunnble(String topic, MQAdminExt mqAdminExt, public CollectTaskRunnble(String topic, MQAdminExt mqAdminExt,
DashboardCollectService dashboardCollectService) { DashboardCollectService dashboardCollectService) {
this.topic = topic; this.topic = topic;
this.mqAdminExt = mqAdminExt; this.mqAdminExt = mqAdminExt;
this.dashboardCollectService = dashboardCollectService; this.dashboardCollectService = dashboardCollectService;

View File

@@ -80,15 +80,14 @@ public class DashboardCollectTask {
this.addSystemTopic(); this.addSystemTopic();
for (String topic : topicSet) { for (String topic : topicSet) {
if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)
|| topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX) || topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)
|| TopicValidator.isSystemTopic(topic)) { || TopicValidator.isSystemTopic(topic)) {
continue; continue;
} }
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,21 +204,19 @@ 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);
} }
} }
private void writeFile(LoadingCache<String, List<String>> map, Map<String, List<String>> fileMap, private void writeFile(LoadingCache<String, List<String>> map, Map<String, List<String>> fileMap,
File file) throws IOException { File file) throws IOException {
Map<String, List<String>> newMap = map.asMap(); Map<String, List<String>> newMap = map.asMap();
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

@@ -31,10 +31,10 @@ import java.util.List;
public class ExcelUtil { public class ExcelUtil {
public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName,
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);
@@ -44,7 +44,7 @@ public class ExcelUtil {
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
EasyExcel.write(getOutputStream(fileName, response), clazz) EasyExcel.write(getOutputStream(fileName, response), clazz)
.excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data); .excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data);
} }
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception { private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {

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,11 +117,10 @@ 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,12 +130,11 @@ 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

@@ -107,7 +107,7 @@ public class WebUtil {
HttpSession session = request.getSession(false); HttpSession session = request.getSession(false);
if (session != null) { if (session != null) {
return session.getAttribute(key); return session.getAttribute(key);
} }
return null; return null;

View File

@@ -58,6 +58,12 @@ rocketmq:
ticketKey: ticket ticketKey: ticket
# must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required # must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
loginRequired: false loginRequired: false
# Authentication mode for RocketMQ Dashboard
# Available options:
# - 'file': Use username/password stored in a file (requires 'auth.file.path')
# - 'acl': Use credentials from ACL system (requires 'acl.access.key' and 'acl.secret.key')
# Default: file
authMode: file
useTLS: false useTLS: false
proxyAddr: 127.0.0.1:8080 proxyAddr: 127.0.0.1:8080
proxyAddrs: proxyAddrs:

View File

@@ -17,34 +17,34 @@
--> -->
<configuration> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8"> <encoder charset="UTF-8">
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %p %t - %m%n</pattern>
</encoder>
</appender>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${user.home}/logs/dashboardlogs/rocketmq-dashboard.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.home}/logs/dashboardlogs/rocketmq-dashboard-%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>104857600</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %p %t - %m%n</pattern> <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset> </encoder>
</encoder> </appender>
</appender>
<root level="INFO"> <appender name="FILE"
<appender-ref ref="STDOUT" /> class="ch.qos.logback.core.rolling.RollingFileAppender">
<appender-ref ref="FILE" /> <file>${user.home}/logs/dashboardlogs/rocketmq-dashboard.log</file>
</root> <append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.home}/logs/dashboardlogs/rocketmq-dashboard-%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>104857600</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</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

View File

@@ -17,12 +17,20 @@
package org.apache.rocketmq.dashboard; package org.apache.rocketmq.dashboard;
import java.lang.reflect.Field; import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import java.util.ArrayList; import org.apache.rocketmq.remoting.protocol.route.BrokerData;
import java.util.List;
import org.mockito.internal.util.MockUtil; import org.mockito.internal.util.MockUtil;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class BaseTest { public class BaseTest {
/** /**
* Inject the corresponding mock class automatically * Inject the corresponding mock class automatically
@@ -43,7 +51,7 @@ public class BaseTest {
for (Field localField : localFields) { for (Field localField : localFields) {
for (Field field : fields) { for (Field field : fields) {
if (field.getName() if (field.getName()
.equals(localField.getName())) { .equals(localField.getName())) {
Object obj = ReflectionUtils.getField(field, target); Object obj = ReflectionUtils.getField(field, target);
if (obj == null) { if (obj == null) {
Object destObj = ReflectionUtils.getField(localField, localDest); Object destObj = ReflectionUtils.getField(localField, localDest);
@@ -69,4 +77,20 @@ public class BaseTest {
ReflectionUtils.doWithFields(leafClass, fc); ReflectionUtils.doWithFields(leafClass, fc);
return fields; return fields;
} }
protected ClusterInfo getClusterInfo() {
ClusterInfo clusterInfo = new ClusterInfo();
Map<String, Set<String>> clusterAddrTable = new HashMap<>();
clusterAddrTable.put("DefaultCluster", new HashSet<>(Arrays.asList("broker-a")));
Map<String, BrokerData> brokerAddrTable = new HashMap<>();
BrokerData brokerData = new BrokerData();
brokerData.setBrokerName("broker-a");
HashMap<Long, String> brokerNameTable = new HashMap<>();
brokerNameTable.put(0L, "localhost:10911");
brokerData.setBrokerAddrs(brokerNameTable);
brokerAddrTable.put("broker-a", brokerData);
clusterInfo.setBrokerAddrTable(brokerAddrTable);
clusterInfo.setClusterAddrTable(clusterAddrTable);
return clusterInfo;
}
} }

View File

@@ -17,26 +17,45 @@
package org.apache.rocketmq.dashboard.admin; package org.apache.rocketmq.dashboard.admin;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.rocketmq.dashboard.aspect.admin.MQAdminAspect; import org.apache.rocketmq.dashboard.aspect.admin.MQAdminAspect;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
import org.apache.rocketmq.tools.admin.MQAdminExt; import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.MethodSignature;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MQAdminAspectTest { public class MQAdminAspectTest {
@Mock
private RMQConfigure rmqConfigure;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(rmqConfigure.isLoginRequired()).thenReturn(false);
}
@Test @Test
public void testAroundMQAdminMethod() throws Throwable { public void testAroundMQAdminMethod() throws Throwable {
MQAdminAspect mqAdminAspect = new MQAdminAspect(); MQAdminAspect mqAdminAspect = new MQAdminAspect();
Field field = mqAdminAspect.getClass().getDeclaredField("rmqConfigure");
field.setAccessible(true);
field.set(mqAdminAspect, rmqConfigure);
ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
MethodSignature signature = mock(MethodSignature.class); MethodSignature signature = mock(MethodSignature.class);
Method method = mock(Method.class); Method method = mock(Method.class);
@@ -44,16 +63,39 @@ public class MQAdminAspectTest {
when(joinPoint.getSignature()).thenReturn(signature); when(joinPoint.getSignature()).thenReturn(signature);
GenericObjectPool<MQAdminExt> mqAdminExtPool = mock(GenericObjectPool.class); GenericObjectPool<MQAdminExt> mqAdminExtPool = mock(GenericObjectPool.class);
// 1. Mock borrowObject() 行为:第一次抛异常,第二次返回 DefaultMQAdminExt
when(mqAdminExtPool.borrowObject()) when(mqAdminExtPool.borrowObject())
.thenThrow(new RuntimeException("borrowObject exception")) .thenThrow(new RuntimeException("borrowObject exception"))
.thenReturn(new DefaultMQAdminExt()); .thenReturn(new DefaultMQAdminExt());
doNothing().doThrow(new RuntimeException("returnObject exception"))
.when(mqAdminExtPool).returnObject(any()); // 2. Mock returnObject() 行为:第一次什么都不做,第二次抛异常
Field field = mqAdminAspect.getClass().getDeclaredField("mqAdminExtPool"); doNothing().when(mqAdminExtPool).returnObject(any());
doThrow(new RuntimeException("returnObject exception"))
.when(mqAdminExtPool).returnObject(any());
// 3. 通过反射注入 Mock 对象
field = mqAdminAspect.getClass().getDeclaredField("mqAdminExtPool");
field.setAccessible(true); field.setAccessible(true);
field.set(mqAdminAspect, mqAdminExtPool); field.set(mqAdminAspect, mqAdminExtPool);
// exception
mqAdminAspect.aroundMQAdminMethod(joinPoint); // 4. 第一次调用 aroundMQAdminMethod预期 borrowObject() 抛异常
mqAdminAspect.aroundMQAdminMethod(joinPoint); try {
mqAdminAspect.aroundMQAdminMethod(joinPoint);
fail("Expected RuntimeException but no exception was thrown");
} catch (RuntimeException e) {
assertEquals("borrowObject exception", e.getMessage());
}
// 5. 第二次调用 aroundMQAdminMethod预期 borrowObject() 成功,但 returnObject() 抛异常
try {
mqAdminAspect.aroundMQAdminMethod(joinPoint);
fail("Expected RuntimeException but no exception was thrown");
} catch (RuntimeException e) {
assertEquals("returnObject exception", e.getMessage());
}
// 6. 验证 borrowObject() 和 returnObject() 各调用了两次
verify(mqAdminExtPool, times(2)).borrowObject();
verify(mqAdminExtPool, times(1)).returnObject(any());
} }
} }

Some files were not shown because too many files have changed in this diff Show More