mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-10 19:48:29 +08:00
Compare commits
11 Commits
a4e02f472f
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
ce8306a602 | ||
|
8037cfcf05 | ||
|
bd9f3e6b39 | ||
|
a013c8fad1 | ||
|
cd262da8b1 | ||
|
37dbd7f327 | ||
|
79556420f5 | ||
|
9cb185afc1 | ||
|
f60103af9b | ||
|
9c2a069976 | ||
|
8cc7d6a727 |
2
NOTICE
2
NOTICE
@@ -1,5 +1,5 @@
|
||||
Apache RocketMQ
|
||||
Copyright 2016-2022 The Apache Software Foundation
|
||||
Copyright 2016-2025 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
1
frontend-new/.env.development
Normal file
1
frontend-new/.env.development
Normal file
@@ -0,0 +1 @@
|
||||
REACT_APP_API_BASE_URL=http://localhost:8082
|
1
frontend-new/.env.production
Normal file
1
frontend-new/.env.production
Normal file
@@ -0,0 +1 @@
|
||||
REACT_APP_API_BASE_URL=
|
@@ -9,7 +9,7 @@ In the project directory, you can run:
|
||||
### `npm run start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3003](http://localhost:3000) to view it in your browser.
|
||||
Open [http://localhost:3003](http://localhost:3003) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
34
frontend-new/package-lock.json
generated
34
frontend-new/package-lock.json
generated
@@ -19,11 +19,11 @@
|
||||
"echarts": "^5.6.0",
|
||||
"framer-motion": "^12.16.0",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"i18next": "^25.1.3",
|
||||
"i18next": "^23.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-i18next": "14.1.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-scripts": "5.0.1",
|
||||
@@ -8918,9 +8918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-25.1.3.tgz",
|
||||
"integrity": "sha512-VY1iKox3YWPRTNMHFdgk5TV+Jq6rNKexLCLpPmP5oXXJ5Kl7yDBi3ycZ5fyEKZ1tNBW5gOqD4WV0XqE7rl3JUg==",
|
||||
"version": "23.16.8",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-23.16.8.tgz",
|
||||
"integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -8936,15 +8936,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
@@ -14000,17 +13992,16 @@
|
||||
"integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ=="
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-15.5.1.tgz",
|
||||
"integrity": "sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==",
|
||||
"version": "14.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-14.1.3.tgz",
|
||||
"integrity": "sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
@@ -14018,9 +14009,6 @@
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -14,11 +14,11 @@
|
||||
"echarts": "^5.6.0",
|
||||
"framer-motion": "^12.16.0",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"i18next": "^25.1.3",
|
||||
"i18next": "^23.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-i18next": "14.1.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-scripts": "5.0.1",
|
||||
|
@@ -14,8 +14,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const appConfig = {
|
||||
apiBaseUrl: 'http://localhost:8082'
|
||||
apiBaseUrl: process.env.REACT_APP_API_BASE_URL || window.location.origin
|
||||
};
|
||||
|
||||
let _redirectHandler = null;
|
||||
@@ -33,29 +34,73 @@ const remoteApi = {
|
||||
return `${appConfig.apiBaseUrl}/${endpoint}`;
|
||||
},
|
||||
|
||||
_fetch: async (url, options) => {
|
||||
|
||||
async getCsrfToken() {
|
||||
const csrfToken = this.getCookie();
|
||||
|
||||
if (csrfToken) {
|
||||
return csrfToken;
|
||||
}
|
||||
|
||||
const response = await fetch(remoteApi.buildUrl("/rocketmq-dashboard/csrf-token"), {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const newCsrfToken = this.getCookie();
|
||||
if (!newCsrfToken) {
|
||||
console.error("Failed to get CSRF Token");
|
||||
throw new Error("CSRF Token not available");
|
||||
}
|
||||
return newCsrfToken;
|
||||
},
|
||||
|
||||
getCookie() {
|
||||
return document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1')
|
||||
},
|
||||
|
||||
_fetch: async (url, options = {}) => {
|
||||
const headers = {
|
||||
...options.headers,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
|
||||
const csrfToken = await remoteApi.getCsrfToken();
|
||||
console.log(csrfToken)
|
||||
if (!csrfToken) {
|
||||
console.warn('CSRF Token not found');
|
||||
}else{
|
||||
headers["X-XSRF-TOKEN"] = csrfToken;
|
||||
}
|
||||
console.log(csrfToken)
|
||||
|
||||
|
||||
try {
|
||||
// 在 options 中添加 credentials: 'include'
|
||||
const response = await fetch(url, {
|
||||
...options, // 保留原有的 options
|
||||
credentials: 'include' // 关键改动:允许发送 Cookie
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
|
||||
// 检查响应是否被重定向,并且最终的 URL 包含了登录页的路径。
|
||||
// 这是会话过期或需要认证时后端重定向到登录页的常见模式。
|
||||
// 注意:fetch 会自动跟随 GET 请求的 3xx 重定向,所以我们检查的是 response.redirected。
|
||||
if (response.redirected) {
|
||||
if (_redirectHandler) {
|
||||
_redirectHandler(); // 如果设置了重定向处理函数,则调用它
|
||||
_redirectHandler();
|
||||
}
|
||||
return {__isRedirectHandled: true};
|
||||
}
|
||||
|
||||
if(response.status == 403){
|
||||
window.localStorage.removeItem("csrfToken");
|
||||
console.log(111)
|
||||
await remoteApi.getCsrfToken()
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Fetch 请求出错:", error);
|
||||
throw error;
|
||||
console.error('fetch error:', error);
|
||||
window.localStorage.removeItem("csrfToken");
|
||||
console.log(111)
|
||||
await remoteApi.getCsrfToken()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -232,24 +277,19 @@ const remoteApi = {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 假设服务器总是返回 JSON
|
||||
const data = await response.json();
|
||||
|
||||
// 1. 打开一个新的空白窗口
|
||||
const newWindow = window.open('', '_blank');
|
||||
|
||||
if (!newWindow) {
|
||||
// 浏览器可能会阻止弹窗,需要用户允许
|
||||
return {status: 1, errMsg: "Failed to open new window. Please allow pop-ups for this site."};
|
||||
}
|
||||
|
||||
// 2. 将 JSON 数据格式化后写入新窗口
|
||||
newWindow.document.write('<html><head><title>DLQ 导出内容</title></head><body>');
|
||||
newWindow.document.write('<h1>DLQ 导出 JSON 内容</h1>');
|
||||
// 使用 <pre> 标签保持格式,并使用 JSON.stringify 格式化 JSON 以便于阅读
|
||||
newWindow.document.write('<pre>' + JSON.stringify(data, null, 2) + '</pre>');
|
||||
newWindow.document.write('</body></html>');
|
||||
newWindow.document.close(); // 关闭文档流,确保内容显示
|
||||
newWindow.document.close();
|
||||
|
||||
return {status: 0, msg: "导出请求成功,内容已在新页面显示"};
|
||||
} catch (error) {
|
||||
@@ -316,7 +356,7 @@ const remoteApi = {
|
||||
*/
|
||||
queryMessageByTopicAndKey: async (topic, key) => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/message/queryMessageByTopicAndKey.query?topic=${topic}&key=${key}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/message/queryMessageByTopicAndKey.query?topic=${encodeURIComponent(topic)}&key=${key}`));
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
@@ -355,6 +395,7 @@ const remoteApi = {
|
||||
*/
|
||||
resendMessageDirectly: async (msgId, consumerGroup, topic) => {
|
||||
topic = encodeURIComponent(topic)
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/message/consumeMessageDirectly.do?msgId=${msgId}&consumerGroup=${consumerGroup}&topic=${topic}`), {
|
||||
method: 'POST',
|
||||
@@ -381,6 +422,9 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
queryConsumerGroupList: async (skipSysGroup, address) => {
|
||||
if (address === undefined) {
|
||||
address = ""
|
||||
}
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/groupList.query?skipSysGroup=${skipSysGroup}&address=${address}`));
|
||||
const data = await response.json();
|
||||
@@ -392,6 +436,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
refreshConsumerGroup: async (consumerGroup) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/group.refresh?consumerGroup=${consumerGroup}`));
|
||||
const data = await response.json();
|
||||
@@ -402,9 +447,12 @@ const remoteApi = {
|
||||
}
|
||||
},
|
||||
|
||||
refreshAllConsumerGroup: async () => {
|
||||
refreshAllConsumerGroup: async (address) => {
|
||||
if (address === undefined) {
|
||||
address = ""
|
||||
}
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/group.refresh.all"));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/group.refresh.all?address=${address}`));
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
@@ -443,6 +491,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
fetchBrokerNameList: async (consumerGroup) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/fetchBrokerNameList.query?consumerGroup=${consumerGroup}`));
|
||||
const data = await response.json();
|
||||
@@ -454,6 +503,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
deleteConsumerGroup: async (groupName, brokerNameList) => {
|
||||
groupName = encodeURIComponent(groupName)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/deleteSubGroup.do"), {
|
||||
method: 'POST',
|
||||
@@ -471,6 +521,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
queryConsumerConfig: async (consumerGroup) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/examineSubscriptionGroupConfig.query?consumerGroup=${consumerGroup}`));
|
||||
const data = await response.json();
|
||||
@@ -499,6 +550,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
queryTopicByConsumer: async (consumerGroup, address) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/queryTopicByConsumer.query?consumerGroup=${consumerGroup}&address=${address}`));
|
||||
const data = await response.json();
|
||||
@@ -510,6 +562,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
queryConsumerConnection: async (consumerGroup, address) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/consumerConnection.query?consumerGroup=${consumerGroup}&address=${address}`));
|
||||
const data = await response.json();
|
||||
@@ -521,6 +574,7 @@ const remoteApi = {
|
||||
},
|
||||
|
||||
queryConsumerRunningInfo: async (consumerGroup, clientId, jstack = false) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/consumerRunningInfo.query?consumerGroup=${consumerGroup}&clientId=${clientId}&jstack=${jstack}`));
|
||||
const data = await response.json();
|
||||
@@ -559,7 +613,7 @@ const remoteApi = {
|
||||
|
||||
getTopicStats: async (topic) => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/stats.query?topic=${topic}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/stats.query?topic=${encodeURIComponent(topic)}`));
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching topic stats:", error);
|
||||
@@ -569,7 +623,7 @@ const remoteApi = {
|
||||
|
||||
getTopicRoute: async (topic) => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/route.query?topic=${topic}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/route.query?topic=${encodeURIComponent(topic)}`));
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching topic route:", error);
|
||||
@@ -579,7 +633,7 @@ const remoteApi = {
|
||||
|
||||
getTopicConsumers: async (topic) => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/queryConsumerByTopic.query?topic=${topic}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/queryConsumerByTopic.query?topic=${encodeURIComponent(topic)}`));
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching topic consumers:", error);
|
||||
@@ -589,7 +643,7 @@ const remoteApi = {
|
||||
|
||||
getTopicConsumerGroups: async (topic) => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/queryTopicConsumerInfo.query?topic=${topic}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/queryTopicConsumerInfo.query?topic=${encodeURIComponent(topic)}`));
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching consumer groups:", error);
|
||||
@@ -599,7 +653,7 @@ const remoteApi = {
|
||||
|
||||
getTopicConfig: async (topic) => {
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/examineTopicConfig.query?topic=${topic}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/examineTopicConfig.query?topic=${encodeURIComponent(topic)}`));
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching topic config:", error);
|
||||
@@ -867,21 +921,17 @@ const remoteApi = {
|
||||
login: async (username, password) => {
|
||||
try {
|
||||
|
||||
|
||||
// 2. 发送请求,注意 body 可以是空字符串或 null,或者直接省略 body
|
||||
// 这里使用 GET 方法,因为参数在 URL 上
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/login/login.do"), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded' // 这个 header 可能不再需要,或者需要调整
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
username: username, // 假设 username 是变量名
|
||||
password: password // 假设 password 是变量名
|
||||
}).toString()
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 处理响应
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
@@ -904,21 +954,18 @@ const remoteApi = {
|
||||
};
|
||||
|
||||
const tools = {
|
||||
// 适配新的数据结构
|
||||
dashboardRefreshTime: 5000,
|
||||
generateBrokerMap: (brokerServer, clusterAddrTable, brokerAddrTable) => {
|
||||
const clusterMap = {}; // 最终存储 { clusterName: [brokerInstance1, brokerInstance2, ...] }
|
||||
const clusterMap = {};
|
||||
|
||||
Object.entries(clusterAddrTable).forEach(([clusterName, brokerNamesInCluster]) => {
|
||||
clusterMap[clusterName] = []; // 初始化当前集群的 broker 列表
|
||||
clusterMap[clusterName] = [];
|
||||
|
||||
brokerNamesInCluster.forEach(brokerName => {
|
||||
// 从 brokerAddrTable 获取当前 brokerName 下的所有 brokerId 及其地址
|
||||
const brokerAddrs = brokerAddrTable[brokerName]?.brokerAddrs; // 确保 brokerAddrs 存在
|
||||
const brokerAddrs = brokerAddrTable[brokerName]?.brokerAddrs;
|
||||
if (brokerAddrs) {
|
||||
Object.entries(brokerAddrs).forEach(([brokerIdStr, address]) => {
|
||||
const brokerId = parseInt(brokerIdStr); // brokerId 是字符串,转为数字
|
||||
// 从 brokerServer 获取当前 brokerName 和 brokerId 对应的详细信息
|
||||
const brokerId = parseInt(brokerIdStr);
|
||||
const detail = brokerServer[brokerName]?.[brokerIdStr];
|
||||
|
||||
if (detail) {
|
||||
|
@@ -21,7 +21,7 @@ import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
|
||||
const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
||||
const ClientInfoModal = ({visible, group, address, onCancel, messageApi}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [connectionData, setConnectionData] = useState(null);
|
||||
@@ -34,7 +34,12 @@ const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
||||
try {
|
||||
const connResponse = await remoteApi.queryConsumerConnection(group, address);
|
||||
|
||||
if (connResponse.status === 0) setConnectionData(connResponse.data);
|
||||
if (connResponse.status === 0) {
|
||||
setConnectionData(connResponse.data);
|
||||
}else{
|
||||
messageApi.error(connResponse.errMsg);
|
||||
setConnectionData(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import {Modal, Spin, Table} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const ConsumerDetailModal = ({visible, group, address, onCancel}) => {
|
||||
const ConsumerDetailModal = ({visible, group, address, onCancel ,messageApi}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
@@ -34,6 +34,10 @@ const ConsumerDetailModal = ({visible, group, address, onCancel}) => {
|
||||
const response = await remoteApi.queryTopicByConsumer(group, address);
|
||||
if (response.status === 0) {
|
||||
setDetails(response.data);
|
||||
}else {
|
||||
// Handle error case
|
||||
messageApi.error(response.errMsg);
|
||||
setDetails([]);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
@@ -18,15 +18,12 @@
|
||||
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, t}) => {
|
||||
const [brokerList, setBrokerList] = useState([]);
|
||||
const [selectedBrokers, setSelectedBrokers] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取Broker列表
|
||||
useEffect(() => {
|
||||
const fetchBrokers = async () => {
|
||||
if (!visible) return;
|
||||
@@ -45,7 +42,6 @@ const DeleteConsumerModal = ({visible, group, onCancel, onSuccess}) => {
|
||||
fetchBrokers();
|
||||
}, [visible, group]);
|
||||
|
||||
// 处理删除提交
|
||||
const handleDelete = async () => {
|
||||
if (selectedBrokers.length === 0) {
|
||||
notification.warning({message: t.PLEASE_SELECT_BROKER});
|
||||
|
@@ -592,9 +592,10 @@ export const translations = {
|
||||
"EXPRESSION_TYPE": "Expression Type",
|
||||
"SUB_VERSION": "Sub Version",
|
||||
"CODE_SET": "Code Set",
|
||||
"TAGS_SET": "Tags Set"
|
||||
|
||||
|
||||
"TAGS_SET": "Tags Set",
|
||||
"DELETE_CONSUMER_GROUP": "Delete Consumer Group",
|
||||
"SELECT_DELETE_BROKERS": "Please select brokers to delete consumer group",
|
||||
"CONFIRM_DELETE": "Confirm Delete",
|
||||
}
|
||||
|
||||
};
|
||||
|
@@ -270,7 +270,7 @@ const ConsumerGroupList = () => {
|
||||
|
||||
const handleRefreshConsumerData = async () => {
|
||||
setLoading(true);
|
||||
const refreshResult = await remoteApi.refreshAllConsumerGroup();
|
||||
const refreshResult = await remoteApi.refreshAllConsumerGroup(selectedProxy);
|
||||
setLoading(false);
|
||||
|
||||
if (refreshResult && refreshResult.status === 0) {
|
||||
@@ -288,7 +288,6 @@ const ConsumerGroupList = () => {
|
||||
setShowConfig(true);
|
||||
};
|
||||
|
||||
// 修改操作按钮的点击处理函数
|
||||
const handleClient = (group, address) => {
|
||||
setSelectedGroup(group);
|
||||
setSelectedAddress(address);
|
||||
@@ -550,12 +549,12 @@ const ConsumerGroupList = () => {
|
||||
/>
|
||||
</Spin>
|
||||
|
||||
{/* 模态框组件保持不变 */}
|
||||
<ClientInfoModal
|
||||
visible={showClientInfo}
|
||||
group={selectedGroup}
|
||||
address={selectedAddress}
|
||||
onCancel={() => setShowClientInfo(false)}
|
||||
messageApi={messageApi}
|
||||
/>
|
||||
|
||||
<ConsumerDetailModal
|
||||
@@ -563,6 +562,7 @@ const ConsumerGroupList = () => {
|
||||
group={selectedGroup}
|
||||
address={selectedAddress}
|
||||
onCancel={() => setShowConsumeDetail(false)}
|
||||
messageApi={messageApi}
|
||||
/>
|
||||
|
||||
<ConsumerConfigModal
|
||||
@@ -579,6 +579,7 @@ const ConsumerGroupList = () => {
|
||||
group={selectedGroup}
|
||||
onCancel={() => setShowDeleteModal(false)}
|
||||
onSuccess={loadConsumerGroups}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@@ -20,7 +20,6 @@ import {defaultTheme, themes} from '../../assets/styles/theme';
|
||||
import {setTheme} from '../actions/themeActions';
|
||||
|
||||
export const useTheme = () => {
|
||||
// 从 Redux store 中取出 currentThemeName
|
||||
const currentThemeName = useSelector(state => state.theme.currentThemeName);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
@@ -28,7 +28,6 @@ const initialState = {
|
||||
const themeReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case SET_THEME:
|
||||
// 注意:reducer 应该返回新的状态对象,而不是直接修改旧状态
|
||||
return {
|
||||
...state,
|
||||
currentThemeName: action.payload,
|
||||
|
15
pom.xml
15
pom.xml
@@ -16,8 +16,7 @@
|
||||
~ 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>
|
||||
@@ -29,7 +28,7 @@
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-dashboard</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<version>2.1.1-SNAPSHOT</version>
|
||||
<name>rocketmq-dashboard</name>
|
||||
|
||||
<scm>
|
||||
@@ -150,6 +149,11 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
@@ -250,6 +254,7 @@
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
@@ -448,7 +453,7 @@
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>install --legacy-peer-deps</arguments>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
@@ -470,7 +475,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>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
FROM java:8
|
||||
FROM eclipse-temurin:17.0.16_8-jre-ubi9-minimal
|
||||
VOLUME /tmp
|
||||
ADD rocketmq-dashboard-*.jar rocketmq-dashboard.jar
|
||||
RUN sh -c 'touch /rocketmq-dashboard.jar'
|
||||
|
@@ -80,7 +80,7 @@ public class MQAdminAspect {
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
|
||||
try {
|
||||
if (isPoolConfigIsolatedByUser(rmqConfigure.isLoginRequired(), methodName)) {
|
||||
if (isPoolConfigIsolatedByUser(rmqConfigure.isLoginRequired(), rmqConfigure.getAuthMode(), methodName)) {
|
||||
currentUserInfo = (UserInfo) UserInfoContext.get(WebUtil.USER_NAME);
|
||||
// 2. Borrow the user-specific MQAdminExt instance.
|
||||
// currentUser.getName() is assumed to be the AccessKey, and currentUser.getPassword() is SecretKey.
|
||||
@@ -123,8 +123,8 @@ public class MQAdminAspect {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPoolConfigIsolatedByUser(boolean loginRequired, String methodName) {
|
||||
if (!loginRequired) {
|
||||
private boolean isPoolConfigIsolatedByUser(boolean loginRequired, String authMode, String methodName) {
|
||||
if (!loginRequired || authMode.equals("file")) {
|
||||
return false;
|
||||
} else {
|
||||
return !METHODS_TO_CHECK.contains(methodName);
|
||||
|
@@ -31,7 +31,6 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.multipart.support.MissingServletRequestPartException;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
@@ -90,17 +89,6 @@ public class AuthWebMVCConfigurerAdapter implements WebMvcConfigurer {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("http://localhost:3003")
|
||||
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.maxAge(3600)
|
||||
.allowCredentials(true)
|
||||
.allowedHeaders("content-type", "Authorization", "X-Requested-With", "Origin", "Accept")
|
||||
.exposedHeaders("authorization");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(withDefaults())
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers("/actuator/**")
|
||||
.ignoringRequestMatchers("/rocketmq-dashboard/csrf-token")
|
||||
.csrfTokenRepository(csrfTokenRepository())
|
||||
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
|
||||
)
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/actuator/**").hasRole("ADMIN")
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
.httpBasic(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3003"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(Arrays.asList("content-type", "Authorization", "X-Requested-With", "Origin", "Accept", "X-XSRF-TOKEN"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
@Bean
|
||||
public CsrfTokenRepository csrfTokenRepository() {
|
||||
return CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/rocketmq-dashboard")
|
||||
public class CsrfTokenController {
|
||||
|
||||
@Autowired
|
||||
private CsrfTokenRepository csrfTokenRepository;
|
||||
|
||||
@RequestMapping(value = "/csrf-token", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Object getCsrfToken(HttpServletRequest request, HttpServletResponse response) {
|
||||
CsrfToken token = csrfTokenRepository.generateToken(request);
|
||||
csrfTokenRepository.saveToken(token, request, response);
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@@ -65,7 +66,7 @@ public class LoginController {
|
||||
|
||||
@RequestMapping(value = "/login.do", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public Object login(org.apache.rocketmq.remoting.protocol.body.UserInfo userInfoRequest,
|
||||
public Object login(@RequestBody org.apache.rocketmq.remoting.protocol.body.UserInfo userInfoRequest,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
logger.info("user:{} login", userInfoRequest.getUsername());
|
||||
|
@@ -33,6 +33,12 @@ public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
if (request.getRequestURL().toString().contains("/rocketmq-dashboard/csrf-token")) {
|
||||
return true;
|
||||
}
|
||||
return loginService.login(request, response);
|
||||
}
|
||||
|
||||
|
@@ -135,6 +135,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_PERMISSION_GROUP);
|
||||
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_OWNER_GROUP);
|
||||
SYSTEM_GROUP_SET.add(MixAll.CID_SYS_RMQ_TRANS);
|
||||
SYSTEM_GROUP_SET.add("CID_DefaultHeartBeatSyncerTopic");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,7 +148,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
if (cacheConsumeInfoList.isEmpty() && !isCacheBeingBuilt) {
|
||||
isCacheBeingBuilt = true;
|
||||
try {
|
||||
makeGroupListCache();
|
||||
makeGroupListCache(address);
|
||||
} finally {
|
||||
isCacheBeingBuilt = false;
|
||||
}
|
||||
@@ -173,7 +174,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
}
|
||||
|
||||
|
||||
public void makeGroupListCache() {
|
||||
public void makeGroupListCache(String address) {
|
||||
SubscriptionGroupWrapper subscriptionGroupWrapper = null;
|
||||
try {
|
||||
ClusterInfo clusterInfo = clusterInfoService.get();
|
||||
@@ -205,7 +206,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
String consumerGroup = entry.getKey();
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
GroupConsumeInfo consumeInfo = queryGroup(consumerGroup, "");
|
||||
GroupConsumeInfo consumeInfo = queryGroup(consumerGroup, address);
|
||||
consumeInfo.setAddress(entry.getValue());
|
||||
if (SYSTEM_GROUP_SET.contains(consumerGroup)) {
|
||||
consumeInfo.setSubGroupType("SYSTEM");
|
||||
@@ -283,6 +284,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName, String address) {
|
||||
groupName = getConsumerGroup(groupName);
|
||||
List<ConsumeStats> consumeStatses = new ArrayList<>();
|
||||
String topic = null;
|
||||
try {
|
||||
@@ -295,9 +297,10 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
List<TopicConsumerInfo> res = new ArrayList<>();
|
||||
String finalGroupName = groupName;
|
||||
consumeStatses.forEach(consumeStats -> {
|
||||
if (consumeStats != null && consumeStats.getOffsetTable() != null && !consumeStats.getOffsetTable().isEmpty()) {
|
||||
res.addAll(toTopicConsumerInfoList(topic, consumeStats, groupName));
|
||||
res.addAll(toTopicConsumerInfoList(topic, consumeStats, finalGroupName));
|
||||
}
|
||||
});
|
||||
return res;
|
||||
@@ -305,6 +308,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public List<TopicConsumerInfo> queryConsumeStatsList(final String topic, String groupName) {
|
||||
groupName = getConsumerGroup(groupName);
|
||||
ConsumeStats consumeStats = null;
|
||||
try {
|
||||
consumeStats = mqAdminExt.examineConsumeStats(groupName, topic);
|
||||
@@ -316,6 +320,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
}
|
||||
|
||||
private List<TopicConsumerInfo> toTopicConsumerInfoList(String topic, ConsumeStats consumeStats, String groupName) {
|
||||
groupName = getConsumerGroup(groupName);
|
||||
List<MessageQueue> mqList = Lists.newArrayList(Iterables.filter(consumeStats.getOffsetTable().keySet(), new Predicate<MessageQueue>() {
|
||||
@Override
|
||||
public boolean apply(MessageQueue o) {
|
||||
@@ -339,6 +344,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
}
|
||||
|
||||
private Map<MessageQueue, String> getClientConnection(String groupName) {
|
||||
groupName = getConsumerGroup(groupName);
|
||||
Map<MessageQueue, String> results = Maps.newHashMap();
|
||||
try {
|
||||
ConsumerConnection consumerConnection = mqAdminExt.examineConsumerConnectionInfo(groupName);
|
||||
@@ -417,7 +423,8 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConsumerConfigInfo> examineSubscriptionGroupConfig(String group) {
|
||||
public List<ConsumerConfigInfo> examineSubscriptionGroupConfig(String consumerGroup) {
|
||||
consumerGroup = getConsumerGroup(consumerGroup);
|
||||
List<ConsumerConfigInfo> consumerConfigInfoList = Lists.newArrayList();
|
||||
try {
|
||||
ClusterInfo clusterInfo = clusterInfoService.get();
|
||||
@@ -425,9 +432,9 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
String brokerAddress = clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr();
|
||||
SubscriptionGroupConfig subscriptionGroupConfig = null;
|
||||
try {
|
||||
subscriptionGroupConfig = mqAdminExt.examineSubscriptionGroupConfig(brokerAddress, group);
|
||||
subscriptionGroupConfig = mqAdminExt.examineSubscriptionGroupConfig(brokerAddress, consumerGroup);
|
||||
} catch (Exception e) {
|
||||
logger.warn("op=examineSubscriptionGroupConfig_error brokerName={} group={}", brokerName, group);
|
||||
logger.warn("op=examineSubscriptionGroupConfig_error brokerName={} group={}", brokerName, consumerGroup);
|
||||
}
|
||||
if (subscriptionGroupConfig == null) {
|
||||
continue;
|
||||
@@ -480,6 +487,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public boolean createAndUpdateSubscriptionGroupConfig(ConsumerConfigInfo consumerConfigInfo) {
|
||||
consumerConfigInfo.getSubscriptionGroupConfig().setGroupName(getConsumerGroup(consumerConfigInfo.getSubscriptionGroupConfig().getGroupName()));
|
||||
try {
|
||||
ClusterInfo clusterInfo = clusterInfoService.get();
|
||||
for (String brokerName : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
|
||||
@@ -495,6 +503,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public Set<String> fetchBrokerNameSetBySubscriptionGroup(String group) {
|
||||
group = getConsumerGroup(group);
|
||||
Set<String> brokerNameSet = Sets.newHashSet();
|
||||
try {
|
||||
List<ConsumerConfigInfo> consumerConfigInfoList = examineSubscriptionGroupConfig(group);
|
||||
@@ -511,6 +520,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public ConsumerConnection getConsumerConnection(String consumerGroup, String address) {
|
||||
consumerGroup = getConsumerGroup(consumerGroup);
|
||||
try {
|
||||
String[] addresses = address.split(",");
|
||||
String addr = addresses[0];
|
||||
@@ -523,6 +533,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack) {
|
||||
consumerGroup = getConsumerGroup(consumerGroup);
|
||||
try {
|
||||
return mqAdminExt.getConsumerRunningInfo(consumerGroup, clientId, jstack);
|
||||
} catch (Exception e) {
|
||||
@@ -533,7 +544,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
|
||||
@Override
|
||||
public GroupConsumeInfo refreshGroup(String address, String consumerGroup) {
|
||||
|
||||
if (isCacheBeingBuilt || cacheConsumeInfoList.isEmpty()) {
|
||||
throw new RuntimeException("Cache is being built or empty, please try again later");
|
||||
}
|
||||
@@ -541,7 +551,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
for (int i = 0; i < cacheConsumeInfoList.size(); i++) {
|
||||
GroupConsumeInfo groupConsumeInfo = cacheConsumeInfoList.get(i);
|
||||
if (groupConsumeInfo.getGroup().equals(consumerGroup)) {
|
||||
GroupConsumeInfo updatedInfo = queryGroup(consumerGroup, "");
|
||||
GroupConsumeInfo updatedInfo = queryGroup(consumerGroup, address);
|
||||
updatedInfo.setUpdateTime(new Date());
|
||||
updatedInfo.setGroup(consumerGroup);
|
||||
updatedInfo.setAddress(consumerGroupMap.get(consumerGroup));
|
||||
@@ -559,4 +569,11 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
consumerGroupMap.clear();
|
||||
return queryGroupList(false, address);
|
||||
}
|
||||
|
||||
public String getConsumerGroup(String consumerGroup) {
|
||||
if (consumerGroup != null && consumerGroup.startsWith("%SYS%")) {
|
||||
return consumerGroup.substring(5); // Remove "%SYS%" prefix
|
||||
}
|
||||
return consumerGroup;
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,18 @@ spring:
|
||||
application:
|
||||
name: rocketmq-dashboard
|
||||
|
||||
security:
|
||||
user:
|
||||
name: rocketmq
|
||||
password: 1234567
|
||||
roles: ADMIN
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
|
||||
logging:
|
||||
config: classpath:logback.xml
|
||||
|
||||
|
@@ -21,11 +21,9 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.rocketmq.client.QueryResult;
|
||||
import org.apache.rocketmq.client.exception.MQBrokerException;
|
||||
import org.apache.rocketmq.client.impl.MQAdminImpl;
|
||||
import org.apache.rocketmq.client.impl.MQClientAPIImpl;
|
||||
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
|
||||
import org.apache.rocketmq.common.TopicConfig;
|
||||
import org.apache.rocketmq.common.message.Message;
|
||||
import org.apache.rocketmq.common.message.MessageExt;
|
||||
import org.apache.rocketmq.common.message.MessageQueue;
|
||||
import org.apache.rocketmq.dashboard.service.client.MQAdminExtImpl;
|
||||
@@ -589,26 +587,6 @@ public class MQAdminExtImplTest {
|
||||
Assert.assertNotNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart() {
|
||||
assertNotNull(mqAdminExtImpl);
|
||||
try {
|
||||
mqAdminExtImpl.start();
|
||||
} catch (Exception e) {
|
||||
Assert.assertTrue(e instanceof IllegalStateException);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdown() {
|
||||
assertNotNull(mqAdminExtImpl);
|
||||
try {
|
||||
mqAdminExtImpl.shutdown();
|
||||
} catch (Exception e) {
|
||||
Assert.assertTrue(e instanceof IllegalStateException);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryConsumeTimeSpan() throws Exception {
|
||||
assertNotNull(mqAdminExtImpl);
|
||||
@@ -659,7 +637,7 @@ public class MQAdminExtImplTest {
|
||||
{
|
||||
when(defaultMQAdminExt.viewBrokerStatsData(anyString(), anyString(), anyString())).thenReturn(new BrokerStatsData());
|
||||
}
|
||||
BrokerStatsData brokerStatsData = mqAdminExtImpl.viewBrokerStatsData(brokerAddr, BrokerStatsManager.TOPIC_PUT_NUMS, "topic_test");
|
||||
BrokerStatsData brokerStatsData = mqAdminExtImpl.viewBrokerStatsData(brokerAddr, BrokerStatsManager.BROKER_ACK_NUMS, "topic_test");
|
||||
Assert.assertNotNull(brokerStatsData);
|
||||
}
|
||||
|
||||
|
@@ -28,6 +28,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -87,15 +88,14 @@ public class LoginControllerTest extends BaseControllerTest {
|
||||
final String rightPwd = "admin";
|
||||
final String wrongPwd = "rocketmq";
|
||||
|
||||
// 模拟 userService.queryByName 方法返回一个用户
|
||||
User user = new User("admin", "admin", 1);
|
||||
user.setPassword(rightPwd);
|
||||
|
||||
|
||||
// 1、login fail
|
||||
perform = mockMvc.perform(post(url)
|
||||
.param("username", username)
|
||||
.param("password", wrongPwd));
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"username\":\"" + username + "\",\"password\":\"" + wrongPwd + "\"}"));
|
||||
perform.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data").doesNotExist())
|
||||
.andExpect(jsonPath("$.status").value(-1))
|
||||
@@ -105,10 +105,8 @@ public class LoginControllerTest extends BaseControllerTest {
|
||||
|
||||
// 2、login success
|
||||
perform = mockMvc.perform(post(url)
|
||||
.param("username", username)
|
||||
.param("password", rightPwd));
|
||||
perform.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.contextPath").value(contextPath));
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"username\":\"" + username + "\",\"password\":\"" + rightPwd + "\"}"));
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user