@@ -18,7 +18,7 @@ docker pull apacherocketmq/rocketmq-dashboard:latest
|
||||
#### Run it (use your own `rocketmq.namesrv.addr` and `port`)
|
||||
|
||||
```shell
|
||||
docker run -d --name rocketmq-dashboard -e "JAVA_OPTS=-Drocketmq.namesrv.addr=127.0.0.1:9876" -p 8080:8080 -t apacherocketmq/rocketmq-dashboard:latest
|
||||
docker run -d --name rocketmq-dashboard -e "JAVA_OPTS=-Drocketmq.namesrv.addr=127.0.0.1:9876" -p 8082:8082 -t apacherocketmq/rocketmq-dashboard:latest
|
||||
```
|
||||
|
||||
### Run with source code
|
||||
@@ -26,7 +26,7 @@ docker run -d --name rocketmq-dashboard -e "JAVA_OPTS=-Drocketmq.namesrv.addr=12
|
||||
|
||||
#### Prerequisite
|
||||
1. 64bit OS, Linux/Unix/Mac is recommended;
|
||||
2. 64bit JDK 1.8+;
|
||||
2. 64bit JDK 17;
|
||||
3. Maven 3.2.x;
|
||||
|
||||
#### Maven spring-boot run
|
||||
|
@@ -2,12 +2,17 @@
|
||||
|
||||
## 运维页面
|
||||
* 你可以修改这个服务使用的namesrv的地址
|
||||
|
||||
* 你可以修改这个服务是否使用VIPChannel(如果你的mq server版本小于3.5.8,请设置不使用)
|
||||
|
||||

|
||||
|
||||
## 驾驶舱
|
||||
* 查看broker的消息量(总量/5分钟图)
|
||||
* 查看单一主题的消息量(总量/趋势图)
|
||||
|
||||

|
||||
|
||||
## 集群页面
|
||||
* 查看集群的分布情况
|
||||
* cluster与broker关系
|
||||
@@ -15,9 +20,13 @@
|
||||
* 查看broker具体信息/运行信息
|
||||
* 查看broker配置信息
|
||||
|
||||

|
||||
|
||||
## 主题页面
|
||||
* 展示所有的主题,可以通过搜索框进行过滤
|
||||
* 筛选 普通/重试/死信 主题
|
||||
* 支持延迟/顺序/事务消息的筛选
|
||||
* 支持延迟/顺序/事物/普通等多种消息类型主题的新增与更新
|
||||
* 添加/更新主题
|
||||
* clusterName 创建在哪几个cluster上
|
||||
* brokerName 创建在哪几个broker上
|
||||
@@ -33,9 +42,11 @@
|
||||
* 重置消费位点(分为在线和不在线两种情况,不过都需要检查重置是否成功)
|
||||
* 删除主题 (会删除掉所有broker以及namesrv上的主题配置和路由信息)
|
||||
|
||||

|
||||
|
||||
## 消费者页面
|
||||
* 展示所有的消费组,可以通过搜索框进行过滤
|
||||
* 刷新页面/每隔五秒定时刷新页面
|
||||
* 刷新页面
|
||||
* 按照订阅组/数量/TPS/延迟 进行排序
|
||||
* 添加/更新消费组
|
||||
* clusterName 创建在哪几个集群上
|
||||
@@ -50,11 +61,20 @@
|
||||
* 消费详情 对应消费组的消费明细查看,这个消费组订阅的所有Topic的消费情况,每个queue对应的消费client查看(包括Retry消息)
|
||||
* 配置 查看变更消费组的配置
|
||||
* 删除 在指定的broker上删除消费组
|
||||
* 是否使用代理进行查询
|
||||
* 消费页面
|
||||
* 支持顺序消费类型订阅组的过滤
|
||||
* 提供顺序消费类型订阅组的新增与更新,如果需要开启顺序消费,FIFO类型的订阅组一定需要打开consumeOrderlyEnable选项
|
||||
|
||||

|
||||
|
||||
## 发布管理页面
|
||||
* 通过Topic和Group查询在线的消息生产者客户端
|
||||
* 信息包含客户端主机 版本
|
||||
|
||||
|
||||

|
||||
|
||||
## 消息查询页面
|
||||
* 根据Topic和时间区间查询
|
||||
*由于数据量大 最多只会展示2000条,多的会被忽略
|
||||
@@ -63,20 +83,25 @@
|
||||
* 根据消息主题和消息Id进行消息的查询
|
||||
* 消息详情可以展示这条消息的详细信息,查看消息对应到具体消费组的消费情况(如果异常,可以查看具体的异常信息)。可以向指定的消费组重发消息。
|
||||
|
||||
## RocketMQ-V5.0 仪表盘
|
||||
* 版本切换
|
||||
* RocketMQ右上角可切换不同版本,用户可以自主选择 RocketMQ-5.x 或 RocketMQ-4.x 版本
|
||||
* 主题页面
|
||||
* 支持延迟/顺序/事务消息的筛选
|
||||
* 支持延迟/顺序/事物/普通等多种消息类型主题的新增与更新
|
||||
* 消费页面
|
||||
* 支持顺序消费类型订阅组的过滤
|
||||
* 提供顺序消费类型订阅组的新增与更新,如果需要开启顺序消费,FIFO类型的订阅组一定需要打开consumeOrderlyEnable选项
|
||||

|
||||
|
||||
## 代理页面
|
||||
* 代理页面(RocketMQ 5.0新增)
|
||||
* 支持代理节点的新增与查询
|
||||
* 支持代理节点地址配置:在application.yml中可对proxyAddr和proxyAddrs属性进行预配置
|
||||
|
||||

|
||||
|
||||
## ACL2.0管理界面
|
||||
|
||||
- 支持根据broker地址的acl规则的查询
|
||||
- acl规则的修改、新增、删除、查找
|
||||
- (不再支持acl1.0)
|
||||
|
||||

|
||||
|
||||
## HTTPS 方式访问Dashboard
|
||||
|
||||
* HTTPS功能实际上是使用SpringBoot提供的配置功能即可完成,首先,需要有一个SSL KeyStore来存放服务端证书,可以使用本工程所提供的测试密钥库:
|
||||
resources/rmqcngkeystore.jks, 它可以通过如下keytool命令生成
|
||||
```
|
||||
@@ -111,7 +136,7 @@ rocketmq.config.loginRequired=true
|
||||
# Dashboard文件目录,登录用户配置文件所在目录
|
||||
rocketmq.config.dataPath=/tmp/rocketmq-console/data
|
||||
```
|
||||
* 2.确保${rocketmq.config.dataPath}定义的目录存在,并且该目录下创建登录配置文件"users.properties", 如果该目录下不存在此文件,则默认使用resources/users.properties文件。
|
||||
* 2.确保${rocketmq.config.dataPath}定义的目录存在,并且该目录下创建登录配置文件"users.properties", 如果该目录下不存在此文件,则默认使用resources/users.properties文件。 ps: 如果rocketmq启用了acl,控制台必须配置ak和sk,同时application.yml中的rocketmq.config.authmode 需要为acl且登录功能需要打开才能正常使用,登录后将使用acl2.0中的用户名和密码构造rpchook与broker进行通信。
|
||||
users.properties文件格式为:
|
||||
```$xslt
|
||||
# 该文件支持热修改,即添加和修改用户时,不需要重新启动console
|
||||
@@ -126,6 +151,8 @@ user2=user2
|
||||
```
|
||||
* 3.启动控制台则开启了登录功能
|
||||
|
||||
|
||||
|
||||
## 权限检验
|
||||
如果用户访问console时开启了登录功能,会按照登录的角色对访问的接口进行权限控制。
|
||||
* 1.在Spring配置文件resources/application.properties中修改rocketmq.config.loginRequired=true开启登录功能
|
||||
@@ -149,7 +176,7 @@ role-permission.yml文件格式为:
|
||||
|
||||
rolePerms:
|
||||
# 普通用户
|
||||
ordinary:
|
||||
Normal:
|
||||
- /rocketmq/nsaddr
|
||||
- /ops/*
|
||||
- /dashboard/**
|
||||
|
BIN
docs/1_0_0/UserGuide_CN/image-20250706143719935.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706143801952.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706143819962.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706143900173.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706143924854.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706144100067.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706144145077.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706144418694.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/1_0_0/UserGuide_CN/image-20250706145313629.png
Normal file
After Width: | Height: | Size: 33 KiB |
@@ -1,170 +1,232 @@
|
||||
# RocketMQ User Guide
|
||||
|
||||
## OPS Page
|
||||
* You can change dashboard's namesrvAddr here
|
||||
* You can change the value of useVIPChannel here (if you rocketMQ version < 3.5.8,the value of useVIPChannel should be false)
|
||||
# RocketMQ Usage Documentation
|
||||
|
||||
## DashBoard Page
|
||||
* broker's message count (broker total message count/5 min trend)
|
||||
* topic's message count(topic total message count/5 min trend)
|
||||
---
|
||||
|
||||
## Operations Page
|
||||
* You can **modify the NameSrv address** used by this service.
|
||||
* You can configure whether this service uses **VIPChannel** (if your MQ server version is less than 3.5.8, please set it to not use).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Dashboard
|
||||
* View the **broker's message volume** (total/5-minute chart).
|
||||
* View a **single topic's message volume** (total/trend chart).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Cluster Page
|
||||
* Cluster Detail
|
||||
* relation between cluster and broker
|
||||
* broker's master / salve node
|
||||
* broker'a detail info(runtime info)
|
||||
* broker's config
|
||||
* View the **cluster distribution**.
|
||||
* Cluster and broker relationships.
|
||||
* Broker information.
|
||||
* View **specific broker information/runtime information**.
|
||||
* View **broker configuration information**.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Topic Page
|
||||
* show all the topics,you can filter topic by search bar
|
||||
* filter (Normal/retry/dead) topic
|
||||
* Add/Update Topic
|
||||
* clusterName (create on which cluster)
|
||||
* brokerName (create on which broker)
|
||||
* topicName
|
||||
* writeQueueNums
|
||||
* readQueueNums
|
||||
* perm //2 for write 4 for read 6 for write and read
|
||||
* STATUS look over message send status(send to which broker/which queue/how many messages)
|
||||
* ROUTER look update topic's router(this topic send to which broker,the broker's queue info)
|
||||
* CONSUMER MANAGE(this topic consume by which group,how about the consume state)
|
||||
* TOPIC CONFIG(check or change the topic's config)
|
||||
* SEND MESSAGE(send a test message)
|
||||
* Reset CONSUMER OFFSET (the consumer online or not online is different,you need check the reset result)
|
||||
* DELETE (will delete the topic on all broker and namesrv)
|
||||
* Display all topics, filterable via a **search box**.
|
||||
* **Filter topics** by normal, retry, or dead-letter types.
|
||||
* Supports filtering for delayed, ordered, and transactional messages.
|
||||
* Supports adding and updating topics of various message types: delayed, ordered, transactional, and normal.
|
||||
* **Add/Update Topic**:
|
||||
* **clusterName**: Specify which clusters to create the topic on.
|
||||
* **brokerName**: Specify which brokers to create the topic on.
|
||||
* **topicName**: The name of the topic.
|
||||
* **writeQueueNums**: Number of write queues.
|
||||
* **readQueueNums**: Number of read queues.
|
||||
* **perm**: // 2 for write, 4 for read, 6 for read/write.
|
||||
* **Status**: Query message delivery status (which brokers/queues delivered to, quantity, etc.).
|
||||
* **Routing**: View message routing (which brokers messages for this topic will be sent to, and corresponding broker queue information).
|
||||
* **Consumer Management**: See which groups are consuming this topic and their consumption status.
|
||||
* **Topic Configuration**: View and modify the current configuration.
|
||||
* **Send Message**: Send a test message to this topic.
|
||||
* **Reset Consumer Offset**: Available for both online and offline scenarios, though success should always be verified.
|
||||
* **Delete Topic**: Deletes all topic configurations and routing information from all brokers and NameSrvs.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Consumer Page
|
||||
* show all the consumers,you can filter consumer by search bar
|
||||
* refresh page/refresh page per 5 seconds
|
||||
* order by SubscriptionGroup/Quantity/TPS/Delay
|
||||
* Add/Update Consumer
|
||||
* clusterName (create on which cluster)
|
||||
* brokerName (create on which broker)
|
||||
* groupName (consumer group name)
|
||||
* consumeEnable (this group can't consume message if this is false)
|
||||
* consumeBroadcastEnable (can't use broadcast is this is false)
|
||||
* retryQueueNums
|
||||
* brokerId (consume form where when broker is normal)
|
||||
* whichBrokerWhenConsumeSlowly(consume form where when broker has problem)
|
||||
* CLIENT (look over online consumer's client,include subscribe info and consume mode)
|
||||
* CONSUME DETAIL (look over this consumer's consume detail,broker offset and the consumer offset,queue consumed by which client)
|
||||
* CONFIG (check or change the consumer's config)
|
||||
* DELETE (delete the consumer group on selected group)
|
||||
* Display all **consumer groups**, filterable via a search box.
|
||||
* **Refresh** page.
|
||||
* **Sort** by subscription group, quantity, TPS, or latency.
|
||||
* **Add/Update Consumer Group**:
|
||||
* **clusterName**: Specify on which clusters to create.
|
||||
* **brokerName**: Specify on which brokers to create.
|
||||
* **groupName**: Consumer group name.
|
||||
* **consumeEnable**: // Whether consumption is enabled. If `FALSE`, consumption will be disabled.
|
||||
* **consumeBroadcastEnable**: // Whether broadcast consumption is enabled.
|
||||
* **retryQueueNums**: // Size of the retry queue.
|
||||
* **brokerId**: // Normally consume from this broker.
|
||||
* **whichBrokerWhenConsumeSlowly**: // Which broker to consume from if issues arise.
|
||||
* **Terminal**: View online consumer clients, including version, subscription information, and consumption mode.
|
||||
* **Consumption Details**: View detailed consumption information for the corresponding consumer group, including the consumption status of all subscribed topics and the consumer client for each queue (including retry messages).
|
||||
* **Configuration**: View and modify the consumer group's configuration.
|
||||
* **Delete**: Delete the consumer group on the specified broker.
|
||||
* **Query using Proxy**:
|
||||
* **Consumption Page**:
|
||||
* Supports filtering for **ordered consumption type subscription groups**.
|
||||
* Provides **adding and updating for ordered consumption type subscription groups**. If ordered consumption needs to be enabled, the `consumeOrderlyEnable` option must be turned on for FIFO type subscription groups.
|
||||
|
||||
## Producer Page
|
||||
* Query online producer client by topic and group
|
||||
* show client's server / version
|
||||
|
||||
## Message Page
|
||||
* Query By Topic And Time
|
||||
*Only Return 2000 Messages,the message more than 2000 will be hide
|
||||
* Query By Topic And Key
|
||||
* Only Return 64 Messages
|
||||
* Query By Topic And MessageId
|
||||
* look over this message's detail info.you can see the message's consume state(each group has one line),show the exception message if has exception.
|
||||
you can send this message to the group you selected
|
||||

|
||||
|
||||
## RocketMQ-V5.0 dashboard
|
||||
* Version switching
|
||||
* RocketMQ can switch between different versions in the upper right corner, and users can freely choose between RocketMQ-5.X or RocketMQ-4.X versions
|
||||
* Theme page
|
||||
* Support filtering of delayed/sequential/transaction messages
|
||||
* Support the addition and update of multiple message types such as delay, sequence, object, and ordinary themes
|
||||
* Consumption page
|
||||
* Support filtering of subscription groups for fifo consumption types
|
||||
* Provide the addition and update of subscription groups for sequential consumption types. If fifo consumption needs to be enabled, FIFO type subscription groups must have the consumeOrderlyEnable option enabled
|
||||
* Proxy page (Added in RocketMQ 5.0)
|
||||
* Support for adding and querying proxy nodes
|
||||
* Support proxy node address configuration: ProxyAddr and proxyAddrs properties can be pre configured in application.yml
|
||||
---
|
||||
|
||||
## Access Dashboard with HTTPS
|
||||
* SpringBoot itself has provided the SSL configuration. You can use the project test Keystore:resources/rmqcngkeystore.jks. The store is generated with the following unix keytool commands:
|
||||
```
|
||||
#Generate Keystore and add alias rmqcngKey
|
||||
keytool -genkeypair -alias rmqcngKey -keyalg RSA -validity 3650 -keystore rmqcngkeystore.jks
|
||||
#View keystore content
|
||||
keytool -list -v -keystore rmqcngkeystore.jks
|
||||
#Transfer type as official
|
||||
keytool -importkeystore -srckeystore rmqcngkeystore.jks -destkeystore rmqcngkeystore.jks -deststoretype pkcs12
|
||||
## Publishing Management Page
|
||||
* Query online **message producer clients** by Topic and Group.
|
||||
* Information includes client host and version.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Message Query Page
|
||||
* Query by **Topic and Time Range**.
|
||||
* Due to large data volume, a maximum of 2000 entries will be displayed; additional results will be ignored.
|
||||
* Query by **Topic and Key**.
|
||||
* A maximum of 64 entries will be displayed.
|
||||
* Query messages by **message topic and message ID**.
|
||||
* **Message Details** can display comprehensive information about the message and its consumption status by specific consumer groups (including specific error information if an exception occurred). Messages can be re-sent to specified consumer groups.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Proxy Page
|
||||
* **Proxy Page** (New in RocketMQ 5.0)
|
||||
* Supports adding and querying **proxy nodes**.
|
||||
* Supports **proxy node address configuration**: `proxyAddr` and `proxyAddrs` properties can be pre-configured in `application.yml`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## ACL 2.0 Management Interface
|
||||
* Supports querying **ACL rules** based on broker addresses.
|
||||
* **Modification, addition, deletion, and lookup** of ACL rules.
|
||||
* (ACL 1.0 is no longer supported.)
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## HTTPS Access to Dashboard
|
||||
The HTTPS feature is implemented using Spring Boot's configuration capabilities. First, you'll need an **SSL KeyStore** to store the server certificate. You can use the test keystore provided by this project: `resources/rmqcngkeystore.jks`, which can be generated using the following `keytool` commands:
|
||||
|
||||
```bash
|
||||
# Generate a keystore and add a private key with the alias 'rmqcngKey'
|
||||
keytool -genkeypair -alias rmqcngKey -keyalg RSA -validity 3650 -keystore rmqcngkeystore.jks
|
||||
# View keystore content
|
||||
keytool -list -v -keystore rmqcngkeystore.jks
|
||||
# Convert keystore format
|
||||
keytool -importkeystore -srckeystore rmqcngkeystore.jks -destkeystore rmqcngkeystore.jks -deststoretype pkcs12
|
||||
```
|
||||
|
||||
* Uncomment the following SSL properties in resources/application.properties. restart Dashboard then access with HTTPS.
|
||||
Configure `resources/application.properties` by enabling the SSL-related options. HTTPS will be enabled after starting the dashboard.
|
||||
|
||||
```
|
||||
#Set https port
|
||||
|
||||
|
||||
```Properties
|
||||
# Set HTTPS port
|
||||
server.port=8443
|
||||
|
||||
### SSL setting
|
||||
server.ssl.key-store=classpath:rmqcngkeystore.jks
|
||||
server.ssl.key-store-password=rocketmq
|
||||
server.ssl.keyStoreType=PKCS12
|
||||
server.ssl.keyAlias=rmqcngkey
|
||||
#server.ssl.key-store=classpath:rmqcngkeystore.jks
|
||||
#server.ssl.key-store-password=rocketmq
|
||||
#server.ssl.keyStoreType=PKCS12
|
||||
#server.ssl.keyAlias=rmqcngkey
|
||||
```
|
||||
|
||||
## Login/Logout on Dashboard
|
||||
Access Dashboard with username and password and logout to leave the dashboard。To stage the function on, we need the steps below:
|
||||
|
||||
* 1.Turn on the property in resources/application.properties.
|
||||
```$xslt
|
||||
# open the login func
|
||||
rocketmq.config.loginRequired=true
|
||||
|
||||
# Directory of ashboard & login user configure file
|
||||
rocketmq.config.dataPath=/tmp/rocketmq-console/data
|
||||
```
|
||||
* 2.Make sure the directory defined in property ${rocketmq.config.dataPath} exists and the file "users.properties" is created under it.
|
||||
The dashboard system will use the resources/users.properties by default if a customized file is not found。
|
||||
|
||||
The format in the content of users.properties:
|
||||
```$xslt
|
||||
# This file supports hot change, any change will be auto-reloaded without Console restarting.
|
||||
# Format: a user per line, username=password[,N] #N is optional, 0 (Normal User); 1 (Admin)
|
||||
|
||||
# Define Admin
|
||||
admin=admin,1
|
||||
|
||||
# Define Normal users
|
||||
user1=user1
|
||||
user2=user2
|
||||
```
|
||||
* 3.Restart Console Application after above configuration setting well.
|
||||
------
|
||||
|
||||
|
||||
## Permission Control
|
||||
If the login function is enabled when a user accesses the Console, the user controls the access permission of the interface based on the login role.
|
||||
|
||||
* 1.Turn on the property in resources/application.properties.
|
||||
```$xslt
|
||||
# open the login func
|
||||
rocketmq.config.loginRequired=true
|
||||
## Login and Access Dashboard
|
||||
|
||||
# Directory of ashboard & login user configure file
|
||||
rocketmq.config.dataPath=/tmp/rocketmq-console/data
|
||||
```
|
||||
* 2.Make sure the directory defined in property ${rocketmq.config.dataPath} exists and the permission control file "role-permission.yml" is created under it.
|
||||
The console system will use the resources/role-permission.yml by default if a customized file is not found。
|
||||
|
||||
The format in the content of role-permission.yml:
|
||||
```$xslt
|
||||
# This file supports hot change, any change will be auto-reloaded without Console restarting.
|
||||
# Format: To add or delete interface permissions, add or delete interface addresses from the list.
|
||||
# the interface paths can be configured with wildcard characters.
|
||||
# ?: Matches 1 characters.
|
||||
# *: Matches 0 or more characters that are not /.
|
||||
# **: Matches 0 or more characters.
|
||||
|
||||
rolePerms:
|
||||
# ordinary user
|
||||
ordinary:
|
||||
- /rocketmq/nsaddr
|
||||
- /ops/*
|
||||
- /dashboard/**
|
||||
- /topic/*.query
|
||||
- /topic/sendTopicMessage.do
|
||||
- /producer/*.query
|
||||
- /message/*
|
||||
- /messageTrace/*
|
||||
- /monitor/*
|
||||
....
|
||||
```
|
||||
* 3.On the front page, operation buttons such as deleting and updating resources are not displayed for common users in order to better distinguish the rights of common users and admin users. If need to operate related resources, log out and use the admin role to log in
|
||||
The Dashboard supports **logging in with a username and password** and logging out after operations. The following settings are required:
|
||||
|
||||
1. In the Spring configuration file `resources/application.properties`, modify `rocketmq.config.loginRequired=true` to **enable the login function**:
|
||||
|
||||
|
||||
|
||||
```Properties
|
||||
# Enable login function
|
||||
rocketmq.config.loginRequired=true
|
||||
|
||||
# Dashboard file directory, where the login user configuration file is located
|
||||
rocketmq.config.dataPath=/tmp/rocketmq-console/data
|
||||
```
|
||||
|
||||
2. Ensure that the directory defined by `${rocketmq.config.dataPath}` exists and create a login configuration file named "**users.properties**" within it. If this file doesn't exist, `resources/users.properties` will be used by default. **Note:** If RocketMQ's ACL is enabled, the console must configure AK and SK. Additionally, `rocketmq.config.authmode` in `application.yml` needs to be set to `acl` and the login function must be enabled for proper operation. After logging in, the username and password from ACL 2.0 will be used to construct the RPCHook for communication with the broker. The format of the `users.properties` file is:
|
||||
|
||||
|
||||
|
||||
```Properties
|
||||
# This file supports hot modification; adding and modifying users does not require restarting the console.
|
||||
# Format: Each line defines a user, username=password[,N] #N is optional, 0 for normal user; 1 for administrator.
|
||||
|
||||
# Define administrator
|
||||
admin=admin,1
|
||||
|
||||
# Define normal users
|
||||
user1=user1
|
||||
user2=user2
|
||||
```
|
||||
|
||||
3. Starting the console will enable the login function.
|
||||
|
||||
------
|
||||
|
||||
|
||||
|
||||
## Permission Verification
|
||||
|
||||
|
||||
|
||||
If the user accesses the console with the login function enabled, interface access will be controlled based on the logged-in role.
|
||||
|
||||
1. In the Spring configuration file `resources/application.properties`, modify `rocketmq.config.loginRequired=true` to **enable the login function**:
|
||||
|
||||
```Properties
|
||||
# Enable login function
|
||||
rocketmq.config.loginRequired=true
|
||||
|
||||
# Dashboard file directory, where the login user configuration file is located
|
||||
rocketmq.config.dataPath=/tmp/rocketmq-console/data
|
||||
```
|
||||
|
||||
2. Ensure that the directory defined by `${rocketmq.config.dataPath}` exists and create an access permission configuration file named "**role-permission.yml**" within it. If this file doesn't exist, `resources/role-permission.yml` will be used by default. This file saves all accessible interface addresses for the normal user role. The format of the `role-permission.yml` file is:
|
||||
|
||||
```YAML
|
||||
# This file supports hot modification; adding and deleting interface permissions directly in the list.
|
||||
# Interface path configuration supports wildcards:
|
||||
# * Matches 0 or more characters that are not '/'.
|
||||
# ** Matches 0 or more arbitrary characters.
|
||||
# ? Matches 1 arbitrary character.
|
||||
|
||||
rolePerms:
|
||||
# Normal user
|
||||
Normal:
|
||||
- /rocketmq/nsaddr
|
||||
- /ops/*
|
||||
- /dashboard/**
|
||||
- /topic/*.query
|
||||
- /topic/sendTopicMessage.do
|
||||
- /producer/*.query
|
||||
- /message/*
|
||||
- /messageTrace/*
|
||||
- /monitor/*
|
||||
....
|
||||
```
|
||||
|
||||
3. On the frontend page, to better distinguish between normal user and admin user permissions, operation buttons for resource deletion and updates are **not displayed for normal user roles**. To perform resource-related operations, you need to log out and log in with the admin role.
|
@@ -77,7 +77,7 @@ const remoteApi = {
|
||||
listUsers: async (brokerAddress) => {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/acls.query?${params.toString()}`));
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/users.query?${params.toString()}`));
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
|
@@ -147,7 +147,7 @@ export const translations = {
|
||||
"USER_NAME": "用户名",
|
||||
"PASSWORD": "密码",
|
||||
"SYSTEM": "系统",
|
||||
"WELCOME": "您好,欢迎使用RocketMQ仪表盘",
|
||||
"WELCOME": "欢迎使用RocketMQ仪表盘",
|
||||
"ENABLE_MESSAGE_TRACE": "开启消息轨迹",
|
||||
"MESSAGE_TRACE_DETAIL": "消息轨迹详情",
|
||||
"TRACE_TOPIC": "消息轨迹主题",
|
||||
@@ -284,6 +284,12 @@ export const translations = {
|
||||
"PROXY_ENABLED": "代理启用",
|
||||
"BROKER_OVERVIEW": "Broker概览",
|
||||
"TOTAL_MSG_RECEIVED_TODAY": "今天接收的总消息数",
|
||||
"LOGIN_SUCCESS": "登录成功",
|
||||
"LOGIN_FAILED": "登录失败",
|
||||
"USERNAME_REQUIRED": "用户名为必填项",
|
||||
"USERNAME_PLACEHOLDER": "用户名",
|
||||
"PASSWORD_REQUIRED": "密码为必填项",
|
||||
"PASSWORD_PLACEHOLDER": "密码",
|
||||
},
|
||||
en: {
|
||||
"DEFAULT": "Default",
|
||||
@@ -417,7 +423,7 @@ export const translations = {
|
||||
"USER_NAME": "Username",
|
||||
"PASSWORD": "Password",
|
||||
"SYSTEM": "SYSTEM",
|
||||
"WELCOME": "Hi, welcome using RocketMQ Dashboard",
|
||||
"WELCOME": "Welcome using RocketMQ Dashboard",
|
||||
"ENABLE_MESSAGE_TRACE": "Enable Message Trace",
|
||||
"MESSAGE_TRACE_DETAIL": "Message Trace Detail",
|
||||
"TRACE_TOPIC": "TraceTopic",
|
||||
@@ -546,6 +552,12 @@ export const translations = {
|
||||
"PROXY_ENABLED": "Proxy Enabled",
|
||||
"BROKER_OVERVIEW": "Broker Overview",
|
||||
"TOTAL_MSG_RECEIVED_TODAY": "Total messages received today",
|
||||
"LOGIN_SUCCESS": "Login successful",
|
||||
"LOGIN_FAILED": "Login failed",
|
||||
"USERNAME_REQUIRED": "Username is required",
|
||||
"USERNAME_PLACEHOLDER": "Username placeholder",
|
||||
"PASSWORD_REQUIRED": "Password is required",
|
||||
"PASSWORD_PLACEHOLDER": "Password placeholder",
|
||||
|
||||
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ const Acl = () => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const [isAclModalVisible, setIsAclModalVisible] = useState(false);
|
||||
const [writeOperationEnabled, setWriteOperationEnabled] = useState(true);
|
||||
const [currentAcl, setCurrentAcl] = useState(null);
|
||||
const [aclForm] = Form.useForm();
|
||||
const [messageApi, msgContextHolder] = message.useMessage();
|
||||
@@ -132,6 +133,16 @@ const Acl = () => {
|
||||
|
||||
}, [activeTab]); // Dependencies for useEffect
|
||||
|
||||
useEffect(() => {
|
||||
const userPermission = localStorage.getItem('userrole');
|
||||
console.log(userPermission);
|
||||
if (userPermission == 2) {
|
||||
setWriteOperationEnabled(false);
|
||||
} else {
|
||||
setWriteOperationEnabled(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// --- Helper function to update broker options based on selected cluster ---
|
||||
const updateBrokerOptions = (clusterName, info = clusterData) => {
|
||||
if (!info || !info.clusterAddrTable) {
|
||||
@@ -488,24 +499,26 @@ const Acl = () => {
|
||||
dataIndex: 'userStatus',
|
||||
key: 'userStatus',
|
||||
render: (status) => (
|
||||
<Tag color={status=== 'enable' ? 'green' : 'red'}>{status}</Tag>
|
||||
<Tag color={status === 'Enabled' ? 'green' : 'red'}>{status}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t.OPERATION,
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Space size="middle">
|
||||
<Button icon={<EditOutlined />} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
|
||||
<Popconfirm
|
||||
title={t.CONFIRM_DELETE_USER}
|
||||
onConfirm={() => handleDeleteUser(record.username)}
|
||||
okText={t.YES}
|
||||
cancelText={t.NO}
|
||||
>
|
||||
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
writeOperationEnabled ? (
|
||||
<Space size="middle">
|
||||
<Button icon={<EditOutlined />} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
|
||||
<Popconfirm
|
||||
title={t.CONFIRM_DELETE_USER}
|
||||
onConfirm={() => handleDeleteUser(record.username)}
|
||||
okText={t.YES}
|
||||
cancelText={t.NO}
|
||||
>
|
||||
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
) : null
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -552,17 +565,19 @@ const Acl = () => {
|
||||
title: t.OPERATION,
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Space size="middle">
|
||||
<Button icon={<EditOutlined />} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
|
||||
<Popconfirm
|
||||
title={t.CONFIRM_DELETE_ACL}
|
||||
onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
|
||||
okText={t.YES}
|
||||
cancelText={t.NO}
|
||||
>
|
||||
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
writeOperationEnabled ? (
|
||||
<Space size="middle">
|
||||
<Button icon={<EditOutlined />} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
|
||||
<Popconfirm
|
||||
title={t.CONFIRM_DELETE_ACL}
|
||||
onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
|
||||
okText={t.YES}
|
||||
cancelText={t.NO}
|
||||
>
|
||||
<Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
) : null
|
||||
),
|
||||
},
|
||||
];
|
||||
|
@@ -232,6 +232,15 @@ const ConsumerGroupList = () => {
|
||||
return () => clearInterval(intervalId);
|
||||
}, [intervalProcessSwitch, loadConsumerGroups]);
|
||||
|
||||
useEffect(() => {
|
||||
const userPermission = localStorage.getItem('userrole');
|
||||
console.log(userPermission);
|
||||
if (userPermission == 2) {
|
||||
setWriteOperationEnabled(false);
|
||||
} else {
|
||||
setWriteOperationEnabled(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
filterList(paginationConf.current, allConsumerGroupList);
|
||||
|
@@ -250,7 +250,6 @@ 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);
|
||||
@@ -260,8 +259,7 @@ const DashboardPage = () => {
|
||||
...broker,
|
||||
key: broker.brokerName,
|
||||
}));
|
||||
console.log("即将设置的数据:", newData); // 先打印
|
||||
setBrokerTableData(newData); // 再设置状态
|
||||
setBrokerTableData(newData);
|
||||
|
||||
brokerArray.sort((firstBroker, lastBroker) => {
|
||||
const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0);
|
||||
|
@@ -75,7 +75,7 @@ const DlqMessageQueryPage = () => {
|
||||
useEffect(() => {
|
||||
const fetchConsumerGroups = async () => {
|
||||
setLoading(true);
|
||||
const resp = await remoteApi.queryConsumerGroupList();
|
||||
const resp = await remoteApi.queryConsumerGroupList(false);
|
||||
if (resp.status === 0) {
|
||||
const filteredGroups = resp.data
|
||||
.filter(consumerGroup => !consumerGroup.group.startsWith(SYS_GROUP_TOPIC_PREFIX))
|
||||
|
@@ -18,23 +18,24 @@
|
||||
import React from 'react';
|
||||
import {Button, Form, Input, message, Typography} from 'antd';
|
||||
import {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||
import {useLanguage} from "../../i18n/LanguageContext";
|
||||
|
||||
const {Title} = Typography;
|
||||
|
||||
const Login = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [messageApi, msgContextHolder] = message.useMessage();
|
||||
|
||||
const {t} = useLanguage();
|
||||
const onFinish = async (values) => {
|
||||
const {username, password} = values;
|
||||
remoteApi.login(username, password).then((res) => {
|
||||
if (res.status === 0) {
|
||||
messageApi.success('登录成功');
|
||||
messageApi.success(t.LOGIN_SUCCESS);
|
||||
window.localStorage.setItem("username", res.data.loginUserName);
|
||||
window.localStorage.setItem("userrole", res.data.loginUserRole);
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
messageApi.error(res.message || '登录失败,请检查用户名和密码');
|
||||
messageApi.error(res.message || t.LOGIN_FAILED);
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -50,7 +51,7 @@ const Login = () => {
|
||||
borderRadius: 8
|
||||
}}>
|
||||
<Title level={3} style={{textAlign: 'center', marginBottom: 24}}>
|
||||
WELCOME
|
||||
{t.WELCOME}
|
||||
</Title>
|
||||
<Form
|
||||
form={form}
|
||||
@@ -60,30 +61,27 @@ const Login = () => {
|
||||
initialValues={{username: '', password: ''}}
|
||||
>
|
||||
<Form.Item
|
||||
label="用户名"
|
||||
label={t.USERNAME}
|
||||
name="username"
|
||||
rules={[{required: true, message: '请输入用户名'}]}
|
||||
>
|
||||
<Input placeholder="请输入用户名"/>
|
||||
rules={[{required: true, message: t.USERNAME_REQUIRED}]}>
|
||||
<Input placeholder={t.USERNAME_PLACEHOLDER}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="密码"
|
||||
label={t.PASSWORD}
|
||||
name="password"
|
||||
rules={[{required: true, message: '请输入密码'}]}
|
||||
>
|
||||
<Input.Password placeholder="请输入密码"/>
|
||||
rules={[{required: true, message: t.PASSWORD_REQUIRED}]}>
|
||||
<Input.Password placeholder={t.PASSWORD_PLACEHOLDER}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" block>
|
||||
登录
|
||||
{t.LOGIN}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -48,6 +48,16 @@ const Ops = () => {
|
||||
fetchOpsData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const userPermission = localStorage.getItem('userrole');
|
||||
console.log(userPermission);
|
||||
if (userPermission == 2) {
|
||||
setWriteOperationEnabled(false);
|
||||
} else {
|
||||
setWriteOperationEnabled(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleUpdateNameSvrAddr = async () => {
|
||||
if (!selectedNamesrv) {
|
||||
messageApi.warning('请选择一个 NameServer 地址');
|
||||
|
@@ -37,9 +37,13 @@ const ProxyManager = () => {
|
||||
const [notificationApi, notificationContextHolder] = notification.useNotification();
|
||||
|
||||
useEffect(() => {
|
||||
const userRole = sessionStorage.getItem("userrole");
|
||||
const isWriteEnabled = userRole === null || userRole === '1';
|
||||
setWriteOperationEnabled(isWriteEnabled);
|
||||
const userPermission = localStorage.getItem('userrole');
|
||||
console.log(userPermission);
|
||||
if (userPermission == 2) {
|
||||
setWriteOperationEnabled(false);
|
||||
} else {
|
||||
setWriteOperationEnabled(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
@@ -100,6 +100,16 @@ const DeployHistoryList = () => {
|
||||
}, [filterStr, filterNormal, filterDelay, filterFifo, filterTransaction,
|
||||
filterUnspecified, filterRetry, filterDLQ, filterSystem, allTopicList]);
|
||||
|
||||
useEffect(() => {
|
||||
const userPermission = localStorage.getItem('userrole');
|
||||
console.log(userPermission);
|
||||
if (userPermission == 2) {
|
||||
setWriteOperationEnabled(false);
|
||||
} else {
|
||||
setWriteOperationEnabled(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Close functions for Modals
|
||||
const closeAddUpdateDialog = () => {
|
||||
setIsAddUpdateTopicModalVisible(false);
|
||||
|
@@ -57,6 +57,10 @@ public class MQAdminAspect {
|
||||
METHODS_TO_CHECK.add("examineConsumerConnectionInfo");
|
||||
METHODS_TO_CHECK.add("examineConsumeStats");
|
||||
METHODS_TO_CHECK.add("examineProducerConnectionInfo");
|
||||
METHODS_TO_CHECK.add("fetchBrokerRuntimeStats");
|
||||
METHODS_TO_CHECK.add("fetchAllTopicList");
|
||||
METHODS_TO_CHECK.add("examineTopicRouteInfo");
|
||||
METHODS_TO_CHECK.add("queryTopicConsumeByWho");
|
||||
}
|
||||
|
||||
// Pointcut remains the same, targeting methods in MQAdminExtImpl
|
||||
|