mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-10 03:29:59 +08:00
Compare commits
4 Commits
6531929124
...
07793d8aae
Author | SHA1 | Date | |
---|---|---|---|
|
07793d8aae | ||
|
4b9ed97f8f | ||
|
ff73529a75 | ||
|
706082c62f |
@@ -6,19 +6,14 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
### `npm run start`
|
||||
|
||||
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.\
|
||||
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`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
|
@@ -18,13 +18,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="keywords" content="" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>RocketMQ Dashboard</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="description" content=""/>
|
||||
<meta name="keywords" content=""/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<title>RocketMQ Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
@@ -17,19 +17,19 @@
|
||||
|
||||
import React from 'react';
|
||||
import AppRouter from './router'; // 你 router/index.jsx 导出的组件
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import {ToastContainer} from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import {ConfigProvider} from "antd";
|
||||
import {useTheme} from "./store/context/ThemeContext";
|
||||
|
||||
function App() {
|
||||
const {currentTheme} = useTheme();
|
||||
const {currentTheme} = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigProvider theme={currentTheme}>
|
||||
<ToastContainer />
|
||||
<AppRouter />
|
||||
<ToastContainer/>
|
||||
<AppRouter/>
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
|
@@ -15,11 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import {render, screen} from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
render(<App/>);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
@@ -49,7 +49,7 @@ const remoteApi = {
|
||||
if (_redirectHandler) {
|
||||
_redirectHandler(); // 如果设置了重定向处理函数,则调用它
|
||||
}
|
||||
return { __isRedirectHandled: true };
|
||||
return {__isRedirectHandled: true};
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -77,24 +77,24 @@ const remoteApi = {
|
||||
listUsers: async (brokerAddress) => {
|
||||
const params = new URLSearchParams();
|
||||
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();
|
||||
},
|
||||
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ brokerAddress, userInfo })
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({brokerAddress, userInfo})
|
||||
});
|
||||
return await response.json(); // 返回字符串消息
|
||||
},
|
||||
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ brokerAddress, userInfo })
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({brokerAddress, userInfo})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
@@ -103,7 +103,7 @@ const remoteApi = {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
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'
|
||||
});
|
||||
return await response.json();
|
||||
@@ -114,24 +114,24 @@ const remoteApi = {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
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();
|
||||
},
|
||||
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ brokerAddress, subject, policies })
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({brokerAddress, subject, policies})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ brokerAddress, subject, policies })
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({brokerAddress, subject, policies})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
@@ -141,7 +141,7 @@ const remoteApi = {
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
params.append('subject', subject);
|
||||
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'
|
||||
});
|
||||
return await response.json();
|
||||
@@ -159,7 +159,7 @@ const remoteApi = {
|
||||
return data
|
||||
} catch (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;
|
||||
} catch (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) => {
|
||||
@@ -217,7 +217,7 @@ const remoteApi = {
|
||||
return data;
|
||||
} catch (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) => {
|
||||
@@ -236,7 +236,7 @@ const remoteApi = {
|
||||
|
||||
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 数据格式化后写入新窗口
|
||||
@@ -247,10 +247,10 @@ const remoteApi = {
|
||||
newWindow.document.write('</body></html>');
|
||||
newWindow.document.close(); // 关闭文档流,确保内容显示
|
||||
|
||||
return { status: 0, msg: "导出请求成功,内容已在新页面显示" };
|
||||
return {status: 0, msg: "导出请求成功,内容已在新页面显示"};
|
||||
} catch (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;
|
||||
} catch (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);
|
||||
} catch (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 {
|
||||
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();
|
||||
return data;
|
||||
} catch (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;
|
||||
} catch (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;
|
||||
} catch (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;
|
||||
} catch (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: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ consumeGroupName, minCount, maxDiffTotal })
|
||||
body: JSON.stringify({consumeGroupName, minCount, maxDiffTotal})
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (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;
|
||||
} catch (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: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ groupName, brokerNameList })
|
||||
body: JSON.stringify({groupName, brokerNameList})
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (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;
|
||||
} catch (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;
|
||||
} catch (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;
|
||||
} catch (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;
|
||||
} catch (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;
|
||||
} catch (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 () => {
|
||||
@@ -532,7 +532,7 @@ const remoteApi = {
|
||||
return await response.json();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ brokerName, topic })
|
||||
body: JSON.stringify({brokerName, topic})
|
||||
});
|
||||
return await response.json();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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();
|
||||
} catch (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);
|
||||
} catch (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 {
|
||||
const url = new URL(remoteApi.buildUrl('/dashboard/broker.query'));
|
||||
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();
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
if (error.name === 'TimeoutError') {
|
||||
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 {
|
||||
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'));
|
||||
url.searchParams.append('date', date);
|
||||
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();
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
if (error.name === 'TimeoutError') {
|
||||
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 {
|
||||
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) => {
|
||||
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();
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
if (error.name === 'TimeoutError') {
|
||||
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 {
|
||||
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);
|
||||
} catch (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);
|
||||
} catch (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 {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/proxy/addProxyAddr.do"), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ newProxyAddr }).toString()
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({newProxyAddr}).toString()
|
||||
});
|
||||
const data = await response.json();
|
||||
callback(data);
|
||||
} catch (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) => {
|
||||
@@ -882,19 +882,19 @@ const remoteApi = {
|
||||
return data;
|
||||
} catch (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 () => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/login/logout.do"),{
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/login/logout.do"), {
|
||||
method: 'POST'
|
||||
});
|
||||
return await response.json()
|
||||
}catch (error) {
|
||||
} catch (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};
|
||||
|
@@ -16,20 +16,20 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Form, Input, Typography, Modal } from 'antd';
|
||||
import {Form, Input, Typography} from 'antd';
|
||||
import moment from 'moment';
|
||||
import { useLanguage } from '../i18n/LanguageContext'; // 根据实际路径调整
|
||||
import {useLanguage} from '../i18n/LanguageContext'; // 根据实际路径调整
|
||||
|
||||
const { Text } = Typography;
|
||||
const {Text} = Typography;
|
||||
|
||||
const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
|
||||
const { t } = useLanguage();
|
||||
const DlqMessageDetailViewDialog = ({ngDialogData}) => {
|
||||
const {t} = useLanguage();
|
||||
|
||||
const messageView = ngDialogData?.messageView || {};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
|
||||
<div style={{padding: '20px'}}>
|
||||
<Form layout="horizontal" labelCol={{span: 4}} wrapperCol={{span: 20}}>
|
||||
<Form.Item label="Message ID:">
|
||||
<Text strong>{messageView.msgId}</Text>
|
||||
</Form.Item>
|
||||
@@ -39,7 +39,7 @@ const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
|
||||
<Form.Item label="Properties:">
|
||||
<Input.TextArea
|
||||
value={typeof messageView.properties === 'object' ? JSON.stringify(messageView.properties, null, 2) : messageView.properties}
|
||||
style={{ minHeight: 100, resize: 'none' }}
|
||||
style={{minHeight: 100, resize: 'none'}}
|
||||
readOnly
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -61,7 +61,7 @@ const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
|
||||
<Form.Item label="Message body:">
|
||||
<Input.TextArea
|
||||
value={messageView.messageBody}
|
||||
style={{ minHeight: 100, resize: 'none' }}
|
||||
style={{minHeight: 100, resize: 'none'}}
|
||||
readOnly
|
||||
/>
|
||||
</Form.Item>
|
||||
|
@@ -16,16 +16,16 @@
|
||||
*/
|
||||
|
||||
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 { ExclamationCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { useLanguage } from '../i18n/LanguageContext';
|
||||
import { remoteApi } from '../api/remoteApi/remoteApi'; // 确保这个路径正确
|
||||
import {SyncOutlined} from '@ant-design/icons';
|
||||
import {useLanguage} from '../i18n/LanguageContext';
|
||||
import {remoteApi} from '../api/remoteApi/remoteApi'; // 确保这个路径正确
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const {Text, Paragraph} = Typography;
|
||||
|
||||
const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResendMessage }) => {
|
||||
const { t } = useLanguage();
|
||||
const MessageDetailViewDialog = ({visible, onCancel, messageId, topic, onResendMessage}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [messageDetail, setMessageDetail] = 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}>
|
||||
{error && (
|
||||
<Paragraph type="danger" style={{ textAlign: 'center' }}>
|
||||
<Paragraph type="danger" style={{textAlign: 'center'}}>
|
||||
{error}
|
||||
</Paragraph>
|
||||
)}
|
||||
{messageDetail ? ( // 确保 messageDetail 存在时才渲染内容
|
||||
<>
|
||||
{/* 消息信息部分 */}
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_INFO}</Text>} bordered column={2} size="small" style={{ marginBottom: 20 }}>
|
||||
<Descriptions.Item label="Topic" span={2}><Text 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 title={<Text strong>{t.MESSAGE_INFO}</Text>} bordered column={2} size="small"
|
||||
style={{marginBottom: 20}}>
|
||||
<Descriptions.Item label="Topic" span={2}><Text
|
||||
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="StoreTime">
|
||||
{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")}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Queue ID">{messageDetail.messageView.queueId}</Descriptions.Item>
|
||||
<Descriptions.Item label="Queue Offset">{messageDetail.messageView.queueOffset}</Descriptions.Item>
|
||||
<Descriptions.Item label="StoreSize">{messageDetail.messageView.storeSize} bytes</Descriptions.Item>
|
||||
<Descriptions.Item label="ReconsumeTimes">{messageDetail.messageView.reconsumeTimes}</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="Queue Offset">{messageDetail.messageView.queueOffset}</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="SysFlag">{messageDetail.messageView.sysFlag}</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>
|
||||
|
||||
{/* 消息属性部分 */}
|
||||
{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]) => (
|
||||
<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 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>
|
||||
<Paragraph
|
||||
copyable
|
||||
@@ -146,9 +157,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
{messageDetail.messageTrackList && messageDetail.messageTrackList.length > 0 ? (
|
||||
<>
|
||||
<Text strong>{t.MESSAGE_TRACKING}</Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<div style={{marginTop: 10}}>
|
||||
{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}>
|
||||
{track.consumerGroup}
|
||||
</Descriptions.Item>
|
||||
@@ -165,10 +177,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t.OPERATION}>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
icon={<SyncOutlined/>}
|
||||
onClick={() => onResendMessage(messageDetail.messageView, track.consumerGroup)}
|
||||
size="small"
|
||||
style={{ marginRight: 8 }}
|
||||
style={{marginRight: 8}}
|
||||
>
|
||||
{t.RESEND_MESSAGE}
|
||||
</Button>
|
||||
@@ -181,7 +193,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
ellipsis={{
|
||||
rows: 2, // 默认显示2行
|
||||
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}
|
||||
@@ -198,7 +213,8 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
</>
|
||||
) : (
|
||||
// 当 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>
|
||||
</Modal>
|
||||
|
@@ -15,15 +15,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Form, Input, Typography, Collapse, Table, Tag } from 'antd';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {Collapse, Form, Input, Table, Tag, Typography} from 'antd';
|
||||
import moment from 'moment';
|
||||
import { useLanguage } from '../i18n/LanguageContext';
|
||||
import {useLanguage} from '../i18n/LanguageContext';
|
||||
import Paragraph from "antd/es/skeleton/Paragraph";
|
||||
import * as echarts from 'echarts'; // Import ECharts
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Panel } = Collapse;
|
||||
const {Text} = Typography;
|
||||
const {Panel} = Collapse;
|
||||
|
||||
// Constants for styling and formatting, derived from the example
|
||||
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 TRANSACTION_CHECK_COST_TIME = 50; // transactionTraceNode do not have costTime, assume it cost 50ms
|
||||
|
||||
const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
const { t } = useLanguage();
|
||||
const MessageTraceDetailViewDialog = ({ngDialogData}) => {
|
||||
const {t} = useLanguage();
|
||||
const messageTraceGraphRef = useRef(null);
|
||||
|
||||
const producerNode = ngDialogData?.producerNode;
|
||||
@@ -125,6 +125,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
}
|
||||
return `Cost Time: ${formatCostTimeStr(costTime)}<br/>`
|
||||
}
|
||||
|
||||
function buildTimeStamp(timestamp) {
|
||||
if (timestamp < 0) {
|
||||
return 'N/A';
|
||||
@@ -323,94 +324,181 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
|
||||
// ... (rest of your existing component code)
|
||||
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.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' },
|
||||
{
|
||||
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.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 = [
|
||||
{ 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.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' },
|
||||
{
|
||||
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.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 (
|
||||
<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={{padding: '20px', backgroundColor: '#f0f2f5'}}>
|
||||
<div style={{
|
||||
marginBottom: '20px',
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'
|
||||
}}>
|
||||
<Collapse defaultActiveKey={['messageTraceGraph']} expandIconPosition="right">
|
||||
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.MESSAGE_TRACE_GRAPH}</Typography.Title>} key="messageTraceGraph">
|
||||
<div ref={messageTraceGraphRef} style={{ height: 500, width: '100%', backgroundColor: '#fff', padding: '10px' }}>
|
||||
<Panel header={<Typography.Title level={3} style={{
|
||||
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 */}
|
||||
{(!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>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</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">
|
||||
<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 ? (
|
||||
<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' }}>
|
||||
<Typography.Title level={4} style={{ marginBottom: '20px' }}>
|
||||
{t.SEND_MESSAGE_INFO} : ( {t.MESSAGE_ID} <Text strong copyable>{producerNode.msgId}</Text> )
|
||||
<div style={{padding: '16px', backgroundColor: '#fff'}}>
|
||||
<Typography.Title level={4} style={{marginBottom: '20px'}}>
|
||||
{t.SEND_MESSAGE_INFO} : ( {t.MESSAGE_ID} <Text strong
|
||||
copyable>{producerNode.msgId}</Text> )
|
||||
</Typography.Title>
|
||||
<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>}>
|
||||
<Input value={producerNode.topic} readOnly />
|
||||
<Input value={producerNode.topic} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.PRODUCER_GROUP}</Text>}>
|
||||
<Input value={producerNode.groupName} readOnly />
|
||||
<Input value={producerNode.groupName} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.MESSAGE_KEY}</Text>}>
|
||||
<Input value={producerNode.keys} readOnly />
|
||||
<Input value={producerNode.keys} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.TAG}</Text>}>
|
||||
<Input value={producerNode.tags} readOnly />
|
||||
<Input value={producerNode.tags} readOnly/>
|
||||
</Form.Item>
|
||||
|
||||
<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 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 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 label={<Text strong>{t.MSG_TYPE}</Text>}>
|
||||
<Input value={producerNode.traceNode.msgType} readOnly />
|
||||
<Input value={producerNode.traceNode.msgType} readOnly/>
|
||||
</Form.Item>
|
||||
|
||||
<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 label={<Text strong>{t.STORE_HOST}</Text>}>
|
||||
<Input value={producerNode.traceNode.storeHost} readOnly />
|
||||
<Input value={producerNode.traceNode.storeHost} readOnly/>
|
||||
</Form.Item>
|
||||
<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 label={<Text strong>{t.OFFSET_MSG_ID}</Text>}>
|
||||
<Input value={producerNode.offSetMsgId} readOnly />
|
||||
<Input value={producerNode.offSetMsgId} readOnly/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
{producerNode.transactionNodeList && producerNode.transactionNodeList.length > 0 && (
|
||||
<div style={{ marginTop: '30px' }}>
|
||||
<Typography.Title level={4} style={{ marginBottom: '15px' }}>{t.CHECK_TRANSACTION_INFO}:</Typography.Title>
|
||||
<div style={{marginTop: '30px'}}>
|
||||
<Typography.Title level={4}
|
||||
style={{marginBottom: '15px'}}>{t.CHECK_TRANSACTION_INFO}:</Typography.Title>
|
||||
<Table
|
||||
columns={transactionColumns}
|
||||
dataSource={producerNode.transactionNodeList}
|
||||
@@ -418,7 +506,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
bordered
|
||||
pagination={false}
|
||||
size="middle"
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={{x: 'max-content'}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -428,22 +516,31 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
</Collapse>
|
||||
</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">
|
||||
<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 ? (
|
||||
<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 => (
|
||||
<Collapse
|
||||
key={subscriptionNode.subscriptionGroup}
|
||||
style={{ marginBottom: '10px', border: '1px solid #e0e0e0', borderRadius: '4px' }}
|
||||
style={{marginBottom: '10px', border: '1px solid #e0e0e0', borderRadius: '4px'}}
|
||||
defaultActiveKey={[subscriptionNode.subscriptionGroup]}
|
||||
ghost
|
||||
>
|
||||
<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}
|
||||
>
|
||||
<Table
|
||||
@@ -453,7 +550,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
bordered
|
||||
pagination={false}
|
||||
size="middle"
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={{x: 'max-content'}}
|
||||
/>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
|
@@ -16,29 +16,29 @@
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import { Layout, Menu, Dropdown, Button, Drawer, Grid, Space } from 'antd';
|
||||
import {GlobalOutlined, DownOutlined, UserOutlined, MenuOutlined, BgColorsOutlined} from '@ant-design/icons';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLanguage } from '../i18n/LanguageContext';
|
||||
import {Button, Drawer, Dropdown, Grid, Layout, Menu, Space} from 'antd';
|
||||
import {BgColorsOutlined, DownOutlined, GlobalOutlined, MenuOutlined, UserOutlined} from '@ant-design/icons';
|
||||
import {useLocation, useNavigate} from 'react-router-dom';
|
||||
import {useLanguage} from '../i18n/LanguageContext';
|
||||
import {useTheme} from "../store/context/ThemeContext";
|
||||
import {remoteApi} from "../api/remoteApi/remoteApi";
|
||||
|
||||
const { Header } = Layout;
|
||||
const { useBreakpoint } = Grid; // Used to determine screen breakpoints
|
||||
const {Header} = Layout;
|
||||
const {useBreakpoint} = Grid; // Used to determine screen breakpoints
|
||||
|
||||
const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
const Navbar = ({rmqVersion = true, showAcl = true}) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { lang, setLang, t } = useLanguage();
|
||||
const {lang, setLang, t} = useLanguage();
|
||||
const screens = useBreakpoint(); // Get current screen size breakpoints
|
||||
const { currentThemeName, setCurrentThemeName } = useTheme();
|
||||
const {currentThemeName, setCurrentThemeName} = useTheme();
|
||||
const [userName, setUserName] = useState(null);
|
||||
const [drawerVisible, setDrawerVisible] = useState(false); // Controls drawer visibility
|
||||
|
||||
// Get selected menu item key based on current route path
|
||||
const getPath = () => location.pathname.replace('/', '');
|
||||
|
||||
const handleMenuClick = ({ key }) => {
|
||||
const handleMenuClick = ({key}) => {
|
||||
navigate(`/${key}`);
|
||||
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");
|
||||
if (storedUsername) {
|
||||
setUserName(storedUsername);
|
||||
}else {
|
||||
} else {
|
||||
setUserName(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const langMenu = (
|
||||
<Menu onClick={({ key }) => setLang(key)}>
|
||||
<Menu onClick={({key}) => setLang(key)}>
|
||||
<Menu.Item key="en">{t.ENGLISH}</Menu.Item>
|
||||
<Menu.Item key="zh">{t.CHINESE}</Menu.Item>
|
||||
</Menu>
|
||||
@@ -82,7 +82,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
);
|
||||
|
||||
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="pink">{t.PINK}</Menu.Item>
|
||||
<Menu.Item key="green">{t.GREEN}</Menu.Item>
|
||||
@@ -92,17 +92,17 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
|
||||
// Menu item configuration
|
||||
const menuItems = [
|
||||
{ key: 'ops', label: t.OPS },
|
||||
...(rmqVersion ? [{ key: 'proxy', label: t.PROXY }] : []),
|
||||
{ key: '', label: t.DASHBOARD }, // Dashboard corresponds to root path
|
||||
{ key: 'cluster', label: t.CLUSTER },
|
||||
{ key: 'topic', label: t.TOPIC },
|
||||
{ key: 'consumer', label: t.CONSUMER },
|
||||
{ key: 'producer', label: t.PRODUCER },
|
||||
{ key: 'message', label: t.MESSAGE },
|
||||
{ key: 'dlqMessage', label: t.DLQ_MESSAGE },
|
||||
{ key: 'messageTrace', label: t.MESSAGETRACE },
|
||||
...(showAcl ? [{ key: 'acl', label: t.WHITE_LIST }] : []),
|
||||
{key: 'ops', label: t.OPS},
|
||||
...(rmqVersion ? [{key: 'proxy', label: t.PROXY}] : []),
|
||||
{key: '', label: t.DASHBOARD}, // Dashboard corresponds to root path
|
||||
{key: 'cluster', label: t.CLUSTER},
|
||||
{key: 'topic', label: t.TOPIC},
|
||||
{key: 'consumer', label: t.CONSUMER},
|
||||
{key: 'producer', label: t.PRODUCER},
|
||||
{key: 'message', label: t.MESSAGE},
|
||||
{key: 'dlqMessage', label: t.DLQ_MESSAGE},
|
||||
{key: 'messageTrace', label: t.MESSAGETRACE},
|
||||
...(showAcl ? [{key: 'acl', label: t.ACL_MANAGEMENT}] : []),
|
||||
];
|
||||
|
||||
// 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
|
||||
}}
|
||||
>
|
||||
<div className="navbar-left" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div className="navbar-left" style={{display: 'flex', alignItems: 'center'}}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
@@ -141,33 +141,33 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
mode="horizontal"
|
||||
items={menuItems}
|
||||
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>
|
||||
|
||||
<Space size={isExtraSmallScreen ? 8 : 16} > {/* Adjust spacing for buttons */}
|
||||
<Space size={isExtraSmallScreen ? 8 : 16}> {/* Adjust spacing for buttons */}
|
||||
{/* Theme switch button */}
|
||||
<Dropdown overlay={themeMenu}>
|
||||
<Button icon={<BgColorsOutlined />} size="small">
|
||||
<Button icon={<BgColorsOutlined/>} size="small">
|
||||
{!isExtraSmallScreen && `${t.TOPIC}: ${currentThemeName}`}
|
||||
<DownOutlined />
|
||||
<DownOutlined/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Dropdown overlay={langMenu}>
|
||||
<Button icon={<GlobalOutlined />} size="small">
|
||||
<Button icon={<GlobalOutlined/>} size="small">
|
||||
{!isExtraSmallScreen && t.CHANGE_LANG} {/* Hide text on extra small screens */}
|
||||
<DownOutlined />
|
||||
<DownOutlined/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
||||
{userName && (
|
||||
<Dropdown overlay={userMenu}>
|
||||
{/* 使用一个可点击的元素作为 Dropdown 的唯一子元素 */}
|
||||
<a onClick={e => e.preventDefault()} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<UserOutlined style={{ marginRight: 8 }} /> {/* 添加一些间距 */}
|
||||
<a onClick={e => e.preventDefault()} style={{display: 'flex', alignItems: 'center'}}>
|
||||
<UserOutlined style={{marginRight: 8}}/> {/* 添加一些间距 */}
|
||||
{userName}
|
||||
<DownOutlined style={{ marginLeft: 8 }} />
|
||||
<DownOutlined style={{marginLeft: 8}}/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
)}
|
||||
@@ -175,9 +175,9 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
{isSmallScreen && ( // Display hamburger icon on small screens
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<MenuOutlined />}
|
||||
icon={<MenuOutlined/>}
|
||||
onClick={() => setDrawerVisible(true)}
|
||||
style={{ marginLeft: isExtraSmallScreen ? 8 : 16 }} // Adjust margin for hamburger icon
|
||||
style={{marginLeft: isExtraSmallScreen ? 8 : 16}} // Adjust margin for hamburger icon
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
@@ -192,7 +192,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
open={drawerVisible}
|
||||
// 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.
|
||||
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
|
||||
>
|
||||
<Menu
|
||||
@@ -201,7 +201,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
mode="inline" // Use vertical menu in drawer
|
||||
items={menuItems}
|
||||
theme="dark"
|
||||
style={{ height: '100%', borderRight: 0 }} // Ensure menu fills the drawer
|
||||
style={{height: '100%', borderRight: 0}} // Ensure menu fills the drawer
|
||||
/>
|
||||
</Drawer>
|
||||
</Header>
|
||||
|
@@ -15,23 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Input, Select, Tag, Space } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import React, { useState } from 'react';
|
||||
import {Input, Select, Space, Tag} from 'antd';
|
||||
import {PlusOutlined} from '@ant-design/icons';
|
||||
import React, {useState} from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
// 资源类型枚举
|
||||
const resourceTypes = [
|
||||
{ value: 0, label: 'Unknown', prefix: 'UNKNOWN' },
|
||||
{ value: 1, label: 'Any', prefix: 'ANY' },
|
||||
{ value: 2, label: 'Cluster', prefix: 'CLUSTER' },
|
||||
{ value: 3, label: 'Namespace', prefix: 'NAMESPACE' },
|
||||
{ value: 4, label: 'Topic', prefix: 'TOPIC' },
|
||||
{ value: 5, label: 'Group', prefix: 'GROUP' },
|
||||
{value: 0, label: 'Unknown', prefix: 'UNKNOWN'},
|
||||
{value: 1, label: 'Any', prefix: 'ANY'},
|
||||
{value: 2, label: 'Cluster', prefix: 'CLUSTER'},
|
||||
{value: 3, label: 'Namespace', prefix: 'NAMESPACE'},
|
||||
{value: 4, label: 'Topic', prefix: 'TOPIC'},
|
||||
{value: 5, label: 'Group', prefix: 'GROUP'},
|
||||
];
|
||||
|
||||
const ResourceInput = ({ value = [], onChange }) => {
|
||||
const ResourceInput = ({value = [], onChange}) => {
|
||||
// 确保 value 始终是数组
|
||||
const safeValue = Array.isArray(value) ? value : [];
|
||||
|
||||
@@ -96,7 +96,7 @@ const ResourceInput = ({ value = [], onChange }) => {
|
||||
<Space>
|
||||
<Select
|
||||
value={selectedType}
|
||||
style={{ width: 120 }}
|
||||
style={{width: 120}}
|
||||
onChange={handleTypeChange}
|
||||
>
|
||||
{resourceTypes.map(type => (
|
||||
@@ -107,7 +107,7 @@ const ResourceInput = ({ value = [], onChange }) => {
|
||||
</Select>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
style={{ width: 180 }}
|
||||
style={{width: 180}}
|
||||
value={resourceName}
|
||||
onChange={handleNameChange}
|
||||
onPressEnter={handleAddResource}
|
||||
@@ -116,8 +116,8 @@ const ResourceInput = ({ value = [], onChange }) => {
|
||||
/>
|
||||
</Space>
|
||||
) : (
|
||||
<Tag onClick={showInput} style={{ background: '#fff', borderStyle: 'dashed' }}>
|
||||
<PlusOutlined /> 添加资源
|
||||
<Tag onClick={showInput} style={{background: '#fff', borderStyle: 'dashed'}}>
|
||||
<PlusOutlined/> 添加资源
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
|
@@ -15,27 +15,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Input, Select } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {Input, Select} from 'antd';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
// Subject 类型枚举
|
||||
const subjectTypes = [
|
||||
{ value: 'User', label: 'User' },
|
||||
{value: 'User', label: 'User'},
|
||||
];
|
||||
|
||||
const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
const SubjectInput = ({value, onChange, disabled}) => {
|
||||
// 解析传入的 value,将其拆分为 type 和 name
|
||||
const parseValue = (val) => {
|
||||
if (!val || typeof val !== 'string') {
|
||||
return { type: subjectTypes[0].value, name: '' }; // 默认值
|
||||
return {type: subjectTypes[0].value, name: ''}; // 默认值
|
||||
}
|
||||
const parts = val.split(':');
|
||||
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);
|
||||
@@ -76,7 +76,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
return (
|
||||
<Input.Group compact>
|
||||
<Select
|
||||
style={{ width: '30%' }}
|
||||
style={{width: '30%'}}
|
||||
value={currentType}
|
||||
onChange={onTypeChange}
|
||||
disabled={disabled}
|
||||
@@ -88,7 +88,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
style={{ width: '70%' }}
|
||||
style={{width: '70%'}}
|
||||
value={currentName}
|
||||
onChange={onNameChange}
|
||||
placeholder="请输入名称 (例如: yourUsername)"
|
||||
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Table, Spin } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Spin, Table} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
const { t } = useLanguage();
|
||||
const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [connectionData, setConnectionData] = useState(null);
|
||||
const [subscriptionData, setSubscriptionData] = useState(null);
|
||||
@@ -46,15 +46,15 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
}, [visible, group, address]);
|
||||
|
||||
const connectionColumns = [
|
||||
{ title: 'ClientId', dataIndex: 'clientId' },
|
||||
{ title: 'ClientAddr', dataIndex: 'clientAddr' },
|
||||
{ title: 'Language', dataIndex: 'language' },
|
||||
{ title: 'Version', dataIndex: 'versionDesc' },
|
||||
{title: 'ClientId', dataIndex: 'clientId'},
|
||||
{title: 'ClientAddr', dataIndex: 'clientAddr'},
|
||||
{title: 'Language', dataIndex: 'language'},
|
||||
{title: 'Version', dataIndex: 'versionDesc'},
|
||||
];
|
||||
|
||||
const subscriptionColumns = [
|
||||
{ title: 'Topic', dataIndex: 'topic' },
|
||||
{ title: 'SubExpression', dataIndex: 'subString' },
|
||||
{title: 'Topic', dataIndex: 'topic'},
|
||||
{title: 'SubExpression', dataIndex: 'subString'},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -88,7 +88,7 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
rowKey="topic"
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: loading ? <Spin size="small" /> : t.NO_DATA
|
||||
emptyText: loading ? <Spin size="small"/> : t.NO_DATA
|
||||
}}
|
||||
/>
|
||||
<p>ConsumeType: {connectionData.consumeType}</p>
|
||||
|
@@ -15,13 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Descriptions, Form, Input, Select, Switch, message } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi'; // 确保路径正确
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Descriptions, Form, Input, message, Select, Switch} from 'antd';
|
||||
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 [currentBrokerName, setCurrentBrokerName] = useState(brokerName);
|
||||
|
||||
|
@@ -34,7 +34,7 @@ const ConsumerConfigModal = ({visible, isAddConfig, group, onCancel, setIsAddCon
|
||||
setLoading(true);
|
||||
try {
|
||||
// Fetch cluster list for broker names and cluster names
|
||||
if(isAddConfig) {
|
||||
if (isAddConfig) {
|
||||
const clusterResponse = await remoteApi.getClusterList();
|
||||
if (clusterResponse.status === 0 && clusterResponse.data) {
|
||||
const clusterInfo = clusterResponse.data.clusterInfo;
|
||||
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Table, Spin } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Spin, Table} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const ConsumerDetailModal = ({ visible, group, address, onCancel }) => {
|
||||
const { t } = useLanguage();
|
||||
const ConsumerDetailModal = ({visible, group, address, onCancel}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
|
||||
@@ -44,12 +44,12 @@ const ConsumerDetailModal = ({ visible, group, address, onCancel }) => {
|
||||
}, [visible, group, address]);
|
||||
|
||||
const queueColumns = [
|
||||
{ title: 'Broker', dataIndex: 'brokerName' },
|
||||
{ title: 'Queue', dataIndex: 'queueId' },
|
||||
{ title: 'BrokerOffset', dataIndex: 'brokerOffset' },
|
||||
{ title: 'ConsumerOffset', dataIndex: 'consumerOffset' },
|
||||
{ title: 'DiffTotal', dataIndex: 'diffTotal' },
|
||||
{ title: 'LastTimestamp', dataIndex: 'lastTimestamp' },
|
||||
{title: 'Broker', dataIndex: 'brokerName'},
|
||||
{title: 'Queue', dataIndex: 'queueId'},
|
||||
{title: 'BrokerOffset', dataIndex: 'brokerOffset'},
|
||||
{title: 'ConsumerOffset', dataIndex: 'consumerOffset'},
|
||||
{title: 'DiffTotal', dataIndex: 'diffTotal'},
|
||||
{title: 'LastTimestamp', dataIndex: 'lastTimestamp'},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Spin, Checkbox, Button, notification } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Checkbox, Modal, notification, Spin} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
const { t } = useLanguage();
|
||||
const DeleteConsumerModal = ({visible, group, onCancel, onSuccess}) => {
|
||||
const {t} = useLanguage();
|
||||
const [brokerList, setBrokerList] = useState([]);
|
||||
const [selectedBrokers, setSelectedBrokers] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -48,7 +48,7 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
// 处理删除提交
|
||||
const handleDelete = async () => {
|
||||
if (selectedBrokers.length === 0) {
|
||||
notification.warning({ message: t.PLEASE_SELECT_BROKER });
|
||||
notification.warning({message: t.PLEASE_SELECT_BROKER});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
);
|
||||
|
||||
if (response.status === 0) {
|
||||
notification.success({ message: t.DELETE_SUCCESS });
|
||||
notification.success({message: t.DELETE_SUCCESS});
|
||||
onSuccess();
|
||||
onCancel();
|
||||
}
|
||||
@@ -90,9 +90,9 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
]}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<div style={{ marginBottom: 16 }}>{t.SELECT_DELETE_BROKERS}:</div>
|
||||
<div style={{marginBottom: 16}}>{t.SELECT_DELETE_BROKERS}:</div>
|
||||
<Checkbox.Group
|
||||
style={{ width: '100%' }}
|
||||
style={{width: '100%'}}
|
||||
value={selectedBrokers}
|
||||
onChange={values => setSelectedBrokers(values)}
|
||||
>
|
||||
|
@@ -15,10 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, DatePicker, Form, Modal, Select } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {Button, DatePicker, Form, Modal, Select} from "antd";
|
||||
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 [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]);
|
||||
const [selectedTime, setSelectedTime] = useState(null);
|
||||
@@ -49,14 +49,14 @@ const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupLi
|
||||
</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>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={t.SELECT_CONSUMER_GROUP}
|
||||
value={selectedConsumerGroup}
|
||||
onChange={setSelectedConsumerGroup}
|
||||
options={allConsumerGroupList.map(group => ({ value: group, label: group }))}
|
||||
options={allConsumerGroupList.map(group => ({value: group, label: group}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.TIME} required>
|
||||
@@ -65,7 +65,7 @@ const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupLi
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value={selectedTime}
|
||||
onChange={setSelectedTime}
|
||||
style={{ width: '100%' }}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
@@ -19,13 +19,13 @@ import moment from "moment/moment";
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGroupCount, t }) => {
|
||||
const ConsumerViewDialog = ({visible, onClose, topic, consumerData, consumerGroupCount, t}) => {
|
||||
const columns = [
|
||||
{ title: t.BROKER, dataIndex: 'brokerName', key: 'brokerName', align: 'center' },
|
||||
{ title: t.QUEUE, dataIndex: 'queueId', key: 'queueId', align: 'center' },
|
||||
{ title: t.CONSUMER_CLIENT, dataIndex: 'clientInfo', key: 'clientInfo', align: 'center' },
|
||||
{ title: t.BROKER_OFFSET, dataIndex: 'brokerOffset', key: 'brokerOffset', align: 'center' },
|
||||
{ title: t.CONSUMER_OFFSET, dataIndex: 'consumerOffset', key: 'consumerOffset', align: 'center' },
|
||||
{title: t.BROKER, dataIndex: 'brokerName', key: 'brokerName', align: 'center'},
|
||||
{title: t.QUEUE, dataIndex: 'queueId', key: 'queueId', align: 'center'},
|
||||
{title: t.CONSUMER_CLIENT, dataIndex: 'clientInfo', key: 'clientInfo', align: 'center'},
|
||||
{title: t.BROKER_OFFSET, dataIndex: 'brokerOffset', key: 'brokerOffset', align: 'center'},
|
||||
{title: t.CONSUMER_OFFSET, dataIndex: 'consumerOffset', key: 'consumerOffset', align: 'center'},
|
||||
{
|
||||
title: t.DIFF_TOTAL,
|
||||
dataIndex: 'diffTotal',
|
||||
@@ -58,15 +58,19 @@ const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGro
|
||||
<div>{t.NO_DATA} {t.SUBSCRIPTION_GROUP}</div>
|
||||
) : (
|
||||
consumerData && Object.entries(consumerData).map(([consumerGroup, consumeDetail]) => (
|
||||
<div key={consumerGroup} style={{ marginBottom: '24px' }}>
|
||||
<div key={consumerGroup} style={{marginBottom: '24px'}}>
|
||||
<Table
|
||||
bordered
|
||||
pagination={false}
|
||||
showHeader={false}
|
||||
dataSource={[{ consumerGroup, diffTotal: consumeDetail.diffTotal, lastTimestamp: consumeDetail.lastTimestamp }]}
|
||||
dataSource={[{
|
||||
consumerGroup,
|
||||
diffTotal: consumeDetail.diffTotal,
|
||||
lastTimestamp: consumeDetail.lastTimestamp
|
||||
}]}
|
||||
columns={[
|
||||
{ title: t.SUBSCRIPTION_GROUP, dataIndex: 'consumerGroup', key: 'consumerGroup' },
|
||||
{ title: t.DELAY, dataIndex: 'diffTotal', key: 'diffTotal' },
|
||||
{title: t.SUBSCRIPTION_GROUP, dataIndex: 'consumerGroup', key: 'consumerGroup'},
|
||||
{title: t.DELAY, dataIndex: 'diffTotal', key: 'diffTotal'},
|
||||
{
|
||||
title: t.LAST_CONSUME_TIME,
|
||||
dataIndex: 'lastTimestamp',
|
||||
@@ -76,7 +80,7 @@ const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGro
|
||||
]}
|
||||
rowKey="consumerGroup"
|
||||
size="small"
|
||||
style={{ marginBottom: '12px' }}
|
||||
style={{marginBottom: '12px'}}
|
||||
/>
|
||||
<Table
|
||||
bordered
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
|
||||
const ResetOffsetResultDialog = ({visible, onClose, result, t}) => {
|
||||
return (
|
||||
<Modal
|
||||
title="ResetResult"
|
||||
@@ -31,12 +31,12 @@ const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
|
||||
]}
|
||||
>
|
||||
{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
|
||||
dataSource={[{ groupName, status: groupData.status }]}
|
||||
dataSource={[{groupName, status: groupData.status}]}
|
||||
columns={[
|
||||
{ title: 'GroupName', dataIndex: 'groupName', key: 'groupName' },
|
||||
{ title: 'State', dataIndex: 'status', key: 'status' },
|
||||
{title: 'GroupName', dataIndex: 'groupName', key: 'groupName'},
|
||||
{title: 'State', dataIndex: 'status', key: 'status'},
|
||||
]}
|
||||
pagination={false}
|
||||
rowKey="groupName"
|
||||
@@ -47,8 +47,8 @@ const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
|
||||
<div>You Should Check It Yourself</div>
|
||||
) : (
|
||||
<Table
|
||||
dataSource={groupData.rollbackStatsList.map((item, index) => ({ key: index, item }))}
|
||||
columns={[{ dataIndex: 'item', key: 'item' }]}
|
||||
dataSource={groupData.rollbackStatsList.map((item, index) => ({key: index, item}))}
|
||||
columns={[{dataIndex: 'item', key: 'item'}]}
|
||||
pagination={false}
|
||||
rowKey="key"
|
||||
size="small"
|
||||
|
@@ -15,10 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Modal, Table } from "antd";
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
const RouterViewDialog = ({visible, onClose, topic, routeData, t}) => {
|
||||
const brokerColumns = [
|
||||
{
|
||||
title: 'Broker',
|
||||
@@ -30,10 +30,14 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
key: 'brokerAddrs',
|
||||
render: (_, record) => (
|
||||
<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={[
|
||||
{ title: 'Index', dataIndex: 'idx', key: 'idx' },
|
||||
{ title: 'Address', dataIndex: 'address', key: 'address' },
|
||||
{title: 'Index', dataIndex: 'idx', key: 'idx'},
|
||||
{title: 'Address', dataIndex: 'address', key: 'address'},
|
||||
]}
|
||||
pagination={false}
|
||||
bordered
|
||||
@@ -82,7 +86,7 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
<div>
|
||||
<h3>Broker Datas:</h3>
|
||||
{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
|
||||
dataSource={[item]}
|
||||
columns={brokerColumns}
|
||||
@@ -93,7 +97,7 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<div style={{marginTop: '20px'}}>
|
||||
<h3>{t.QUEUE_DATAS}:</h3>
|
||||
<Table
|
||||
dataSource={routeData?.queueDatas || []}
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import {Button, Form, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const SendResultDialog = ({ visible, onClose, result, t }) => {
|
||||
const SendResultDialog = ({visible, onClose, result, t}) => {
|
||||
return (
|
||||
<Modal
|
||||
title="SendResult"
|
||||
@@ -43,11 +43,11 @@ const SendResultDialog = ({ visible, onClose, result, t }) => {
|
||||
: []
|
||||
}
|
||||
columns={[
|
||||
{ dataIndex: 'label', key: 'label' },
|
||||
{dataIndex: 'label', key: 'label'},
|
||||
{
|
||||
dataIndex: '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}
|
||||
@@ -61,5 +61,4 @@ const SendResultDialog = ({ visible, onClose, result, t }) => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default SendResultDialog;
|
||||
|
@@ -76,24 +76,24 @@ const SendTopicMessageDialog = ({
|
||||
</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">
|
||||
<Input disabled />
|
||||
<Input disabled/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.TAG} name="tag">
|
||||
<Input />
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.KEY} name="key">
|
||||
<Input />
|
||||
<Input/>
|
||||
</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
|
||||
style={{ maxHeight: '200px', minHeight: '200px', resize: 'none' }}
|
||||
style={{maxHeight: '200px', minHeight: '200px', resize: 'none'}}
|
||||
rows={8}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.ENABLE_MESSAGE_TRACE} name="traceEnabled" valuePropName="checked">
|
||||
<Checkbox />
|
||||
<Checkbox/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
@@ -15,10 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Form, message, Modal, Select } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {Button, Form, message, Modal, Select} from "antd";
|
||||
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 [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]);
|
||||
|
||||
@@ -52,14 +59,14 @@ const SkipMessageAccumulateDialog = ({ visible, onClose, topic, allConsumerGroup
|
||||
</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>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={t.SELECT_CONSUMER_GROUP}
|
||||
value={selectedConsumerGroup}
|
||||
onChange={setSelectedConsumerGroup}
|
||||
options={allConsumerGroupList.map(group => ({ value: group, label: group }))}
|
||||
options={allConsumerGroupList.map(group => ({value: group, label: group}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
@@ -19,11 +19,11 @@ import moment from "moment/moment";
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const StatsViewDialog = ({ visible, onClose, topic, statsData, t }) => {
|
||||
const StatsViewDialog = ({visible, onClose, topic, statsData, t}) => {
|
||||
const columns = [
|
||||
{ title: t.QUEUE, dataIndex: 'queue', key: 'queue', align: 'center' },
|
||||
{ title: t.MIN_OFFSET, dataIndex: 'minOffset', key: 'minOffset', align: 'center' },
|
||||
{ title: t.MAX_OFFSET, dataIndex: 'maxOffset', key: 'maxOffset', align: 'center' },
|
||||
{title: t.QUEUE, dataIndex: 'queue', key: 'queue', align: 'center'},
|
||||
{title: t.MIN_OFFSET, dataIndex: 'minOffset', key: 'minOffset', align: 'center'},
|
||||
{title: t.MAX_OFFSET, dataIndex: 'maxOffset', key: 'maxOffset', align: 'center'},
|
||||
{
|
||||
title: t.LAST_UPDATE_TIME_STAMP,
|
||||
dataIndex: 'lastUpdateTimestamp',
|
||||
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
// TopicModifyDialog.js
|
||||
import { Button, Modal } from "antd";
|
||||
import {Button, Modal} from "antd";
|
||||
import React from "react";
|
||||
import TopicSingleModifyForm from './TopicSingleModifyForm';
|
||||
|
||||
@@ -43,7 +43,7 @@ const TopicModifyDialog = ({
|
||||
{t.CLOSE}
|
||||
</Button>,
|
||||
]}
|
||||
Style={{ maxHeight: '70vh', overflowY: 'auto' }}
|
||||
Style={{maxHeight: '70vh', overflowY: 'auto'}}
|
||||
>
|
||||
{initialData.map((data, index) => (
|
||||
<TopicSingleModifyForm
|
||||
|
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
|
||||
// TopicSingleModifyForm.js
|
||||
import React, { useEffect } from "react";
|
||||
import {Button, Form, Input, Select, Divider, Row, Col} from "antd";
|
||||
import React, {useEffect} from "react";
|
||||
import {Button, Col, Divider, Form, Input, Row, Select} from "antd";
|
||||
|
||||
const TopicSingleModifyForm = ({
|
||||
initialData,
|
||||
@@ -42,9 +42,9 @@ const TopicSingleModifyForm = ({
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
const updatedValues = { ...values };
|
||||
const updatedValues = {...values};
|
||||
// 提交时,如果 clusterNameList 或 brokerNameList 为空,则填充所有可用的名称
|
||||
if(!bIsUpdate){
|
||||
if (!bIsUpdate) {
|
||||
if (!updatedValues.clusterNameList || updatedValues.clusterNameList.length === 0) {
|
||||
updatedValues.clusterNameList = allClusterNameList;
|
||||
}
|
||||
@@ -60,84 +60,85 @@ const TopicSingleModifyForm = ({
|
||||
};
|
||||
|
||||
const messageTypeOptions = [
|
||||
{ value: 'TRANSACTION', label: 'TRANSACTION' },
|
||||
{ value: 'FIFO', label: 'FIFO' },
|
||||
{ value: 'DELAY', label: 'DELAY' },
|
||||
{ value: 'NORMAL', label: 'NORMAL' },
|
||||
{value: 'TRANSACTION', label: 'TRANSACTION'},
|
||||
{value: 'FIFO', label: 'FIFO'},
|
||||
{value: 'DELAY', label: 'DELAY'},
|
||||
{value: 'NORMAL', label: 'NORMAL'},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 24 }}>
|
||||
{bIsUpdate && <Divider orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>}
|
||||
<Row justify="center"> {/* 使用 Row 居中内容 */}
|
||||
<Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */}
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
<div style={{paddingBottom: 24}}>
|
||||
{bIsUpdate && <Divider
|
||||
orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>}
|
||||
<Row justify="center"> {/* 使用 Row 居中内容 */}
|
||||
<Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */}
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
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">
|
||||
<Select
|
||||
mode="multiple"
|
||||
disabled={bIsUpdate}
|
||||
placeholder={t.SELECT_CLUSTER_NAME}
|
||||
options={allClusterNameList.map(name => ({ value: name, label: name }))}
|
||||
/>
|
||||
<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.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}` }]}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -15,20 +15,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createContext, useState, useContext } from 'react';
|
||||
import { translations } from '../i18n';
|
||||
import React, {createContext, useContext, useState} from 'react';
|
||||
import {translations} from '../i18n';
|
||||
|
||||
const LanguageContext = createContext({
|
||||
lang: 'en',
|
||||
setLang: () => {},
|
||||
setLang: () => {
|
||||
},
|
||||
t: translations['en'], // 当前语言的文本资源
|
||||
});
|
||||
|
||||
export const LanguageProvider = ({ children }) => {
|
||||
export const LanguageProvider = ({children}) => {
|
||||
const [lang, setLang] = useState('en');
|
||||
const t = translations[lang] || translations['en'];
|
||||
return (
|
||||
<LanguageContext.Provider value={{ lang, setLang, t }}>
|
||||
<LanguageContext.Provider value={{lang, setLang, t}}>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
|
@@ -47,10 +47,10 @@ export const translations = {
|
||||
"FETCH_TOPIC_FAILED": "获取主题列表失败",
|
||||
"CONFIRM_DELETE": "确认删除",
|
||||
"CANCEL": "取消",
|
||||
"SELECT_DELETE_BROKERS":"请选择在哪个Broker删除消费者组",
|
||||
"DELETE_CONSUMER_GROUP":"删除消费者组",
|
||||
"SELECT_DELETE_BROKERS": "请选择在哪个Broker删除消费者组",
|
||||
"DELETE_CONSUMER_GROUP": "删除消费者组",
|
||||
"ENGLISH": "英文",
|
||||
"ADD_CONSUMER":"添加消费者",
|
||||
"ADD_CONSUMER": "添加消费者",
|
||||
"CHINESE": "简体中文",
|
||||
"CANNOT_BE_EMPTY": "不能为空",
|
||||
"TITLE": "RocketMQ仪表板",
|
||||
@@ -70,16 +70,16 @@ export const translations = {
|
||||
"CLUSTER_DETAIL": "集群详情",
|
||||
"COMMIT": "提交",
|
||||
"TOPIC": "主题",
|
||||
"SUBSCRIPTION_GROUP":"订阅组",
|
||||
"PRODUCER_GROUP":"生产组",
|
||||
"CONSUMER":"消费者",
|
||||
"PRODUCER":"生产者",
|
||||
"MESSAGE":"消息",
|
||||
"MESSAGE_DETAIL":"消息详情",
|
||||
"RESEND_MESSAGE":"重新发送",
|
||||
"VIEW_EXCEPTION":"查看异常",
|
||||
"DLQ_MESSAGE":"死信消息",
|
||||
"MESSAGETRACE":"消息轨迹",
|
||||
"SUBSCRIPTION_GROUP": "订阅组",
|
||||
"PRODUCER_GROUP": "生产组",
|
||||
"CONSUMER": "消费者",
|
||||
"PRODUCER": "生产者",
|
||||
"MESSAGE": "消息",
|
||||
"MESSAGE_DETAIL": "消息详情",
|
||||
"RESEND_MESSAGE": "重新发送",
|
||||
"VIEW_EXCEPTION": "查看异常",
|
||||
"DLQ_MESSAGE": "死信消息",
|
||||
"MESSAGETRACE": "消息轨迹",
|
||||
"OPERATION": "操作",
|
||||
"ADD": "新增",
|
||||
"UPDATE": "更新",
|
||||
@@ -89,7 +89,7 @@ export const translations = {
|
||||
"CONFIG": "配置",
|
||||
"SEND_MSG": "发送消息",
|
||||
"RESET_CUS_OFFSET": "重置消费位点",
|
||||
"SKIP_MESSAGE_ACCUMULATE":"跳过堆积",
|
||||
"SKIP_MESSAGE_ACCUMULATE": "跳过堆积",
|
||||
"DELETE": "删除",
|
||||
"CHANGE_LANG": "更换语言",
|
||||
"CHANGE_VERSION": "更换版本",
|
||||
@@ -100,73 +100,72 @@ export const translations = {
|
||||
"TRANSACTION": "事务",
|
||||
"UNSPECIFIED": "未指定",
|
||||
"DLQ": "死信",
|
||||
"QUANTITY":"数量",
|
||||
"TYPE":"类型",
|
||||
"MODE":"模式",
|
||||
"DELAY":"延迟",
|
||||
"DASHBOARD":"驾驶舱",
|
||||
"CONSUME_DETAIL":"消费详情",
|
||||
"CLIENT":"终端",
|
||||
"LAST_CONSUME_TIME":"最后消费时间",
|
||||
"TIME":"时间点",
|
||||
"RESET":"重置",
|
||||
"DATE":"日期",
|
||||
"NO_DATA":"暂无数据",
|
||||
"SEARCH":"搜索",
|
||||
"BEGIN":"开始",
|
||||
"END":"结束",
|
||||
"TOPIC_CHANGE":"修改主题",
|
||||
"SEND":"发送",
|
||||
"SUBSCRIPTION_CHANGE":"修改订阅",
|
||||
"QUEUE":"队列",
|
||||
"MIN_OFFSET":"最小位点",
|
||||
"MAX_OFFSET":"最大位点",
|
||||
"LAST_UPDATE_TIME_STAMP":"上次更新时间",
|
||||
"QUEUE_DATAS":"队列信息",
|
||||
"READ_QUEUE_NUMS":"读队列数量",
|
||||
"WRITE_QUEUE_NUMS":"写队列数量",
|
||||
"PERM":"perm",
|
||||
"TAG":"标签",
|
||||
"KEY":"值",
|
||||
"MESSAGE_BODY":"消息主体",
|
||||
"TOPIC_NAME":"主题名",
|
||||
"ORDER":"顺序",
|
||||
"CONSUMER_CLIENT":"消费者终端",
|
||||
"BROKER_OFFSET":"代理者位点",
|
||||
"CONSUMER_OFFSET":"消费者位点",
|
||||
"DIFF_TOTAL":"差值",
|
||||
"LAST_TIME_STAMP":"上次时间",
|
||||
"RESET_OFFSET":"重置位点",
|
||||
"CLUSTER_NAME":"集群名",
|
||||
"OPS":"运维",
|
||||
"PROXY":"代理",
|
||||
"AUTO_REFRESH":"自动刷新",
|
||||
"REFRESH":"刷新",
|
||||
"LOGOUT":"退出",
|
||||
"LOGIN":"登录",
|
||||
"USER_NAME":"用户名",
|
||||
"PASSWORD":"密码",
|
||||
"SYSTEM":"系统",
|
||||
"WELCOME":"您好,欢迎使用RocketMQ仪表盘",
|
||||
"ENABLE_MESSAGE_TRACE":"开启消息轨迹",
|
||||
"MESSAGE_TRACE_DETAIL":"消息轨迹详情",
|
||||
"TRACE_TOPIC":"消息轨迹主题",
|
||||
"SELECT_TRACE_TOPIC":"选择消息轨迹主题",
|
||||
"QUANTITY": "数量",
|
||||
"TYPE": "类型",
|
||||
"MODE": "模式",
|
||||
"DELAY": "延迟",
|
||||
"DASHBOARD": "驾驶舱",
|
||||
"CONSUME_DETAIL": "消费详情",
|
||||
"CLIENT": "终端",
|
||||
"LAST_CONSUME_TIME": "最后消费时间",
|
||||
"TIME": "时间点",
|
||||
"RESET": "重置",
|
||||
"DATE": "日期",
|
||||
"NO_DATA": "暂无数据",
|
||||
"SEARCH": "搜索",
|
||||
"BEGIN": "开始",
|
||||
"END": "结束",
|
||||
"TOPIC_CHANGE": "修改主题",
|
||||
"SEND": "发送",
|
||||
"SUBSCRIPTION_CHANGE": "修改订阅",
|
||||
"QUEUE": "队列",
|
||||
"MIN_OFFSET": "最小位点",
|
||||
"MAX_OFFSET": "最大位点",
|
||||
"LAST_UPDATE_TIME_STAMP": "上次更新时间",
|
||||
"QUEUE_DATAS": "队列信息",
|
||||
"READ_QUEUE_NUMS": "读队列数量",
|
||||
"WRITE_QUEUE_NUMS": "写队列数量",
|
||||
"PERM": "perm",
|
||||
"TAG": "标签",
|
||||
"KEY": "值",
|
||||
"MESSAGE_BODY": "消息主体",
|
||||
"TOPIC_NAME": "主题名",
|
||||
"ORDER": "顺序",
|
||||
"CONSUMER_CLIENT": "消费者终端",
|
||||
"BROKER_OFFSET": "代理者位点",
|
||||
"CONSUMER_OFFSET": "消费者位点",
|
||||
"DIFF_TOTAL": "差值",
|
||||
"LAST_TIME_STAMP": "上次时间",
|
||||
"RESET_OFFSET": "重置位点",
|
||||
"CLUSTER_NAME": "集群名",
|
||||
"OPS": "运维",
|
||||
"PROXY": "代理",
|
||||
"AUTO_REFRESH": "自动刷新",
|
||||
"REFRESH": "刷新",
|
||||
"LOGOUT": "退出",
|
||||
"LOGIN": "登录",
|
||||
"USER_NAME": "用户名",
|
||||
"PASSWORD": "密码",
|
||||
"SYSTEM": "系统",
|
||||
"WELCOME": "您好,欢迎使用RocketMQ仪表盘",
|
||||
"ENABLE_MESSAGE_TRACE": "开启消息轨迹",
|
||||
"MESSAGE_TRACE_DETAIL": "消息轨迹详情",
|
||||
"TRACE_TOPIC": "消息轨迹主题",
|
||||
"SELECT_TRACE_TOPIC": "选择消息轨迹主题",
|
||||
"EXPORT": "导出",
|
||||
"NO_MATCH_RESULT": "没有查到符合条件的结果",
|
||||
"BATCH_RESEND": "批量重发",
|
||||
"BATCH_EXPORT": "批量导出",
|
||||
"WHITE_LIST":"白名单",
|
||||
"ACCOUNT_INFO":"账户信息",
|
||||
"IS_ADMIN":"是否管理员",
|
||||
"DEFAULT_TOPIC_PERM":"topic默认权限",
|
||||
"DEFAULT_GROUP_PERM":"消费组默认权限",
|
||||
"TOPIC_PERM":"topic权限",
|
||||
"GROUP_PERM":"消费组权限",
|
||||
"SYNCHRONIZE":"同步",
|
||||
"SHOW":"显示",
|
||||
"HIDE":"隐藏",
|
||||
"MESSAGE_TYPE":"消息类型",
|
||||
"ACCOUNT_INFO": "账户信息",
|
||||
"IS_ADMIN": "是否管理员",
|
||||
"DEFAULT_TOPIC_PERM": "topic默认权限",
|
||||
"DEFAULT_GROUP_PERM": "消费组默认权限",
|
||||
"TOPIC_PERM": "topic权限",
|
||||
"GROUP_PERM": "消费组权限",
|
||||
"SYNCHRONIZE": "同步",
|
||||
"SHOW": "显示",
|
||||
"HIDE": "隐藏",
|
||||
"MESSAGE_TYPE": "消息类型",
|
||||
"MESSAGE_TYPE_UNSPECIFIED": "未指定,为普通消息",
|
||||
"MESSAGE_TYPE_NORMAL": "普通消息",
|
||||
"MESSAGE_TYPE_FIFO": "顺序消息",
|
||||
@@ -279,6 +278,12 @@ export const translations = {
|
||||
"ENTER_IP_HINT": "请输入 IP 地址,按回车键添加,支持 IPv4、IPv6 和 CIDR",
|
||||
"PLEASE_ENTER_DECISION": "请输入决策!",
|
||||
"MENU": "菜单",
|
||||
"SELECT_PROXY": "选择代理",
|
||||
"ENABLE_PROXY": "启用代理",
|
||||
"PROXY_DISABLED": "代理禁用",
|
||||
"PROXY_ENABLED": "代理启用",
|
||||
"BROKER_OVERVIEW": "Broker概览",
|
||||
"TOTAL_MSG_RECEIVED_TODAY": "今天接收的总消息数",
|
||||
},
|
||||
en: {
|
||||
"DEFAULT": "Default",
|
||||
@@ -294,7 +299,7 @@ export const translations = {
|
||||
"SELECT_TOPIC_PLACEHOLDER": "Please select topic",
|
||||
"MESSAGE_ID_TOPIC_HINT": "Message ID Topic",
|
||||
"TOPIC_ADD": "Add Topic",
|
||||
"SKIP_MESSAGE_ACCUMULATE":"Skip Message Accumulate",
|
||||
"SKIP_MESSAGE_ACCUMULATE": "Skip Message Accumulate",
|
||||
"OPERATION_FAILED": "Operation Failed",
|
||||
"FORM_VALIDATION_FAILED": "Form Validation Failed",
|
||||
"ADD_CONSUMER": "Add Consumer",
|
||||
@@ -325,7 +330,7 @@ export const translations = {
|
||||
"ADDRESS": "Address",
|
||||
"VERSION": "Version",
|
||||
"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_CUS_COUNT": "Yesterday Consume Count",
|
||||
"TODAY_PRO_COUNT": "Today Produce Count",
|
||||
@@ -335,16 +340,16 @@ export const translations = {
|
||||
"CLUSTER": "Cluster",
|
||||
"CLUSTER_DETAIL": "Cluster Detail",
|
||||
"TOPIC": "Topic",
|
||||
"SUBSCRIPTION_GROUP":"SubscriptionGroup",
|
||||
"PRODUCER_GROUP":"ProducerGroup",
|
||||
"CONSUMER":"Consumer",
|
||||
"PRODUCER":"Producer",
|
||||
"MESSAGE":"Message",
|
||||
"MESSAGE_DETAIL":"Message Detail",
|
||||
"RESEND_MESSAGE":"Resend Message",
|
||||
"VIEW_EXCEPTION":"View Exception",
|
||||
"MESSAGETRACE":"MessageTrace",
|
||||
"DLQ_MESSAGE":"DLQMessage",
|
||||
"SUBSCRIPTION_GROUP": "SubscriptionGroup",
|
||||
"PRODUCER_GROUP": "ProducerGroup",
|
||||
"CONSUMER": "Consumer",
|
||||
"PRODUCER": "Producer",
|
||||
"MESSAGE": "Message",
|
||||
"MESSAGE_DETAIL": "Message Detail",
|
||||
"RESEND_MESSAGE": "Resend Message",
|
||||
"VIEW_EXCEPTION": "View Exception",
|
||||
"MESSAGETRACE": "MessageTrace",
|
||||
"DLQ_MESSAGE": "DLQMessage",
|
||||
"COMMIT": "Commit",
|
||||
"OPERATION": "Operation",
|
||||
"ADD": "Add",
|
||||
@@ -365,73 +370,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":"LastConsumeTime",
|
||||
"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":"minOffset",
|
||||
"MAX_OFFSET":"maxOffset",
|
||||
"LAST_UPDATE_TIME_STAMP":"lastUpdateTimeStamp",
|
||||
"QUEUE_DATAS":"queueDatas",
|
||||
"READ_QUEUE_NUMS":"readQueueNums",
|
||||
"WRITE_QUEUE_NUMS":"writeQueueNums",
|
||||
"PERM":"perm",
|
||||
"TAG":"Tag",
|
||||
"KEY":"Key",
|
||||
"MESSAGE_BODY":"Message Body",
|
||||
"TOPIC_NAME":"topicName",
|
||||
"ORDER":"order",
|
||||
"CONSUMER_CLIENT":"consumerClient",
|
||||
"BROKER_OFFSET":"brokerOffset",
|
||||
"CONSUMER_OFFSET":"consumerOffset",
|
||||
"DIFF_TOTAL":"diffTotal",
|
||||
"LAST_TIME_STAMP":"lastTimeStamp",
|
||||
"RESET_OFFSET":"resetOffset",
|
||||
"CLUSTER_NAME":"clusterName",
|
||||
"OPS":"OPS",
|
||||
"PROXY":"Proxy",
|
||||
"AUTO_REFRESH":"AUTO_REFRESH",
|
||||
"REFRESH":"REFRESH",
|
||||
"LOGOUT":"Logout",
|
||||
"LOGIN":"Login",
|
||||
"USER_NAME":"Username",
|
||||
"PASSWORD":"Password",
|
||||
"SYSTEM":"SYSTEM",
|
||||
"WELCOME":"Hi, welcome using RocketMQ Dashboard",
|
||||
"ENABLE_MESSAGE_TRACE":"Enable Message Trace",
|
||||
"MESSAGE_TRACE_DETAIL":"Message Trace Detail",
|
||||
"TRACE_TOPIC":"TraceTopic",
|
||||
"SELECT_TRACE_TOPIC":"selectTraceTopic",
|
||||
"QUANTITY": "Quantity",
|
||||
"TYPE": "Type",
|
||||
"MODE": "Mode",
|
||||
"DELAY": "Delay",
|
||||
"DASHBOARD": "Dashboard",
|
||||
"CONSUME_DETAIL": "CONSUME DETAIL",
|
||||
"CLIENT": "CLIENT",
|
||||
"LAST_CONSUME_TIME": "LastConsumeTime",
|
||||
"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": "minOffset",
|
||||
"MAX_OFFSET": "maxOffset",
|
||||
"LAST_UPDATE_TIME_STAMP": "lastUpdateTimeStamp",
|
||||
"QUEUE_DATAS": "queueDatas",
|
||||
"READ_QUEUE_NUMS": "readQueueNums",
|
||||
"WRITE_QUEUE_NUMS": "writeQueueNums",
|
||||
"PERM": "perm",
|
||||
"TAG": "Tag",
|
||||
"KEY": "Key",
|
||||
"MESSAGE_BODY": "Message Body",
|
||||
"TOPIC_NAME": "topicName",
|
||||
"ORDER": "order",
|
||||
"CONSUMER_CLIENT": "consumerClient",
|
||||
"BROKER_OFFSET": "brokerOffset",
|
||||
"CONSUMER_OFFSET": "consumerOffset",
|
||||
"DIFF_TOTAL": "diffTotal",
|
||||
"LAST_TIME_STAMP": "lastTimeStamp",
|
||||
"RESET_OFFSET": "resetOffset",
|
||||
"CLUSTER_NAME": "clusterName",
|
||||
"OPS": "OPS",
|
||||
"PROXY": "Proxy",
|
||||
"AUTO_REFRESH": "AUTO_REFRESH",
|
||||
"REFRESH": "REFRESH",
|
||||
"LOGOUT": "Logout",
|
||||
"LOGIN": "Login",
|
||||
"USER_NAME": "Username",
|
||||
"PASSWORD": "Password",
|
||||
"SYSTEM": "SYSTEM",
|
||||
"WELCOME": "Hi, welcome using RocketMQ Dashboard",
|
||||
"ENABLE_MESSAGE_TRACE": "Enable Message Trace",
|
||||
"MESSAGE_TRACE_DETAIL": "Message Trace Detail",
|
||||
"TRACE_TOPIC": "TraceTopic",
|
||||
"SELECT_TRACE_TOPIC": "selectTraceTopic",
|
||||
"EXPORT": "export",
|
||||
"NO_MATCH_RESULT": "no match result",
|
||||
"BATCH_RESEND": "batchReSend",
|
||||
"BATCH_EXPORT": "batchExport",
|
||||
"WHITE_LIST":"White List",
|
||||
"ACCOUNT_INFO":"Account Info",
|
||||
"IS_ADMIN":"Is Admin",
|
||||
"DEFAULT_TOPIC_PERM":"Default Topic Permission",
|
||||
"DEFAULT_GROUP_PERM":"Default Group Permission",
|
||||
"TOPIC_PERM":"Topic Permission",
|
||||
"GROUP_PERM":"Group Permission",
|
||||
"SYNCHRONIZE":"Synchronize Data",
|
||||
"SHOW":"Show",
|
||||
"HIDE":"Hide",
|
||||
"MESSAGE_TYPE":"messageType",
|
||||
"ACCOUNT_INFO": "Account Info",
|
||||
"IS_ADMIN": "Is Admin",
|
||||
"DEFAULT_TOPIC_PERM": "Default Topic Permission",
|
||||
"DEFAULT_GROUP_PERM": "Default Group Permission",
|
||||
"TOPIC_PERM": "Topic Permission",
|
||||
"GROUP_PERM": "Group Permission",
|
||||
"SYNCHRONIZE": "Synchronize Data",
|
||||
"SHOW": "Show",
|
||||
"HIDE": "Hide",
|
||||
"MESSAGE_TYPE": "messageType",
|
||||
"MESSAGE_TYPE_UNSPECIFIED": "UNSPECIFIED, is NORMAL",
|
||||
"MESSAGE_TYPE_NORMAL": "NORMAL",
|
||||
"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.",
|
||||
"PLEASE_ENTER_DECISION": "Please enter decision!",
|
||||
"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",
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@@ -16,15 +16,15 @@
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { App as AntdApp } from 'antd';
|
||||
import {App as AntdApp} from 'antd';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import {LanguageProvider} from "./i18n/LanguageContext";
|
||||
import {Provider} from "react-redux";
|
||||
@@ -27,17 +27,15 @@ import store from './store';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
|
||||
<LanguageProvider>
|
||||
<React.StrictMode>
|
||||
<AntdApp>
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
<App/>
|
||||
</Provider>
|
||||
</AntdApp>
|
||||
</React.StrictMode>
|
||||
</LanguageProvider>
|
||||
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
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 {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import ClientInfoModal from "../../components/consumer/ClientInfoModal";
|
||||
@@ -44,8 +44,29 @@ const ConsumerGroupList = () => {
|
||||
const [isAddConfig, setIsAddConfig] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
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({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
@@ -60,12 +81,17 @@ const ConsumerGroupList = () => {
|
||||
const loadConsumerGroups = useCallback(async (currentPage) => {
|
||||
setLoading(true);
|
||||
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) {
|
||||
setAllConsumerGroupList(response.data);
|
||||
if(currentPage!=null){
|
||||
if (currentPage != null) {
|
||||
filterList(currentPage, response.data);
|
||||
}else{
|
||||
} else {
|
||||
filterList(1, response.data);
|
||||
}
|
||||
} else {
|
||||
@@ -87,7 +113,6 @@ const ConsumerGroupList = () => {
|
||||
};
|
||||
|
||||
const filterList = useCallback((currentPage, data) => {
|
||||
// 排序处理
|
||||
let sortedData = [...data];
|
||||
if (sortConfig.sortKey) {
|
||||
sortedData.sort((a, b) => {
|
||||
@@ -153,6 +178,48 @@ const ConsumerGroupList = () => {
|
||||
filterList(paginationConf.current, sortedList);
|
||||
}, [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(() => {
|
||||
loadConsumerGroups();
|
||||
}, [loadConsumerGroups]);
|
||||
@@ -380,7 +447,7 @@ const ConsumerGroupList = () => {
|
||||
filterList(pagination.current, allConsumerGroupList);
|
||||
};
|
||||
|
||||
const closeConfigModal = () =>{
|
||||
const closeConfigModal = () => {
|
||||
setShowConfig(false);
|
||||
setIsAddConfig(false);
|
||||
}
|
||||
@@ -389,16 +456,18 @@ const ConsumerGroupList = () => {
|
||||
<>
|
||||
{msgContextHolder}
|
||||
{notificationContextHolder}
|
||||
<div style={{padding: '20px'}}>
|
||||
<div style={{ padding: '20px' }}>
|
||||
<Spin spinning={loading} tip={t.LOADING}>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '15px'}}>
|
||||
<div style={{display: 'flex', alignItems: 'center'}}>
|
||||
<label style={{marginRight: '8px'}}>{t.SUBSCRIPTION_GROUP}:</label>
|
||||
<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
{/* 左侧:筛选和操作按钮 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '15px', flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<label style={{ marginRight: '8px', whiteSpace: 'nowrap' }}>{t.SUBSCRIPTION_GROUP}:</label>
|
||||
<Input
|
||||
style={{width: '200px'}}
|
||||
style={{ width: '200px' }}
|
||||
value={filterStr}
|
||||
onChange={(e) => handleFilterInputChange(e.target.value)}
|
||||
placeholder="输入订阅组名称"
|
||||
/>
|
||||
</div>
|
||||
<Checkbox checked={filterNormal}
|
||||
@@ -423,12 +492,35 @@ const ConsumerGroupList = () => {
|
||||
<Button type="primary" onClick={handleRefreshConsumerData}>
|
||||
{t.REFRESH}
|
||||
</Button>
|
||||
{/*<Switch*/}
|
||||
{/* checked={intervalProcessSwitch}*/}
|
||||
{/* onChange={(checked) => setIntervalProcessSwitch(checked)}*/}
|
||||
{/* checkedChildren={t.AUTO_REFRESH}*/}
|
||||
{/* unCheckedChildren={t.AUTO_REFRESH}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
|
||||
{/* 右侧:代理选项 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
|
||||
<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>
|
||||
|
||||
@@ -443,6 +535,7 @@ const ConsumerGroupList = () => {
|
||||
/>
|
||||
</Spin>
|
||||
|
||||
{/* 模态框组件保持不变 */}
|
||||
<ClientInfoModal
|
||||
visible={showClientInfo}
|
||||
group={selectedGroup}
|
||||
|
@@ -250,17 +250,18 @@ const DashboardPage = () => {
|
||||
const brokerAddrTable = resp.data.clusterInfo.brokerAddrTable; // Corrected to brokerAddrTable
|
||||
const brokerDetail = resp.data.brokerServer;
|
||||
const clusterMap = tools.generateBrokerMap(brokerDetail, clusterAddrTable, brokerAddrTable);
|
||||
|
||||
console.log(brokerAddrTable)
|
||||
let brokerArray = [];
|
||||
Object.values(clusterMap).forEach(brokersInCluster => {
|
||||
brokerArray = brokerArray.concat(brokersInCluster);
|
||||
});
|
||||
|
||||
// Update broker table data
|
||||
setBrokerTableData(brokerArray.map(broker => ({
|
||||
const newData = brokerArray.map(broker => ({
|
||||
...broker,
|
||||
key: broker.brokerName // Ant Design Table needs a unique key
|
||||
})));
|
||||
key: broker.brokerName,
|
||||
}));
|
||||
console.log("即将设置的数据:", newData); // 先打印
|
||||
setBrokerTableData(newData); // 再设置状态
|
||||
|
||||
brokerArray.sort((firstBroker, lastBroker) => {
|
||||
const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0);
|
||||
@@ -347,7 +348,7 @@ const DashboardPage = () => {
|
||||
|
||||
const brokerColumns = [
|
||||
{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,
|
||||
dataIndex: 'msgGetTotalTodayNow',
|
||||
|
@@ -179,7 +179,6 @@ const DlqMessageQueryPage = () => {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
// console.log("根据Message ID查询DLQ消息:", { msgId: messageId, consumerGroup: selectedConsumerGroup });
|
||||
try {
|
||||
const resp = await remoteApi.viewMessage(messageId, DLQ_GROUP_TOPIC_PREFIX + selectedConsumerGroup);
|
||||
if (resp.status === 0) {
|
||||
@@ -323,7 +322,6 @@ const DlqMessageQueryPage = () => {
|
||||
msgId: message.properties.ORIGIN_MESSAGE_ID,
|
||||
consumerGroup: selectedConsumerGroup,
|
||||
}));
|
||||
// console.log(`批量重发DLQ消息到 ${selectedConsumerGroup}:`, messagesToResend);
|
||||
try {
|
||||
const resp = await remoteApi.batchResendDlqMessage(messagesToResend);
|
||||
if (resp.status === 0) {
|
||||
@@ -355,7 +353,6 @@ const DlqMessageQueryPage = () => {
|
||||
message: t.ERROR,
|
||||
description: t.BATCH_RESEND_FAILED,
|
||||
});
|
||||
console.error("批量重发失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@@ -16,17 +16,17 @@
|
||||
*/
|
||||
|
||||
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";
|
||||
|
||||
const { Title } = Typography;
|
||||
const {Title} = Typography;
|
||||
|
||||
const Login = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [messageApi, msgContextHolder] = message.useMessage();
|
||||
|
||||
const onFinish = async (values) => {
|
||||
const { username, password } = values;
|
||||
const {username, password} = values;
|
||||
remoteApi.login(username, password).then((res) => {
|
||||
if (res.status === 0) {
|
||||
messageApi.success('登录成功');
|
||||
|
@@ -146,7 +146,6 @@ const MessageQueryPage = () => {
|
||||
message: t.ERROR,
|
||||
description: t.QUERY_FAILED,
|
||||
});
|
||||
console.error("查询失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -182,7 +181,6 @@ const MessageQueryPage = () => {
|
||||
message: t.ERROR,
|
||||
description: t.QUERY_FAILED,
|
||||
});
|
||||
console.error("查询失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -241,7 +239,6 @@ const MessageQueryPage = () => {
|
||||
message: t.ERROR,
|
||||
description: t.RESEND_FAILED,
|
||||
});
|
||||
console.error("重发失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Optionally, you might want to refresh the message detail after resend
|
||||
@@ -455,7 +452,6 @@ const MessageQueryPage = () => {
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{/* Message ID 查询结果通常直接弹窗显示,这里不需要表格 */}
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
@@ -15,12 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Select, Button, Switch, Input, Typography, Space, message } from 'antd';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Input, message, Select, Space, Switch, Typography} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Option } = Select;
|
||||
const {Title} = Typography;
|
||||
const {Option} = Select;
|
||||
|
||||
const Ops = () => {
|
||||
const [namesrvAddrList, setNamesrvAddrList] = useState([]);
|
||||
|
@@ -15,16 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Button, Select, Input, Card, Row, Col, notification, Spin } from 'antd';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import { remoteApi } from "../../api/remoteApi/remoteApi";
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Card, Col, Input, Modal, notification, Row, Select, Spin} from 'antd';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
import {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
const ProxyManager = () => {
|
||||
const { t } = useLanguage();
|
||||
const {t} = useLanguage();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [proxyAddrList, setProxyAddrList] = useState([]);
|
||||
@@ -47,7 +47,7 @@ const ProxyManager = () => {
|
||||
remoteApi.queryProxyHomePage((resp) => {
|
||||
setLoading(false);
|
||||
if (resp.status === 0) {
|
||||
const { proxyAddrList, currentProxyAddr } = resp.data;
|
||||
const {proxyAddrList, currentProxyAddr} = resp.data;
|
||||
setProxyAddrList(proxyAddrList || []);
|
||||
setSelectedProxy(currentProxyAddr || (proxyAddrList && proxyAddrList.length > 0 ? proxyAddrList[0] : ''));
|
||||
|
||||
@@ -58,7 +58,7 @@ const ProxyManager = () => {
|
||||
}
|
||||
|
||||
} 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]);
|
||||
@@ -71,7 +71,10 @@ const ProxyManager = () => {
|
||||
|
||||
const handleAddProxyAddr = () => {
|
||||
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;
|
||||
}
|
||||
setLoading(true);
|
||||
@@ -82,28 +85,28 @@ const ProxyManager = () => {
|
||||
setProxyAddrList(prevList => [...prevList, newProxyAddr.trim()]);
|
||||
}
|
||||
setNewProxyAddr('');
|
||||
notificationApi.info({ message: t.SUCCESS || "SUCCESS", duration: 2 });
|
||||
notificationApi.info({message: t.SUCCESS || "SUCCESS", duration: 2});
|
||||
} else {
|
||||
notificationApi.error({ message: resp.errMsg || t.ADD_PROXY_FAILED, duration: 2 });
|
||||
notificationApi.error({message: resp.errMsg || t.ADD_PROXY_FAILED, duration: 2});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<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
|
||||
title={
|
||||
<div style={{ fontSize: '20px', fontWeight: 'bold' }}>
|
||||
<div style={{fontSize: '20px', fontWeight: 'bold'}}>
|
||||
ProxyServerAddressList
|
||||
</div>
|
||||
}
|
||||
bordered={false}
|
||||
>
|
||||
<Row gutter={[16, 16]} align="middle">
|
||||
<Col flex="auto" style={{ minWidth: 300, maxWidth: 500 }}>
|
||||
<Col flex="auto" style={{minWidth: 300, maxWidth: 500}}>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
style={{width: '100%'}}
|
||||
value={selectedProxy}
|
||||
onChange={handleSelectChange}
|
||||
placeholder={t.SELECT}
|
||||
@@ -122,14 +125,14 @@ const ProxyManager = () => {
|
||||
</Row>
|
||||
|
||||
{writeOperationEnabled && (
|
||||
<Row gutter={[16, 16]} align="middle" style={{ marginTop: 16 }}>
|
||||
<Row gutter={[16, 16]} align="middle" style={{marginTop: 16}}>
|
||||
<Col>
|
||||
<label htmlFor="newProxyAddrInput">ProxyAddr:</label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Input
|
||||
id="newProxyAddrInput"
|
||||
style={{ width: 300 }}
|
||||
style={{width: 300}}
|
||||
value={newProxyAddr}
|
||||
onChange={(e) => setNewProxyAddr(e.target.value)}
|
||||
placeholder={t.INPUT_PROXY_ADDR}
|
||||
@@ -149,25 +152,26 @@ const ProxyManager = () => {
|
||||
onCancel={() => setShowModal(false)}
|
||||
title={`${t.PROXY_CONFIG} [${selectedProxy}]`}
|
||||
footer={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<Button onClick={() => setShowModal(false)}>{t.CLOSE}</Button>
|
||||
</div>
|
||||
}
|
||||
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>
|
||||
{Object.entries(allProxyConfig).length > 0 ? (
|
||||
Object.entries(allProxyConfig).map(([key, value]) => (
|
||||
<tr key={key}>
|
||||
<td style={{ fontWeight: 500, width: '30%' }}>{key}</td>
|
||||
<td style={{fontWeight: 500, width: '30%'}}>{key}</td>
|
||||
<td>{value}</td>
|
||||
</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>
|
||||
)}
|
||||
</tbody>
|
||||
|
@@ -173,7 +173,6 @@ const DeployHistoryList = () => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
const filterList = (currentPage) => {
|
||||
const lowExceptStr = filterStr.toLowerCase();
|
||||
const canShowList = allTopicList.filter((topic, index) => {
|
||||
@@ -223,7 +222,7 @@ const DeployHistoryList = () => {
|
||||
|
||||
try {
|
||||
if (isUpdate) {
|
||||
// topic 已经是字符串
|
||||
// topic 已经是字符串
|
||||
const configResult = await remoteApi.getTopicConfig(topic);
|
||||
if (configResult.status === 0) {
|
||||
const dataToSet = Array.isArray(configResult.data) ? configResult.data : [configResult.data];
|
||||
@@ -257,7 +256,7 @@ const DeployHistoryList = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!isUpdate){
|
||||
if (!isUpdate) {
|
||||
const clusterResult = await remoteApi.getClusterList();
|
||||
if (clusterResult.status === 0) {
|
||||
setAllClusterNameList(Object.keys(clusterResult.data.clusterInfo.clusterAddrTable));
|
||||
@@ -276,7 +275,7 @@ const DeployHistoryList = () => {
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.TOPIC_OPERATION_SUCCESS);
|
||||
closeAddUpdateDialog();
|
||||
if(!isUpdateMode) {
|
||||
if (!isUpdateMode) {
|
||||
await getTopicList()
|
||||
}
|
||||
} else {
|
||||
|
@@ -16,15 +16,15 @@
|
||||
*/
|
||||
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
|
@@ -64,7 +64,7 @@ const AppRouter = () => {
|
||||
|
||||
useEffect(() => {
|
||||
remoteApi.setRedirectHandler(() => {
|
||||
navigate('/login', { replace: true });
|
||||
navigate('/login', {replace: true});
|
||||
});
|
||||
}, [navigate]);
|
||||
|
||||
|
@@ -14,10 +14,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { themes, defaultTheme } from '../../assets/styles/theme';
|
||||
import { setTheme } from '../actions/themeActions';
|
||||
import {useEffect} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {defaultTheme, themes} from '../../assets/styles/theme';
|
||||
import {setTheme} from '../actions/themeActions';
|
||||
|
||||
export const useTheme = () => {
|
||||
// 从 Redux store 中取出 currentThemeName
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createStore,combineReducers } from 'redux';
|
||||
import {combineReducers, createStore} from 'redux';
|
||||
import themeReducer from './reducers/themeReducer';
|
||||
|
||||
// 组合所有的 reducers
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SET_THEME } from '../actions/themeActions';
|
||||
import {SET_THEME} from '../actions/themeActions';
|
||||
|
||||
const getInitialTheme = () => {
|
||||
return localStorage.getItem('appTheme') || 'default';
|
||||
|
7
pom.xml
7
pom.xml
@@ -16,7 +16,8 @@
|
||||
~ 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>
|
||||
<groupId>org.apache</groupId>
|
||||
@@ -432,7 +433,7 @@
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node </id>
|
||||
<id>install node</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
@@ -469,7 +470,7 @@
|
||||
<configuration>
|
||||
<target>
|
||||
<copy todir="${project.build.directory}/classes/public">
|
||||
<fileset dir="${project.basedir}/frontend-new/build" />
|
||||
<fileset dir="${project.basedir}/frontend-new/build"/>
|
||||
</copy>
|
||||
</target>
|
||||
</configuration>
|
||||
|
@@ -32,7 +32,7 @@ public class MQAdminPooledObjectFactory implements PooledObjectFactory<MQAdminEx
|
||||
@Override
|
||||
public PooledObject<MQAdminExt> makeObject() throws Exception {
|
||||
DefaultPooledObject<MQAdminExt> pooledObject = new DefaultPooledObject<>(
|
||||
mqAdminFactory.getInstance());
|
||||
mqAdminFactory.getInstance());
|
||||
return pooledObject;
|
||||
}
|
||||
|
||||
|
@@ -40,8 +40,8 @@ public class MqAdminExtObjectPool {
|
||||
MQAdminFactory mqAdminFactory = new MQAdminFactory(rmqConfigure);
|
||||
mqAdminPooledObjectFactory.setMqAdminFactory(mqAdminFactory);
|
||||
GenericObjectPool<MQAdminExt> genericObjectPool = new GenericObjectPool<MQAdminExt>(
|
||||
mqAdminPooledObjectFactory,
|
||||
genericObjectPoolConfig);
|
||||
mqAdminPooledObjectFactory,
|
||||
genericObjectPoolConfig);
|
||||
return genericObjectPool;
|
||||
}
|
||||
}
|
||||
|
@@ -41,20 +41,20 @@ public class CollectExecutorConfig {
|
||||
@Bean(name = "collectExecutor")
|
||||
public ExecutorService collectExecutor(CollectExecutorConfig collectExecutorConfig) {
|
||||
ExecutorService collectExecutor = new ThreadPoolExecutor(
|
||||
collectExecutorConfig.getCoreSize(),
|
||||
collectExecutorConfig.getMaxSize(),
|
||||
collectExecutorConfig.getKeepAliveTime(),
|
||||
TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingDeque<>(collectExecutorConfig.getQueueSize()),
|
||||
new ThreadFactory() {
|
||||
private final AtomicLong threadIndex = new AtomicLong(0);
|
||||
collectExecutorConfig.getCoreSize(),
|
||||
collectExecutorConfig.getMaxSize(),
|
||||
collectExecutorConfig.getKeepAliveTime(),
|
||||
TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingDeque<>(collectExecutorConfig.getQueueSize()),
|
||||
new ThreadFactory() {
|
||||
private final AtomicLong threadIndex = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r, "collectTopicThread_" + this.threadIndex.incrementAndGet());
|
||||
}
|
||||
},
|
||||
new ThreadPoolExecutor.DiscardOldestPolicy()
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r, "collectTopicThread_" + this.threadIndex.incrementAndGet());
|
||||
}
|
||||
},
|
||||
new ThreadPoolExecutor.DiscardOldestPolicy()
|
||||
);
|
||||
return collectExecutor;
|
||||
}
|
||||
|
@@ -91,6 +91,10 @@ public class RMQConfigure {
|
||||
@Getter
|
||||
private Integer clientCallbackExecutorThreads = 4;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private String authMode = "file";
|
||||
|
||||
public void setProxyAddrs(List<String> proxyAddrs) {
|
||||
this.proxyAddrs = proxyAddrs;
|
||||
if (CollectionUtils.isNotEmpty(proxyAddrs)) {
|
||||
@@ -112,10 +116,12 @@ public class RMQConfigure {
|
||||
logger.info("setNameSrvAddrByProperty nameSrvAddr={}", namesrvAddr);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isACLEnabled() {
|
||||
return !(StringUtils.isAnyBlank(this.accessKey, this.secretKey) ||
|
||||
StringUtils.isAnyEmpty(this.accessKey, this.secretKey));
|
||||
StringUtils.isAnyEmpty(this.accessKey, this.secretKey));
|
||||
}
|
||||
|
||||
public String getRocketMqDashboardDataPath() {
|
||||
return dataPath;
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import org.apache.rocketmq.dashboard.model.request.UserUpdateRequest;
|
||||
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
|
||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||
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.GetMapping;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@Controller
|
||||
@RequestMapping("/acl")
|
||||
public class AclController {
|
||||
|
||||
@Autowired
|
||||
private AclServiceImpl aclService;
|
||||
|
||||
@GetMapping("/listUsers")
|
||||
@GetMapping("/users.query")
|
||||
@ResponseBody
|
||||
public List<UserInfo> listUsers(@RequestParam(required = false) String brokerAddress) {
|
||||
return aclService.listUsers(brokerAddress);
|
||||
}
|
||||
|
||||
@GetMapping("/listAcls")
|
||||
@GetMapping("/acls.query")
|
||||
@ResponseBody
|
||||
public Object listAcls(
|
||||
@RequestParam(required = false) String brokerAddress,
|
||||
@@ -56,34 +56,34 @@ public class AclController {
|
||||
return aclService.listAcls(brokerAddress, searchParam);
|
||||
}
|
||||
|
||||
@PostMapping("/createAcl")
|
||||
@PostMapping("/createAcl.do")
|
||||
@ResponseBody
|
||||
public Object createAcl(@RequestBody PolicyRequest request) {
|
||||
aclService.createAcl(request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteUser")
|
||||
@DeleteMapping("/deleteUser.do")
|
||||
@ResponseBody
|
||||
public Object deleteUser(@RequestParam(required = false) String brokerAddress, @RequestParam String username) {
|
||||
aclService.deleteUser(brokerAddress, username);
|
||||
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
|
||||
public Object updateUser(@RequestBody UserUpdateRequest request) {
|
||||
aclService.updateUser(request.getBrokerAddress(), request.getUserInfo());
|
||||
return true;
|
||||
}
|
||||
|
||||
@PostMapping("/createUser")
|
||||
@PostMapping("/createUser.do")
|
||||
public Object createUser(@RequestBody UserCreateRequest request) {
|
||||
aclService.createUser(request.getBrokerAddress(), request.getUserInfo());
|
||||
return true;
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteAcl")
|
||||
@DeleteMapping("/deleteAcl.do")
|
||||
public Object deleteAcl(
|
||||
@RequestParam(required = false) String brokerAddress,
|
||||
@RequestParam String subject,
|
||||
@@ -92,7 +92,7 @@ public class AclController {
|
||||
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
|
||||
public Object updateAcl(@RequestBody PolicyRequest request) {
|
||||
aclService.updateAcl(request);
|
||||
|
@@ -54,7 +54,7 @@ public class ConsumerController {
|
||||
@RequestMapping(value = "/group.refresh")
|
||||
@ResponseBody
|
||||
public Object refresh(String address,
|
||||
String consumerGroup) {
|
||||
String consumerGroup) {
|
||||
return consumerService.refreshGroup(address, consumerGroup);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class ConsumerController {
|
||||
@ResponseBody
|
||||
public Object consumerCreateOrUpdateRequest(@RequestBody ConsumerConfigInfo consumerConfigInfo) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public class ConsumerController {
|
||||
@RequestMapping(value = "/consumerRunningInfo.query")
|
||||
@ResponseBody
|
||||
public Object getConsumerRunningInfo(@RequestParam String consumerGroup, @RequestParam String clientId,
|
||||
@RequestParam boolean jstack) {
|
||||
@RequestParam boolean jstack) {
|
||||
return consumerService.getConsumerRunningInfo(consumerGroup, clientId, jstack);
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ public class DashboardController {
|
||||
if (Strings.isNullOrEmpty(topicName)) {
|
||||
return dashboardService.queryTopicData(date);
|
||||
}
|
||||
return dashboardService.queryTopicData(date,topicName);
|
||||
return dashboardService.queryTopicData(date, topicName);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/topicCurrent.query", method = RequestMethod.GET)
|
||||
|
@@ -64,7 +64,7 @@ public class MessageTraceController {
|
||||
@RequestMapping(value = "/viewMessageTraceGraph.query", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public MessageTraceGraph viewMessageTraceGraph(@RequestParam String msgId,
|
||||
@RequestParam(required = false) String traceTopic) {
|
||||
@RequestParam(required = false) String traceTopic) {
|
||||
return messageTraceService.queryMessageTraceGraph(msgId, traceTopic);
|
||||
}
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ public class MonitorController {
|
||||
@RequestMapping(value = "/createOrUpdateConsumerMonitor.do", method = {RequestMethod.POST})
|
||||
@ResponseBody
|
||||
public Object createOrUpdateConsumerMonitor(@RequestParam String consumeGroupName, @RequestParam int minCount,
|
||||
@RequestParam int maxDiffTotal) {
|
||||
@RequestParam int maxDiffTotal) {
|
||||
return monitorService.createOrUpdateConsumerMonitor(consumeGroupName, new ConsumerMonitorConfig(minCount, maxDiffTotal));
|
||||
}
|
||||
|
||||
|
@@ -52,7 +52,7 @@ public class OpsController {
|
||||
@ResponseBody
|
||||
public Object addNameSvrAddr(@RequestParam String newNamesrvAddr) {
|
||||
Preconditions.checkArgument(StringUtils.isNotEmpty(newNamesrvAddr),
|
||||
"namesrvAddr can not be blank");
|
||||
"namesrvAddr can not be blank");
|
||||
opsService.addNameSvrAddr(newNamesrvAddr);
|
||||
return true;
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
public class ProxyController {
|
||||
@Resource
|
||||
private ProxyService proxyService;
|
||||
|
||||
@RequestMapping(value = "/homePage.query", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Object homePage() {
|
||||
|
@@ -59,7 +59,7 @@ public class TestController {
|
||||
|
||||
@Override
|
||||
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
|
||||
ConsumeConcurrentlyContext context) {
|
||||
ConsumeConcurrentlyContext context) {
|
||||
logger.info("receiveMessage msgSize={}", msgs.size());
|
||||
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
|
||||
}
|
||||
@@ -72,26 +72,25 @@ public class TestController {
|
||||
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override public void run() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
try {
|
||||
Message msg = new Message(testTopic,
|
||||
"TagA" + i,
|
||||
"KEYS" + i,
|
||||
("Hello RocketMQ " + i).getBytes()
|
||||
"TagA" + i,
|
||||
"KEYS" + i,
|
||||
("Hello RocketMQ " + i).getBytes()
|
||||
);
|
||||
Thread.sleep(1000L);
|
||||
SendResult sendResult = producer.send(msg);
|
||||
logger.info("sendMessage={}", JsonUtil.obj2String(sendResult));
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ public class TopicController {
|
||||
@RequestMapping(value = "/list.query", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@ public class TopicController {
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(value = "/createOrUpdate.do", method = { RequestMethod.POST})
|
||||
@RequestMapping(value = "/createOrUpdate.do", method = {RequestMethod.POST})
|
||||
@ResponseBody
|
||||
public Object topicCreateOrUpdateRequest(@RequestBody TopicConfigInfo topicCreateOrUpdateRequest) {
|
||||
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));
|
||||
topicService.createOrUpdate(topicCreateOrUpdateRequest);
|
||||
return true;
|
||||
@@ -100,14 +100,14 @@ public class TopicController {
|
||||
@RequestMapping(value = "/examineTopicConfig.query")
|
||||
@ResponseBody
|
||||
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);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/sendTopicMessage.do", method = {RequestMethod.POST})
|
||||
@ResponseBody
|
||||
public Object sendTopicMessage(
|
||||
@RequestBody SendTopicMessageRequest sendTopicMessageRequest) throws RemotingException, MQClientException, InterruptedException {
|
||||
@RequestBody SendTopicMessageRequest sendTopicMessageRequest) throws RemotingException, MQClientException, InterruptedException {
|
||||
return topicService.sendTopicMessageRequest(sendTopicMessageRequest);
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,6 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
|
||||
@WebFilter(urlPatterns = "/*", filterName = "httpBasicAuthorizedFilter")
|
||||
public class HttpBasicAuthorizedFilter implements Filter {
|
||||
|
||||
|
@@ -25,7 +25,9 @@ import java.util.Map;
|
||||
|
||||
public class MessageView {
|
||||
|
||||
/** from MessageExt **/
|
||||
/**
|
||||
* from MessageExt
|
||||
**/
|
||||
private int queueId;
|
||||
private int storeSize;
|
||||
private long queueOffset;
|
||||
@@ -41,13 +43,17 @@ public class MessageView {
|
||||
private long preparedTransactionOffset;
|
||||
/**from MessageExt**/
|
||||
|
||||
/** from Message **/
|
||||
/**
|
||||
* from Message
|
||||
**/
|
||||
private String topic;
|
||||
private int flag;
|
||||
private Map<String, String> properties;
|
||||
private String messageBody; // body
|
||||
|
||||
/** from Message **/
|
||||
/**
|
||||
* from Message
|
||||
**/
|
||||
|
||||
public static MessageView fromMessageExt(MessageExt messageExt) {
|
||||
MessageView messageView = new MessageView();
|
||||
|
@@ -19,8 +19,8 @@ package org.apache.rocketmq.dashboard.model;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
public class User {
|
||||
public static final int ORDINARY = 0;
|
||||
public static final int ADMIN = 1;
|
||||
public static final int SUPER = 0;
|
||||
public static final int NORMAL = 1;
|
||||
|
||||
private long id;
|
||||
private String name;
|
||||
|
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.model.request;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import java.util.List;
|
||||
@@ -24,7 +25,9 @@ public class TopicConfigInfo {
|
||||
private List<String> clusterNameList;
|
||||
private List<String> brokerNameList;
|
||||
|
||||
/** topicConfig */
|
||||
/**
|
||||
* topicConfig
|
||||
*/
|
||||
private String topicName;
|
||||
private int writeQueueNums;
|
||||
private int readQueueNums;
|
||||
@@ -32,6 +35,7 @@ public class TopicConfigInfo {
|
||||
private boolean order;
|
||||
|
||||
private String messageType;
|
||||
|
||||
public List<String> getClusterNameList() {
|
||||
return clusterNameList;
|
||||
}
|
||||
@@ -40,8 +44,9 @@ public class TopicConfigInfo {
|
||||
this.clusterNameList = clusterNameList;
|
||||
}
|
||||
|
||||
/** topicConfig */
|
||||
|
||||
/**
|
||||
* topicConfig
|
||||
*/
|
||||
|
||||
|
||||
public List<String> getBrokerNameList() {
|
||||
@@ -102,8 +107,6 @@ public class TopicConfigInfo {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
@@ -112,16 +115,16 @@ public class TopicConfigInfo {
|
||||
return false;
|
||||
TopicConfigInfo that = (TopicConfigInfo) o;
|
||||
return writeQueueNums == that.writeQueueNums &&
|
||||
readQueueNums == that.readQueueNums &&
|
||||
perm == that.perm &&
|
||||
order == that.order &&
|
||||
Objects.equal(topicName, that.topicName) &&
|
||||
Objects.equal(messageType, that.messageType);
|
||||
readQueueNums == that.readQueueNums &&
|
||||
perm == that.perm &&
|
||||
order == that.order &&
|
||||
Objects.equal(topicName, that.topicName) &&
|
||||
Objects.equal(messageType, that.messageType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(topicName, writeQueueNums, readQueueNums, perm, order,messageType);
|
||||
return Objects.hashCode(topicName, writeQueueNums, readQueueNums, perm, order, messageType);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,9 +17,11 @@
|
||||
|
||||
package org.apache.rocketmq.dashboard.model.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class UserInfoParam {
|
||||
private String username;
|
||||
private String password;
|
||||
|
@@ -29,16 +29,16 @@ import java.util.Set;
|
||||
public abstract class AbstractCommonService {
|
||||
@Resource
|
||||
protected MQAdminExt mqAdminExt;
|
||||
|
||||
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();
|
||||
if (CollectionUtils.isNotEmpty(clusterNameList)) {
|
||||
try {
|
||||
for (String clusterName : clusterNameList) {
|
||||
finalBrokerNameList.addAll(clusterAddrTable.get(clusterName));
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -39,5 +39,5 @@ public interface AclService {
|
||||
|
||||
void deleteAcl(String brokerAddress, String subject, String resource);
|
||||
|
||||
void updateAcl(PolicyRequest policyRequest);
|
||||
void updateAcl(PolicyRequest policyRequest);
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface ConsumerService {
|
||||
List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup,String address);
|
||||
List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup, String address);
|
||||
|
||||
GroupConsumeInfo queryGroup(String consumerGroup, String address);
|
||||
|
||||
|
@@ -32,7 +32,7 @@ public interface DashboardService {
|
||||
Map<String, List<String>> queryTopicData(String date);
|
||||
|
||||
/**
|
||||
* @param date format yyyy-MM-dd
|
||||
* @param date format yyyy-MM-dd
|
||||
* @param topicName
|
||||
*/
|
||||
List<String> queryTopicData(String date, String topicName);
|
||||
|
@@ -39,21 +39,18 @@ public interface MessageService {
|
||||
/**
|
||||
* @param topic
|
||||
* @param begin
|
||||
* @param end
|
||||
* org.apache.rocketmq.tools.command.message.PrintMessageSubCommand
|
||||
* @param end org.apache.rocketmq.tools.command.message.PrintMessageSubCommand
|
||||
*/
|
||||
List<MessageView> queryMessageByTopic(final String topic, final long begin,
|
||||
final long end);
|
||||
final long end);
|
||||
|
||||
List<MessageTrack> messageTrackDetail(MessageExt msg);
|
||||
|
||||
ConsumeMessageDirectlyResult consumeMessageDirectly(String topic, String msgId, String consumerGroup,
|
||||
String clientId);
|
||||
String clientId);
|
||||
|
||||
|
||||
MessagePage queryMessageByPage(MessageQuery query);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ public interface OpsService {
|
||||
|
||||
String getNameSvrList();
|
||||
|
||||
Map<CheckerType,Object> rocketMqStatusCheck();
|
||||
Map<CheckerType, Object> rocketMqStatusCheck();
|
||||
|
||||
boolean updateIsVIPChannel(String useVIPChannel);
|
||||
|
||||
|
@@ -24,5 +24,5 @@ public interface ProxyService {
|
||||
|
||||
void updateProxyAddrList(String proxyAddr);
|
||||
|
||||
Map<String, Object> getProxyHomePage();
|
||||
Map<String, Object> getProxyHomePage();
|
||||
}
|
||||
|
@@ -94,19 +94,20 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
private Logger logger = LoggerFactory.getLogger(MQAdminExtImpl.class);
|
||||
|
||||
|
||||
public MQAdminExtImpl() {}
|
||||
public MQAdminExtImpl() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateBrokerConfig(String brokerAddr, Properties properties)
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
UnsupportedEncodingException, InterruptedException, MQBrokerException, MQClientException {
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
UnsupportedEncodingException, InterruptedException, MQBrokerException, MQClientException {
|
||||
MQAdminInstance.threadLocalMQAdminExt().updateBrokerConfig(brokerAddr, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAndUpdateTopicConfig(String addr, TopicConfig config)
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
MQAdminInstance.threadLocalMQAdminExt().createAndUpdateTopicConfig(addr, config);
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public void createAndUpdateSubscriptionGroupConfig(String addr, SubscriptionGroupConfig config)
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
MQAdminInstance.threadLocalMQAdminExt().createAndUpdateSubscriptionGroupConfig(addr, config);
|
||||
}
|
||||
|
||||
@@ -134,8 +135,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
RemotingCommand response = null;
|
||||
try {
|
||||
response = remotingClient.invokeSync(addr, request, 8000);
|
||||
}
|
||||
catch (Exception err) {
|
||||
} catch (Exception err) {
|
||||
Throwables.throwIfUnchecked(err);
|
||||
throw new RuntimeException(err);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public TopicStatsTable examineTopicStats(String topic)
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineTopicStats(topic);
|
||||
}
|
||||
|
||||
@@ -191,14 +191,14 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public KVTable fetchBrokerRuntimeStats(String brokerAddr)
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
InterruptedException, MQBrokerException {
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
InterruptedException, MQBrokerException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().fetchBrokerRuntimeStats(brokerAddr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsumeStats examineConsumeStats(String consumerGroup)
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(consumerGroup);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public ConsumeStats examineConsumeStats(String consumerGroup, String topic)
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(consumerGroup, topic);
|
||||
}
|
||||
|
||||
@@ -220,27 +220,27 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public ClusterInfo examineBrokerClusterInfo()
|
||||
throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException,
|
||||
RemotingConnectException {
|
||||
throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException,
|
||||
RemotingConnectException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineBrokerClusterInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopicRouteData examineTopicRouteInfo(String topic)
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineTopicRouteInfo(topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsumerConnection examineConsumerConnectionInfo(String consumerGroup)
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
InterruptedException, MQBrokerException, RemotingException, MQClientException {
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
InterruptedException, MQBrokerException, RemotingException, MQClientException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineConsumerConnectionInfo(consumerGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProducerConnection examineProducerConnectionInfo(String producerGroup, String topic)
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineProducerConnectionInfo(producerGroup, topic);
|
||||
}
|
||||
|
||||
@@ -251,14 +251,14 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public int wipeWritePermOfBroker(String namesrvAddr, String brokerName)
|
||||
throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException,
|
||||
RemotingTimeoutException, InterruptedException, MQClientException {
|
||||
throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException,
|
||||
RemotingTimeoutException, InterruptedException, MQClientException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().wipeWritePermOfBroker(namesrvAddr, brokerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -269,62 +269,62 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public String getKVConfig(String namespace, String key)
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().getKVConfig(namespace, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVTable getKVListByNamespace(String namespace)
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().getKVListByNamespace(namespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
MQAdminInstance.threadLocalMQAdminExt().deleteTopicInBroker(addrs, topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTopicInNameServer(Set<String> addrs, String topic)
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
MQAdminInstance.threadLocalMQAdminExt().deleteTopicInNameServer(addrs, topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSubscriptionGroup(String addr, String groupName)
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
MQAdminInstance.threadLocalMQAdminExt().deleteSubscriptionGroup(addr, groupName);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteKvConfig(String namespace, String key)
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
|
||||
MQAdminInstance.threadLocalMQAdminExt().deleteKvConfig(namespace, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -335,59 +335,59 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupList queryTopicConsumeByWho(String topic)
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
InterruptedException, MQBrokerException, RemotingException, MQClientException {
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
|
||||
InterruptedException, MQBrokerException, RemotingException, MQClientException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().queryTopicConsumeByWho(topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cleanExpiredConsumerQueue(String cluster)
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException,
|
||||
InterruptedException {
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException,
|
||||
InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().cleanExpiredConsumerQueue(cluster);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cleanExpiredConsumerQueueByAddr(String addr)
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException,
|
||||
InterruptedException {
|
||||
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException,
|
||||
InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().cleanExpiredConsumerQueueByAddr(addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack)
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
throws RemotingException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().getConsumerRunningInfo(consumerGroup, clientId, jstack);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<MessageTrack> messageTrackDetail(MessageExt msg)
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().messageTrackDetail(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
//https://github.com/apache/incubator-rocketmq/pull/69
|
||||
@Override
|
||||
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);
|
||||
MQAdminImpl mqAdminImpl = MQAdminInstance.threadLocalMqClientInstance().getMQAdminImpl();
|
||||
Set<String> clusterList = MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic);
|
||||
@@ -478,7 +478,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -489,96 +489,99 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopicList fetchTopicsByCLuster(
|
||||
String clusterName) throws RemotingException, MQClientException, InterruptedException {
|
||||
String clusterName) throws RemotingException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().fetchTopicsByCLuster(clusterName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cleanUnusedTopic(
|
||||
String cluster) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
|
||||
String cluster) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().cleanUnusedTopic(cluster);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cleanUnusedTopicByAddr(
|
||||
String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
|
||||
String addr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().cleanUnusedTopicByAddr(addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTopicClusterList(
|
||||
String topic) throws InterruptedException, MQBrokerException, MQClientException, RemotingException {
|
||||
String topic) throws InterruptedException, MQBrokerException, MQClientException, RemotingException {
|
||||
return MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
// 4.0.0 added
|
||||
@Override public void updateNameServerConfig(Properties properties,
|
||||
List<String> list) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException {
|
||||
@Override
|
||||
public void updateNameServerConfig(Properties properties,
|
||||
List<String> list) throws InterruptedException, RemotingConnectException, UnsupportedEncodingException, RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException {
|
||||
|
||||
}
|
||||
|
||||
@Override public Map<String, Properties> getNameServerConfig(
|
||||
List<String> list) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException {
|
||||
@Override
|
||||
public Map<String, Properties> getNameServerConfig(
|
||||
List<String> list) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException, UnsupportedEncodingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public QueryConsumeQueueResponseBody queryConsumeQueue(String brokerAddr, String topic,
|
||||
int queueId, long index, int count,
|
||||
String consumerGroup) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException {
|
||||
@Override
|
||||
public QueryConsumeQueueResponseBody queryConsumeQueue(String brokerAddr, String topic,
|
||||
int queueId, long index, int count,
|
||||
String consumerGroup) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -588,8 +591,9 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
}
|
||||
|
||||
|
||||
@Override public boolean resumeCheckHalfMessage(String topic,
|
||||
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
@Override
|
||||
public boolean resumeCheckHalfMessage(String topic,
|
||||
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -602,7 +606,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public void removeBrokerFromContainer(String brokerContainerAddr, String clusterName, String brokerName,
|
||||
long brokerId) throws InterruptedException, MQBrokerException, RemotingTimeoutException,
|
||||
long brokerId) throws InterruptedException, MQBrokerException, RemotingTimeoutException,
|
||||
RemotingSendRequestException, RemotingConnectException {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'removeBrokerFromContainer'");
|
||||
@@ -624,7 +628,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public ConsumeStats examineConsumeStats(String brokerAddr, String consumerGroup, String topicName,
|
||||
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException,
|
||||
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException,
|
||||
RemotingConnectException, MQBrokerException {
|
||||
// TODO Auto-generated method stub
|
||||
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(brokerAddr, consumerGroup, topicName, timeoutMillis);
|
||||
@@ -717,7 +721,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getConsumerRunningInfo'");
|
||||
}
|
||||
@@ -731,7 +735,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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 {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setMessageRequestMode'");
|
||||
@@ -746,14 +750,14 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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
|
||||
throw new UnsupportedOperationException("Unimplemented method 'resetOffsetByQueueId'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createStaticTopic(String addr, String defaultTopic, TopicConfig topicConfig,
|
||||
TopicQueueMappingDetail mappingDetail, boolean force)
|
||||
TopicQueueMappingDetail mappingDetail, boolean force)
|
||||
throws RemotingException, InterruptedException, MQBrokerException {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'createStaticTopic'");
|
||||
@@ -761,7 +765,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
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
|
||||
throw new UnsupportedOperationException("Unimplemented method 'updateAndGetGroupReadForbidden'");
|
||||
}
|
||||
@@ -831,7 +835,7 @@ public class MQAdminExtImpl implements MQAdminExt {
|
||||
|
||||
@Override
|
||||
public void cleanControllerBrokerData(String controllerAddr, String clusterName, String brokerName,
|
||||
String brokerAddr, boolean isCleanLivingBroker)
|
||||
String brokerAddr, boolean isCleanLivingBroker)
|
||||
throws RemotingException, InterruptedException, MQBrokerException {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'cleanControllerBrokerData'");
|
||||
|
@@ -58,7 +58,7 @@ public abstract class AbstractFileStore {
|
||||
}
|
||||
}
|
||||
|
||||
abstract void load(InputStream inputStream);
|
||||
protected abstract void load(InputStream inputStream);
|
||||
|
||||
private void load() {
|
||||
load(null);
|
||||
@@ -66,7 +66,7 @@ public abstract class AbstractFileStore {
|
||||
|
||||
private boolean watch() {
|
||||
try {
|
||||
FileWatchService fileWatchService = new FileWatchService(new String[] {filePath}, new FileWatchService.Listener() {
|
||||
FileWatchService fileWatchService = new FileWatchService(new String[]{filePath}, new FileWatchService.Listener() {
|
||||
@Override
|
||||
public void onChanged(String path) {
|
||||
log.info("The file changed, reload the context");
|
||||
|
@@ -212,7 +212,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
} else {
|
||||
consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL");
|
||||
}
|
||||
consumeInfo.setGroup(consumerGroup);
|
||||
consumeInfo.setUpdateTime(new Date());
|
||||
groupConsumeInfoList.add(consumeInfo);
|
||||
} catch (Exception e) {
|
||||
@@ -270,6 +269,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
logger.warn("examineConsumeStats or examineConsumerConnectionInfo exception, "
|
||||
+ consumerGroup, e);
|
||||
}
|
||||
groupConsumeInfo.setGroup(consumerGroup);
|
||||
return groupConsumeInfo;
|
||||
}
|
||||
|
||||
|
@@ -51,51 +51,52 @@ public class DashboardCollectServiceImpl implements DashboardCollectService {
|
||||
private final static Logger log = LoggerFactory.getLogger(DashboardCollectServiceImpl.class);
|
||||
|
||||
private LoadingCache<String, List<String>> brokerMap = CacheBuilder.newBuilder()
|
||||
.maximumSize(1000)
|
||||
.concurrencyLevel(10)
|
||||
.recordStats()
|
||||
.ticker(Ticker.systemTicker())
|
||||
.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>>() {
|
||||
.maximumSize(1000)
|
||||
.concurrencyLevel(10)
|
||||
.recordStats()
|
||||
.ticker(Ticker.systemTicker())
|
||||
.removalListener(new RemovalListener<Object, Object>() {
|
||||
@Override
|
||||
public List<String> load(String key) {
|
||||
List<String> list = Lists.newArrayList();
|
||||
return list;
|
||||
public void onRemoval(RemovalNotification<Object, Object> notification) {
|
||||
log.debug(notification.getKey() + " was removed, cause is " + notification.getCause());
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
.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()
|
||||
.maximumSize(1000)
|
||||
.concurrencyLevel(10)
|
||||
.recordStats()
|
||||
.ticker(Ticker.systemTicker())
|
||||
.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>>() {
|
||||
.maximumSize(1000)
|
||||
.concurrencyLevel(10)
|
||||
.recordStats()
|
||||
.ticker(Ticker.systemTicker())
|
||||
.removalListener(new RemovalListener<Object, Object>() {
|
||||
@Override
|
||||
public List<String> load(String key) {
|
||||
List<String> list = Lists.newArrayList();
|
||||
return list;
|
||||
public void onRemoval(RemovalNotification<Object, Object> notification) {
|
||||
log.debug(notification.getKey() + " was removed, cause is " + notification.getCause());
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
.build(
|
||||
new CacheLoader<String, List<String>>() {
|
||||
@Override
|
||||
public List<String> load(String key) {
|
||||
List<String> list = Lists.newArrayList();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@Override
|
||||
public LoadingCache<String, List<String>> getBrokerMap() {
|
||||
return brokerMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadingCache<String, List<String>> getTopicMap() {
|
||||
return topicMap;
|
||||
@@ -106,8 +107,7 @@ public class DashboardCollectServiceImpl implements DashboardCollectService {
|
||||
List<String> strings;
|
||||
try {
|
||||
strings = Files.readLines(file, Charsets.UTF_8);
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
|
||||
@Resource
|
||||
private DashboardCollectService dashboardCollectService;
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@Override
|
||||
|
@@ -60,7 +60,7 @@ public class DlqMessageServiceImpl implements DlqMessageService {
|
||||
} catch (MQClientException e) {
|
||||
// If the %DLQ%Group does not exist, the message returns null
|
||||
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());
|
||||
} else {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
@@ -78,8 +78,8 @@ public class DlqMessageServiceImpl implements DlqMessageService {
|
||||
List<DlqMessageResendResult> batchResendResults = new LinkedList<>();
|
||||
for (DlqMessageRequest dlqMessage : dlqMessages) {
|
||||
ConsumeMessageDirectlyResult result = messageService.consumeMessageDirectly(dlqMessage.getTopicName(),
|
||||
dlqMessage.getMsgId(), dlqMessage.getConsumerGroup(),
|
||||
dlqMessage.getClientId());
|
||||
dlqMessage.getMsgId(), dlqMessage.getConsumerGroup(),
|
||||
dlqMessage.getClientId());
|
||||
DlqMessageResendResult resendResult = new DlqMessageResendResult(result, dlqMessage.getMsgId());
|
||||
batchResendResults.add(resendResult);
|
||||
}
|
||||
|
@@ -17,13 +17,10 @@
|
||||
|
||||
package org.apache.rocketmq.dashboard.service.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
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.UserService;
|
||||
import org.apache.rocketmq.dashboard.service.provider.UserInfoProvider;
|
||||
import org.apache.rocketmq.dashboard.service.strategy.UserContext;
|
||||
import org.apache.rocketmq.dashboard.util.UserInfoContext;
|
||||
import org.apache.rocketmq.dashboard.util.WebUtil;
|
||||
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 java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Service
|
||||
public class LoginServiceImpl implements LoginService {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Resource
|
||||
private RMQConfigure rmqConfigure;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserInfoProvider userInfoProvider;
|
||||
private UserContext userContext;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean login(HttpServletRequest request, HttpServletResponse response) {
|
||||
String username = (String) WebUtil.getValueFromSession(request, WebUtil.USER_NAME);
|
||||
if (username != null) {
|
||||
UserInfo userInfo = userInfoProvider.getUserInfoByUsername(username);
|
||||
UserInfo userInfo = userContext.queryByUsername(username);
|
||||
if (userInfo == null) {
|
||||
auth(request, response);
|
||||
return false;
|
||||
}
|
||||
UserInfoContext.set(WebUtil.USER_NAME, userInfo);
|
||||
return true;
|
||||
|
||||
}
|
||||
auth(request, response);
|
||||
return false;
|
||||
@@ -69,11 +61,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
protected void auth(HttpServletRequest request, HttpServletResponse response) {
|
||||
try {
|
||||
String url = WebUtil.getUrl(request);
|
||||
try {
|
||||
url = URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("url encode:{}", url, e);
|
||||
}
|
||||
url = URLEncoder.encode(url, StandardCharsets.UTF_8);
|
||||
logger.debug("redirect url : {}", url);
|
||||
WebUtil.redirect(response, request, "/#/login?redirect=" + url);
|
||||
} catch (IOException e) {
|
||||
|
@@ -33,7 +33,6 @@ import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
|
||||
import org.apache.rocketmq.client.consumer.PullResult;
|
||||
import org.apache.rocketmq.client.consumer.PullStatus;
|
||||
import org.apache.rocketmq.client.exception.MQClientException;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
import org.apache.rocketmq.common.Pair;
|
||||
import org.apache.rocketmq.common.message.MessageClientIDSetter;
|
||||
import org.apache.rocketmq.common.message.MessageExt;
|
||||
@@ -74,6 +73,9 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class MessageServiceImpl implements MessageService {
|
||||
|
||||
@Resource
|
||||
private AutoCloseConsumerWrapper autoCloseConsumerWrapper;
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
|
||||
|
||||
private static final Cache<String, List<QueueOffsetInfo>> CACHE = CacheBuilder.newBuilder()
|
||||
@@ -128,8 +130,8 @@ public class MessageServiceImpl implements MessageService {
|
||||
if (isEnableAcl) {
|
||||
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();
|
||||
try {
|
||||
String subExpression = "*";
|
||||
@@ -262,8 +264,8 @@ public class MessageServiceImpl implements MessageService {
|
||||
if (isEnableAcl) {
|
||||
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;
|
||||
List<QueueOffsetInfo> queueOffsetInfos = new ArrayList<>();
|
||||
@@ -402,8 +404,8 @@ public class MessageServiceImpl implements MessageService {
|
||||
if (isEnableAcl) {
|
||||
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<>();
|
||||
|
||||
long offset = query.getPageNum() * query.getPageSize();
|
||||
@@ -541,9 +543,9 @@ public class MessageServiceImpl implements MessageService {
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultMQPullConsumer buildDefaultMQPullConsumer(RPCHook rpcHook, boolean useTLS) {
|
||||
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook);
|
||||
consumer.setUseTLS(useTLS);
|
||||
return consumer;
|
||||
}
|
||||
// public DefaultMQPullConsumer buildDefaultMQPullConsumer(RPCHook rpcHook, boolean useTLS) {
|
||||
// DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook);
|
||||
// consumer.setUseTLS(useTLS);
|
||||
// return consumer;
|
||||
// }
|
||||
}
|
||||
|
@@ -133,21 +133,21 @@ public class MessageTraceServiceImpl implements MessageTraceService {
|
||||
}
|
||||
|
||||
private List<SubscriptionNode> buildSubscriptionNodeList(
|
||||
Map<String, Pair<MessageTraceView, MessageTraceView>> requestIdTracePairMap) {
|
||||
Map<String, Pair<MessageTraceView, MessageTraceView>> requestIdTracePairMap) {
|
||||
Map<String, List<TraceNode>> subscriptionTraceNodeMap = Maps.newHashMap();
|
||||
for (Pair<MessageTraceView, MessageTraceView> traceNodePair : requestIdTracePairMap.values()) {
|
||||
List<TraceNode> traceNodeList = subscriptionTraceNodeMap
|
||||
.computeIfAbsent(buildGroupName(traceNodePair), (o) -> Lists.newArrayList());
|
||||
.computeIfAbsent(buildGroupName(traceNodePair), (o) -> Lists.newArrayList());
|
||||
traceNodeList.add(buildConsumeMessageTraceNode(traceNodePair));
|
||||
}
|
||||
return subscriptionTraceNodeMap.entrySet().stream()
|
||||
.map((Function<Map.Entry<String, List<TraceNode>>, SubscriptionNode>) subscriptionEntry -> {
|
||||
List<TraceNode> traceNodeList = subscriptionEntry.getValue();
|
||||
SubscriptionNode subscriptionNode = new SubscriptionNode();
|
||||
subscriptionNode.setSubscriptionGroup(subscriptionEntry.getKey());
|
||||
subscriptionNode.setConsumeNodeList(sortTraceNodeListByBeginTimestamp(traceNodeList));
|
||||
return subscriptionNode;
|
||||
}).collect(Collectors.toList());
|
||||
.map((Function<Map.Entry<String, List<TraceNode>>, SubscriptionNode>) subscriptionEntry -> {
|
||||
List<TraceNode> traceNodeList = subscriptionEntry.getValue();
|
||||
SubscriptionNode subscriptionNode = new SubscriptionNode();
|
||||
subscriptionNode.setSubscriptionGroup(subscriptionEntry.getKey());
|
||||
subscriptionNode.setConsumeNodeList(sortTraceNodeListByBeginTimestamp(traceNodeList));
|
||||
return subscriptionNode;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
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,
|
||||
Map<String, Pair<MessageTraceView, MessageTraceView>> 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())) {
|
||||
case SubBefore:
|
||||
messageTracePair.setObject1(messageTraceView);
|
||||
|
@@ -81,8 +81,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
private void writeDataJsonToFile(String path, String dataStr) {
|
||||
try {
|
||||
MixAll.string2File(dataStr, path);
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -33,8 +33,7 @@ public class ProducerServiceImpl implements ProducerService {
|
||||
public ProducerConnection getProducerConnection(String producerGroup, String topic) {
|
||||
try {
|
||||
return mqAdminExt.examineProducerConnectionInfo(producerGroup, topic);
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -127,7 +127,7 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
|
||||
try {
|
||||
TopicConfigSerializeWrapper topicConfigSerializeWrapper = mqAdminExt.getAllTopicConfig(brokerAddr.getBrokerAddrs().get(0L), 10000L);
|
||||
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())) {
|
||||
continue;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
|
||||
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();
|
||||
topicType.setTopicName(topicName);
|
||||
|
||||
|
@@ -17,29 +17,26 @@
|
||||
|
||||
package org.apache.rocketmq.dashboard.service.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.rocketmq.auth.authentication.enums.UserType;
|
||||
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.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.tools.admin.MQAdminExt;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private RMQConfigure configure;
|
||||
|
||||
@Autowired
|
||||
private UserInfoProvider userInfoProvider;
|
||||
private UserContext userContext;
|
||||
|
||||
@Autowired
|
||||
private UserMQAdminPoolManager userMQAdminPoolManager;
|
||||
@@ -47,7 +44,7 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
@Override
|
||||
public User queryByName(String name) {
|
||||
UserInfo userInfo = userInfoProvider.getUserInfoByUsername(name);
|
||||
UserInfo userInfo = userContext.queryByUsername(name);
|
||||
if (userInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -15,11 +15,10 @@
|
||||
* 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 MQAdminExtCallback<T> {
|
||||
T doInMQAdminExt(MQAdminExt mqAdminExt) throws Exception;
|
||||
public interface UserStrategy {
|
||||
UserInfo getUserInfoByUsername(String username);
|
||||
}
|
@@ -23,6 +23,7 @@ import org.apache.rocketmq.common.MixAll;
|
||||
import org.apache.rocketmq.remoting.RPCHook;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -32,9 +33,10 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Component
|
||||
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 final AtomicBoolean isTaskScheduled = new AtomicBoolean(false);
|
||||
@@ -77,7 +79,10 @@ public class AutoCloseConsumerWrapper {
|
||||
|
||||
protected DefaultMQPullConsumer createNewConsumer(RPCHook rpcHook, Boolean useTLS) {
|
||||
return new DefaultMQPullConsumer(MixAll.TOOLS_CONSUMER_GROUP, rpcHook) {
|
||||
{ setUseTLS(useTLS); } };
|
||||
{
|
||||
setUseTLS(useTLS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void startIdleCheckTask() {
|
||||
|
@@ -37,8 +37,7 @@ public class GlobalExceptionHandler {
|
||||
if (ex instanceof ServiceException) {
|
||||
logger.error("Occur service exception: {}", ex.getMessage());
|
||||
value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
logger.error("op=global_exception_handler_print_error", ex);
|
||||
value = new JsonResult<Object>(-1, ex.getMessage() == null ? ex.toString() : ex.getMessage());
|
||||
}
|
||||
|
@@ -37,18 +37,17 @@ public class GlobalRestfulResponseBodyAdvice implements ResponseBodyAdvice<Objec
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(
|
||||
Object obj, MethodParameter methodParameter, MediaType mediaType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType,
|
||||
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
|
||||
Object obj, MethodParameter methodParameter, MediaType mediaType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType,
|
||||
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
|
||||
Annotation originalControllerReturnValue = methodParameter.getMethodAnnotation(OriginalControllerReturnValue.class);
|
||||
if (originalControllerReturnValue != null) {
|
||||
return obj;
|
||||
}
|
||||
JsonResult value;
|
||||
if (obj instanceof JsonResult) {
|
||||
value = (JsonResult)obj;
|
||||
}
|
||||
else {
|
||||
value = (JsonResult) obj;
|
||||
} else {
|
||||
value = new JsonResult(obj);
|
||||
}
|
||||
return value;
|
||||
|
@@ -44,7 +44,7 @@ public class CollectTaskRunnble implements Runnable {
|
||||
private DashboardCollectService dashboardCollectService;
|
||||
|
||||
public CollectTaskRunnble(String topic, MQAdminExt mqAdminExt,
|
||||
DashboardCollectService dashboardCollectService) {
|
||||
DashboardCollectService dashboardCollectService) {
|
||||
this.topic = topic;
|
||||
this.mqAdminExt = mqAdminExt;
|
||||
this.dashboardCollectService = dashboardCollectService;
|
||||
|
@@ -80,15 +80,14 @@ public class DashboardCollectTask {
|
||||
this.addSystemTopic();
|
||||
for (String topic : topicSet) {
|
||||
if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)
|
||||
|| topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)
|
||||
|| TopicValidator.isSystemTopic(topic)) {
|
||||
|| topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)
|
||||
|| TopicValidator.isSystemTopic(topic)) {
|
||||
continue;
|
||||
}
|
||||
CollectTaskRunnble collectTask = new CollectTaskRunnble(topic, mqAdminExt, dashboardCollectService);
|
||||
collectExecutor.submit(collectTask);
|
||||
}
|
||||
}
|
||||
catch (Exception err) {
|
||||
} catch (Exception err) {
|
||||
Throwables.throwIfUnchecked(err);
|
||||
throw new RuntimeException(err);
|
||||
}
|
||||
@@ -100,7 +99,6 @@ public class DashboardCollectTask {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Scheduled(cron = "0 0/1 * * * ?")
|
||||
public void collectBroker() {
|
||||
if (!rmqConfigure.isEnableDashBoardCollect()) {
|
||||
@@ -139,8 +137,7 @@ public class DashboardCollectTask {
|
||||
dashboardCollectService.getBrokerMap().put(entry.getValue(), list);
|
||||
}
|
||||
log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -152,12 +149,10 @@ public class DashboardCollectTask {
|
||||
}
|
||||
try {
|
||||
return mqAdminExt.fetchBrokerRuntimeStats(brokerAddr);
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
catch (InterruptedException e1) {
|
||||
} catch (InterruptedException e1) {
|
||||
Throwables.throwIfUnchecked(e1);
|
||||
throw new RuntimeException(e1);
|
||||
}
|
||||
@@ -189,16 +184,14 @@ public class DashboardCollectTask {
|
||||
Map<String, List<String>> topicFileMap;
|
||||
if (brokerFile.exists()) {
|
||||
brokerFileMap = dashboardCollectService.jsonDataFile2map(brokerFile);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
brokerFileMap = Maps.newHashMap();
|
||||
Files.createParentDirs(brokerFile);
|
||||
}
|
||||
|
||||
if (topicFile.exists()) {
|
||||
topicFileMap = dashboardCollectService.jsonDataFile2map(topicFile);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
topicFileMap = Maps.newHashMap();
|
||||
Files.createParentDirs(topicFile);
|
||||
}
|
||||
@@ -211,21 +204,19 @@ public class DashboardCollectTask {
|
||||
log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
|
||||
log.debug("Topic Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getTopicMap().asMap()));
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
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>> resultMap = Maps.newHashMap();
|
||||
if (fileMap.size() == 0) {
|
||||
resultMap = newMap;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (Map.Entry<String, List<String>> entry : fileMap.entrySet()) {
|
||||
List<String> oldList = entry.getValue();
|
||||
List<String> newList = newMap.get(entry.getKey());
|
||||
|
@@ -38,7 +38,7 @@ public class MonitorTask {
|
||||
@Resource
|
||||
private ConsumerService consumerService;
|
||||
|
||||
// @Scheduled(cron = "* * * * * ?")
|
||||
// @Scheduled(cron = "* * * * * ?")
|
||||
public void scanProblemConsumeGroup() {
|
||||
for (Map.Entry<String, ConsumerMonitorConfig> configEntry : monitorService.queryConsumerMonitorConfig().entrySet()) {
|
||||
GroupConsumeInfo consumeInfo = consumerService.queryGroup(configEntry.getKey(), null);
|
||||
|
@@ -31,10 +31,10 @@ import java.util.List;
|
||||
public class ExcelUtil {
|
||||
|
||||
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();
|
||||
WriteFont writeFont = new WriteFont();
|
||||
writeFont.setFontHeightInPoints((short)12);
|
||||
writeFont.setFontHeightInPoints((short) 12);
|
||||
writeFont.setFontName("Microsoft YaHei UI");
|
||||
headWriteCellStyle.setWriteFont(writeFont);
|
||||
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
||||
@@ -44,7 +44,7 @@ public class ExcelUtil {
|
||||
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
||||
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
|
||||
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 {
|
||||
|
@@ -55,8 +55,7 @@ public class JsonUtil {
|
||||
public static void writeValue(Writer writer, Object obj) {
|
||||
try {
|
||||
objectMapper.writeValue(writer, obj);
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
Throwables.propagateIfPossible(e);
|
||||
}
|
||||
}
|
||||
@@ -67,9 +66,8 @@ public class JsonUtil {
|
||||
}
|
||||
|
||||
try {
|
||||
return src instanceof String ? (String)src : objectMapper.writeValueAsString(src);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return src instanceof String ? (String) src : objectMapper.writeValueAsString(src);
|
||||
} catch (Exception e) {
|
||||
logger.error("Parse Object to String error src=" + src, e);
|
||||
return null;
|
||||
}
|
||||
@@ -81,9 +79,8 @@ public class JsonUtil {
|
||||
}
|
||||
|
||||
try {
|
||||
return src instanceof byte[] ? (byte[])src : objectMapper.writeValueAsBytes(src);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return src instanceof byte[] ? (byte[]) src : objectMapper.writeValueAsBytes(src);
|
||||
} catch (Exception e) {
|
||||
logger.error("Parse Object to byte[] error", e);
|
||||
return null;
|
||||
}
|
||||
@@ -95,9 +92,8 @@ public class JsonUtil {
|
||||
}
|
||||
str = escapesSpecialChar(str);
|
||||
try {
|
||||
return clazz.equals(String.class) ? (T)str : objectMapper.readValue(str, clazz);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
|
||||
} catch (Exception e) {
|
||||
logger.error("Parse String to Object error\nString: {}\nClass<T>: {}\nError: {}", str, clazz.getName(), e);
|
||||
return null;
|
||||
}
|
||||
@@ -108,9 +104,8 @@ public class JsonUtil {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return clazz.equals(byte[].class) ? (T)bytes : objectMapper.readValue(bytes, clazz);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return clazz.equals(byte[].class) ? (T) bytes : objectMapper.readValue(bytes, clazz);
|
||||
} catch (Exception e) {
|
||||
logger.error("Parse byte[] to Object error\nbyte[]: {}\nClass<T>: {}\nError: {}", bytes, clazz.getName(), e);
|
||||
return null;
|
||||
}
|
||||
@@ -122,11 +117,10 @@ public class JsonUtil {
|
||||
}
|
||||
str = escapesSpecialChar(str);
|
||||
try {
|
||||
return (T)(typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
|
||||
} catch (Exception e) {
|
||||
logger.error("Parse String to Object error\nString: {}\nTypeReference<T>: {}\nError: {}", str,
|
||||
typeReference.getType(), e);
|
||||
typeReference.getType(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -136,12 +130,11 @@ public class JsonUtil {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (T)(typeReference.getType().equals(byte[].class) ? bytes : objectMapper.readValue(bytes,
|
||||
typeReference));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return (T) (typeReference.getType().equals(byte[].class) ? bytes : objectMapper.readValue(bytes,
|
||||
typeReference));
|
||||
} catch (Exception e) {
|
||||
logger.error("Parse byte[] to Object error\nbyte[]: {}\nTypeReference<T>: {}\nError: {}", bytes,
|
||||
typeReference.getType(), e);
|
||||
typeReference.getType(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -107,7 +107,7 @@ public class WebUtil {
|
||||
HttpSession session = request.getSession(false);
|
||||
|
||||
if (session != null) {
|
||||
return session.getAttribute(key);
|
||||
return session.getAttribute(key);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@@ -58,6 +58,12 @@ rocketmq:
|
||||
ticketKey: ticket
|
||||
# must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
|
||||
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
|
||||
proxyAddr: 127.0.0.1:8080
|
||||
proxyAddrs:
|
||||
|
@@ -17,34 +17,34 @@
|
||||
-->
|
||||
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<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>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder charset="UTF-8">
|
||||
<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>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
<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>
|
||||
<charset class="java.nio.charset.Charset">UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
</configuration>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
@@ -14,13 +14,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# 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)
|
||||
|
||||
# Define Admin
|
||||
admin=admin,1
|
||||
|
||||
super=admin,1
|
||||
# Define Users
|
||||
user1=user1
|
||||
user2=user2
|
||||
user1=user
|
||||
user2=user
|
||||
|
@@ -17,12 +17,20 @@
|
||||
|
||||
package org.apache.rocketmq.dashboard;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
|
||||
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
|
||||
import org.mockito.internal.util.MockUtil;
|
||||
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 {
|
||||
/**
|
||||
* Inject the corresponding mock class automatically
|
||||
@@ -43,7 +51,7 @@ public class BaseTest {
|
||||
for (Field localField : localFields) {
|
||||
for (Field field : fields) {
|
||||
if (field.getName()
|
||||
.equals(localField.getName())) {
|
||||
.equals(localField.getName())) {
|
||||
Object obj = ReflectionUtils.getField(field, target);
|
||||
if (obj == null) {
|
||||
Object destObj = ReflectionUtils.getField(localField, localDest);
|
||||
@@ -69,4 +77,20 @@ public class BaseTest {
|
||||
ReflectionUtils.doWithFields(leafClass, fc);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -17,26 +17,45 @@
|
||||
|
||||
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.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.MQAdminExt;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.junit.Before;
|
||||
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.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MQAdminAspectTest {
|
||||
|
||||
@Mock
|
||||
private RMQConfigure rmqConfigure;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(rmqConfigure.isLoginRequired()).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAroundMQAdminMethod() throws Throwable {
|
||||
MQAdminAspect mqAdminAspect = new MQAdminAspect();
|
||||
Field field = mqAdminAspect.getClass().getDeclaredField("rmqConfigure");
|
||||
field.setAccessible(true);
|
||||
field.set(mqAdminAspect, rmqConfigure);
|
||||
ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
|
||||
MethodSignature signature = mock(MethodSignature.class);
|
||||
Method method = mock(Method.class);
|
||||
@@ -44,16 +63,39 @@ public class MQAdminAspectTest {
|
||||
when(joinPoint.getSignature()).thenReturn(signature);
|
||||
|
||||
GenericObjectPool<MQAdminExt> mqAdminExtPool = mock(GenericObjectPool.class);
|
||||
// 1. Mock borrowObject() 行为:第一次抛异常,第二次返回 DefaultMQAdminExt
|
||||
when(mqAdminExtPool.borrowObject())
|
||||
.thenThrow(new RuntimeException("borrowObject exception"))
|
||||
.thenReturn(new DefaultMQAdminExt());
|
||||
doNothing().doThrow(new RuntimeException("returnObject exception"))
|
||||
.when(mqAdminExtPool).returnObject(any());
|
||||
Field field = mqAdminAspect.getClass().getDeclaredField("mqAdminExtPool");
|
||||
.thenThrow(new RuntimeException("borrowObject exception"))
|
||||
.thenReturn(new DefaultMQAdminExt());
|
||||
|
||||
// 2. Mock returnObject() 行为:第一次什么都不做,第二次抛异常
|
||||
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.set(mqAdminAspect, mqAdminExtPool);
|
||||
// exception
|
||||
mqAdminAspect.aroundMQAdminMethod(joinPoint);
|
||||
mqAdminAspect.aroundMQAdminMethod(joinPoint);
|
||||
|
||||
// 4. 第一次调用 aroundMQAdminMethod,预期 borrowObject() 抛异常
|
||||
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
Reference in New Issue
Block a user