Compare commits
16 Commits
a5138eb0d8
...
rocketmq-d
Author | SHA1 | Date | |
---|---|---|---|
|
a013c8fad1 | ||
|
cd262da8b1 | ||
|
37dbd7f327 | ||
|
79556420f5 | ||
|
9cb185afc1 | ||
|
f60103af9b | ||
|
9c2a069976 | ||
|
8cc7d6a727 | ||
|
a4e02f472f | ||
|
87cfa3e872 | ||
|
c297d059a9 | ||
|
07793d8aae | ||
|
4b9ed97f8f | ||
|
ff73529a75 | ||
|
706082c62f | ||
|
6531929124 |
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,5 +1,4 @@
|
||||
## [Apache RocketMQ](https://github.com/apache/rocketmq) Dashboard
|
||||
[](https://travis-ci.com/github/apache/rocketmq-dashboard) [](https://coveralls.io/github/apache/rocketmq-dashboard?branch=master)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[](https://codecov.io/gh/apache/rocketmq-dashboard)
|
||||
[](http://isitmaintained.com/project/apache/rocketmq-dashboard "Average time to resolve an issue")
|
||||
@@ -18,7 +17,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 +25,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,26 @@
|
||||
* 根据消息主题和消息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规则的修改、新增、删除、查找
|
||||
- 如果只是选取了集群名字,那么查询的acl列表将会取交集,如果选取了brokerName,就会返回该broker的acl列表。
|
||||
- (不再支持acl1.0)
|
||||
|
||||

|
||||
|
||||
## HTTPS 方式访问Dashboard
|
||||
|
||||
* HTTPS功能实际上是使用SpringBoot提供的配置功能即可完成,首先,需要有一个SSL KeyStore来存放服务端证书,可以使用本工程所提供的测试密钥库:
|
||||
resources/rmqcngkeystore.jks, 它可以通过如下keytool命令生成
|
||||
```
|
||||
@@ -111,7 +137,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 +152,8 @@ user2=user2
|
||||
```
|
||||
* 3.启动控制台则开启了登录功能
|
||||
|
||||
|
||||
|
||||
## 权限检验
|
||||
如果用户访问console时开启了登录功能,会按照登录的角色对访问的接口进行权限控制。
|
||||
* 1.在Spring配置文件resources/application.properties中修改rocketmq.config.loginRequired=true开启登录功能
|
||||
@@ -149,7 +177,7 @@ role-permission.yml文件格式为:
|
||||
|
||||
rolePerms:
|
||||
# 普通用户
|
||||
ordinary:
|
||||
Normal:
|
||||
- /rocketmq/nsaddr
|
||||
- /ops/*
|
||||
- /dashboard/**
|
||||
@@ -161,4 +189,4 @@ rolePerms:
|
||||
- /monitor/*
|
||||
....
|
||||
```
|
||||
* 3.前端页面显示上,为了更好区分普通用户和admin用户权限,关于资源的删除、更新等操作按钮不对普通用户角色显示,如果要执行资源相关操作,需要退出使用admin角色登录。
|
||||
* 3.前端页面显示上,为了更好区分普通用户和admin用户权限,关于资源的删除、更新等操作按钮不对普通用户角色显示,如果要执行资源相关操作,需要退出使用admin角色登录。
|
||||
|
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.
|
@@ -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: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.
|
||||
|
||||
### `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.\
|
||||
|
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",
|
||||
|
@@ -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();
|
||||
});
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
const appConfig = {
|
||||
apiBaseUrl: 'http://localhost:8082' // 请替换为你的实际 API Base URL
|
||||
apiBaseUrl: 'http://localhost:8082'
|
||||
};
|
||||
|
||||
let _redirectHandler = null;
|
||||
@@ -49,7 +49,7 @@ const remoteApi = {
|
||||
if (_redirectHandler) {
|
||||
_redirectHandler(); // 如果设置了重定向处理函数,则调用它
|
||||
}
|
||||
return { __isRedirectHandled: true };
|
||||
return {__isRedirectHandled: true};
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -74,74 +74,78 @@ const remoteApi = {
|
||||
}
|
||||
},
|
||||
|
||||
listUsers: async (brokerAddress) => {
|
||||
listUsers: async (brokerName, clusterName) => {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/listUsers?${params.toString()}`));
|
||||
if (brokerName) params.append('brokerName', brokerName);
|
||||
if (clusterName) params.append('clusterName', clusterName);
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/users.query?${params.toString()}`));
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
createUser: async (brokerAddress, userInfo) => {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser'), {
|
||||
createUser: async (brokerName, userInfo, clusterName) => {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser.do'), {
|
||||
method: 'POST',
|
||||
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'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ brokerAddress, userInfo })
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({brokerName, userInfo, clusterName})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
deleteUser: async (brokerAddress, username) => {
|
||||
updateUser: async (brokerName, userInfo, clusterName) => {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateUser.do'), {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({brokerName, userInfo, clusterName})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
deleteUser: async (brokerName, username, clusterName) => {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
if (brokerName) params.append('brokerName', brokerName);
|
||||
if (clusterName) params.append('clusterName', clusterName);
|
||||
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();
|
||||
},
|
||||
|
||||
// --- ACL 权限相关 API ---
|
||||
listAcls: async (brokerAddress, searchParam) => {
|
||||
listAcls: async (brokerName, searchParam, clusterName) => {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
if (brokerName) params.append('brokerName', brokerName);
|
||||
if (clusterName) params.append('clusterName', clusterName);
|
||||
if (searchParam) params.append('searchParam', searchParam);
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/listAcls?${params.toString()}`));
|
||||
if (searchParam != null) console.log(1111)
|
||||
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'), {
|
||||
createAcl: async (brokerName, subject, policies, clusterName) => {
|
||||
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({brokerName, subject, policies, clusterName})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
updateAcl: async (brokerAddress, subject, policies) => {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateAcl'), {
|
||||
updateAcl: async (brokerName, subject, policies, clusterName) => {
|
||||
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({brokerName, subject, policies, clusterName})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
deleteAcl: async (brokerAddress, subject, resource) => {
|
||||
deleteAcl: async (brokerName, subject, resource, clusterName) => {
|
||||
const params = new URLSearchParams();
|
||||
if (brokerAddress) params.append('brokerAddress', brokerAddress);
|
||||
if (brokerName) params.append('brokerAddress', brokerName);
|
||||
params.append('subject', subject);
|
||||
if (resource) params.append('resource', resource);
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl?${params.toString()}`), {
|
||||
if (clusterName) params.append('clusterName', clusterName);
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl.do?${params.toString()}`), {
|
||||
method: 'DELETE'
|
||||
});
|
||||
return await response.json();
|
||||
@@ -159,7 +163,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 +201,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 +221,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 +240,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 +251,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 +271,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"};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -312,7 +316,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) {
|
||||
@@ -351,6 +355,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',
|
||||
@@ -372,29 +377,30 @@ 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"};
|
||||
}
|
||||
},
|
||||
|
||||
refreshConsumerGroup: async (consumerGroup) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/group.refresh?consumerGroup=${consumerGroup}`));
|
||||
const data = await response.json();
|
||||
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 +411,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 +422,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,52 +434,55 @@ 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"};
|
||||
}
|
||||
},
|
||||
|
||||
fetchBrokerNameList: async (consumerGroup) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/fetchBrokerNameList.query?consumerGroup=${consumerGroup}`));
|
||||
const data = await response.json();
|
||||
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}`};
|
||||
}
|
||||
},
|
||||
|
||||
deleteConsumerGroup: async (groupName, brokerNameList) => {
|
||||
groupName = encodeURIComponent(groupName)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/deleteSubGroup.do"), {
|
||||
method: 'POST',
|
||||
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}`};
|
||||
}
|
||||
},
|
||||
|
||||
queryConsumerConfig: async (consumerGroup) => {
|
||||
consumerGroup = encodeURIComponent(consumerGroup)
|
||||
try {
|
||||
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/examineSubscriptionGroupConfig.query?consumerGroup=${consumerGroup}`));
|
||||
const data = await response.json();
|
||||
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,40 +499,43 @@ 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"};
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
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}`};
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
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}`};
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
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 +544,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,57 +561,57 @@ 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"};
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
return { status: 1, errMsg: "Failed to fetch topic stats" };
|
||||
return {status: 1, errMsg: "Failed to fetch topic stats"};
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
return { status: 1, errMsg: "Failed to fetch topic route" };
|
||||
return {status: 1, errMsg: "Failed to fetch topic route"};
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
return { status: 1, errMsg: "Failed to fetch topic consumers" };
|
||||
return {status: 1, errMsg: "Failed to fetch topic consumers"};
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
return { status: 1, errMsg: "Failed to fetch consumer groups" };
|
||||
return {status: 1, errMsg: "Failed to fetch consumer groups"};
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
return { status: 1, errMsg: "Failed to fetch topic config" };
|
||||
return {status: 1, errMsg: "Failed to fetch topic config"};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -609,7 +621,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 +637,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 +653,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 +669,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 +685,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 +696,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 +712,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 +724,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 +736,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 +748,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 +760,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 +771,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 +779,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 +798,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 +837,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 +851,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 +862,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 +894,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 +951,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,47 +15,45 @@
|
||||
* 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 }) => {
|
||||
// 解析传入的 value,将其拆分为 type 和 name
|
||||
const SubjectInput = ({value, onChange, disabled, t}) => {
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
const [currentType, setCurrentType] = useState(() => parseValue(value).type);
|
||||
const [currentName, setCurrentName] = useState(() => parseValue(value).name);
|
||||
|
||||
// 当外部 value 变化时,更新内部状态
|
||||
useEffect(() => {
|
||||
const parsed = parseValue(value);
|
||||
setCurrentType(parsed.type);
|
||||
setCurrentName(parsed.name);
|
||||
}, [value]);
|
||||
|
||||
// 当类型或名称变化时,通知 Form.Item
|
||||
const triggerChange = (changedType, changedName) => {
|
||||
if (onChange) {
|
||||
// 只有当名称不为空时才组合,否则只返回类型或空字符串
|
||||
|
||||
if (changedName) {
|
||||
onChange(`${changedType}:${changedName}`);
|
||||
} else if (changedType) { // 如果只选择了类型,但名称为空,则不组合
|
||||
onChange(''); // 或者根据需求返回 'User:' 等,但通常这种情况下不应该有值
|
||||
} else if (changedType) {
|
||||
onChange('');
|
||||
} else {
|
||||
onChange('');
|
||||
}
|
||||
@@ -76,7 +74,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
return (
|
||||
<Input.Group compact>
|
||||
<Select
|
||||
style={{ width: '30%' }}
|
||||
style={{width: '30%'}}
|
||||
value={currentType}
|
||||
onChange={onTypeChange}
|
||||
disabled={disabled}
|
||||
@@ -88,10 +86,10 @@ const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
style={{ width: '70%' }}
|
||||
style={{width: '70%'}}
|
||||
value={currentName}
|
||||
onChange={onNameChange}
|
||||
placeholder="请输入名称 (例如: yourUsername)"
|
||||
placeholder={t.PLEASE_INPUT_NAME}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Input.Group>
|
||||
|
@@ -15,16 +15,16 @@
|
||||
* 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 {Descriptions, Modal, Spin, Table, Tag, Tooltip} 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, messageApi}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [connectionData, setConnectionData] = useState(null);
|
||||
const [subscriptionData, setSubscriptionData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -33,10 +33,12 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const connResponse = await remoteApi.queryConsumerConnection(group, address);
|
||||
const topicResponse = await remoteApi.queryTopicByConsumer(group, address);
|
||||
|
||||
if (connResponse.status === 0) setConnectionData(connResponse.data);
|
||||
if (topicResponse.status === 0) setSubscriptionData(topicResponse.data);
|
||||
if (connResponse.status === 0) {
|
||||
setConnectionData(connResponse.data);
|
||||
}else{
|
||||
messageApi.error(connResponse.errMsg);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -46,53 +48,118 @@ 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: t.CLIENTID, dataIndex: 'clientId', key: 'clientId', width: 220, ellipsis: true,
|
||||
render: (text) => (
|
||||
<Tooltip title={text}>
|
||||
{text}
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
{title: t.CLIENTADDR, dataIndex: 'clientAddr', key: 'clientAddr', width: 150, ellipsis: true},
|
||||
{title: t.LANGUAGE, dataIndex: 'language', key: 'language', width: 100},
|
||||
{title: t.VERSION, dataIndex: 'versionDesc', key: 'versionDesc', width: 100},
|
||||
];
|
||||
|
||||
const subscriptionColumns = [
|
||||
{ title: 'Topic', dataIndex: 'topic' },
|
||||
{ title: 'SubExpression', dataIndex: 'subString' },
|
||||
{
|
||||
title: t.TOPIC, dataIndex: 'topic', key: 'topic', width: 250, ellipsis: true,
|
||||
render: (text) => (
|
||||
<Tooltip title={text}>
|
||||
{text}
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
{title: t.SUBSCRIPTION_EXPRESSION, dataIndex: 'subString', key: 'subString', width: 150, ellipsis: true},
|
||||
{
|
||||
title: t.EXPRESSION_TYPE, dataIndex: 'expressionType', key: 'expressionType', width: 120,
|
||||
render: (text) => <Tag color="blue">{text}</Tag>
|
||||
},
|
||||
// --- Added Columns for TagsSet and CodeSet ---
|
||||
{
|
||||
title: t.TAGS_SET, // Ensure t.TAGS_SET is defined in your language file
|
||||
dataIndex: 'tagsSet',
|
||||
key: 'tagsSet',
|
||||
width: 150,
|
||||
render: (tags) => (
|
||||
tags && tags.length > 0 ? (
|
||||
<Tooltip title={tags.join(', ')}>
|
||||
{tags.map((tag, index) => (
|
||||
<Tag key={index} color="default">{tag}</Tag>
|
||||
))}
|
||||
</Tooltip>
|
||||
) : 'N/A'
|
||||
),
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t.CODE_SET, // Ensure t.CODE_SET is defined in your language file
|
||||
dataIndex: 'codeSet',
|
||||
key: 'codeSet',
|
||||
width: 150,
|
||||
render: (codes) => (
|
||||
codes && codes.length > 0 ? (
|
||||
<Tooltip title={codes.join(', ')}>
|
||||
{codes.map((code, index) => (
|
||||
<Tag key={index} color="default">{code}</Tag>
|
||||
))}
|
||||
</Tooltip>
|
||||
) : 'N/A'
|
||||
),
|
||||
ellipsis: true,
|
||||
},
|
||||
// --- End of Added Columns ---
|
||||
{title: t.SUB_VERSION, dataIndex: 'subVersion', key: 'subVersion', width: 150},
|
||||
];
|
||||
|
||||
const formattedSubscriptionData = connectionData?.subscriptionTable
|
||||
? Object.keys(connectionData.subscriptionTable).map(key => ({
|
||||
...connectionData.subscriptionTable[key],
|
||||
key: key,
|
||||
}))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`[${group}]${t.CLIENT}`}
|
||||
title={`[${group}] ${t.CLIENT_INFORMATION}`}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
footer={null}
|
||||
width={800}
|
||||
width={1200} // Increased width to accommodate more columns
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
{connectionData && (
|
||||
<>
|
||||
<Descriptions bordered column={2} title={t.CONNECTION_OVERVIEW} style={{marginBottom: 20}}>
|
||||
<Descriptions.Item label={t.CONSUME_TYPE}>
|
||||
<Tag color="green">{connectionData.consumeType}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t.MESSAGE_MODEL}>
|
||||
<Tag color="geekblue">{connectionData.messageModel}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t.CONSUME_FROM_WHERE}>
|
||||
<Tag color="purple">{connectionData.consumeFromWhere}</Tag>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<h3>{t.CLIENT_CONNECTIONS}</h3>
|
||||
<Table
|
||||
columns={connectionColumns}
|
||||
dataSource={connectionData.connectionSet}
|
||||
rowKey="clientId"
|
||||
pagination={false}
|
||||
scroll={{x: 'max-content'}}
|
||||
style={{marginBottom: 20}}
|
||||
/>
|
||||
<h4>{t.SUBSCRIPTION}</h4>
|
||||
|
||||
<h3>{t.CLIENT_SUBSCRIPTIONS}</h3>
|
||||
<Table
|
||||
columns={subscriptionColumns}
|
||||
dataSource={
|
||||
subscriptionData?.subscriptionTable
|
||||
? Object.entries(subscriptionData.subscriptionTable).map(([topic, detail]) => ({
|
||||
topic,
|
||||
...detail,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
rowKey="topic"
|
||||
dataSource={formattedSubscriptionData}
|
||||
rowKey="key"
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: loading ? <Spin size="small" /> : t.NO_DATA
|
||||
}}
|
||||
scroll={{x: 'max-content'}}
|
||||
/>
|
||||
<p>ConsumeType: {connectionData.consumeType}</p>
|
||||
<p>MessageModel: {connectionData.messageModel}</p>
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
|
@@ -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 ,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);
|
||||
@@ -43,32 +47,88 @@ const ConsumerDetailModal = ({ visible, group, address, onCancel }) => {
|
||||
fetchData();
|
||||
}, [visible, group, address]);
|
||||
|
||||
// Format timestamp to readable date
|
||||
const formatTimestamp = (timestamp) => {
|
||||
if (!timestamp || timestamp === 0) return '-';
|
||||
return new Date(timestamp).toLocaleString();
|
||||
};
|
||||
|
||||
// Group data by topic for better organization
|
||||
const groupByTopic = (data) => {
|
||||
const grouped = {};
|
||||
data.forEach(item => {
|
||||
if (!grouped[item.topic]) {
|
||||
grouped[item.topic] = [];
|
||||
}
|
||||
grouped[item.topic].push(item);
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
|
||||
const groupedDetails = groupByTopic(details);
|
||||
|
||||
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', width: 120},
|
||||
{title: 'Queue ID', dataIndex: 'queueId', width: 100},
|
||||
{title: 'Broker Offset', dataIndex: 'brokerOffset', width: 120},
|
||||
{title: 'Consumer Offset', dataIndex: 'consumerOffset', width: 120},
|
||||
{
|
||||
title: 'Lag (Diff)', dataIndex: 'diffTotal', width: 100,
|
||||
render: (diff) => (
|
||||
<span style={{color: diff > 0 ? '#f5222d' : '#52c41a'}}>
|
||||
{diff}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{title: 'Client Info', dataIndex: 'clientInfo', width: 200},
|
||||
{
|
||||
title: 'Last Consume Time', dataIndex: 'lastTimestamp', width: 180,
|
||||
render: (timestamp) => formatTimestamp(timestamp)
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`[${group}]${t.CONSUME_DETAIL}`}
|
||||
title={
|
||||
<span>Consumer Details - Group: <strong>{group}</strong> | Address: <strong>{address}</strong></span>}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
footer={null}
|
||||
width={1200}
|
||||
width={1400}
|
||||
style={{top: 20}}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
{details.map((consumeDetail, index) => (
|
||||
<div key={index}>
|
||||
<Table
|
||||
columns={queueColumns}
|
||||
dataSource={consumeDetail.queueStatInfoList}
|
||||
rowKey="queueId"
|
||||
pagination={false}
|
||||
/>
|
||||
{Object.entries(groupedDetails).map(([topic, topicDetails]) => (
|
||||
<div key={topic} style={{marginBottom: 24}}>
|
||||
<div style={{
|
||||
background: '#f0f0f0',
|
||||
padding: '8px 16px',
|
||||
marginBottom: 8,
|
||||
borderRadius: 4,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<h3 style={{margin: 0}}>Topic: <strong>{topic}</strong></h3>
|
||||
<div>
|
||||
<span style={{marginRight: 16}}>Total Lag: <strong>{topicDetails[0].diffTotal}</strong></span>
|
||||
<span>Last Consume Time: <strong>{formatTimestamp(topicDetails[0].lastTimestamp)}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{topicDetails.map((detail, index) => (
|
||||
<div key={index} style={{marginBottom: 16}}>
|
||||
<Table
|
||||
columns={queueColumns}
|
||||
dataSource={detail.queueStatInfoList}
|
||||
rowKey={(record) => `${record.brokerName}-${record.queueId}`}
|
||||
pagination={false}
|
||||
size="small"
|
||||
bordered
|
||||
scroll={{x: 'max-content'}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</Spin>
|
||||
|
@@ -15,18 +15,15 @@
|
||||
* 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';
|
||||
|
||||
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,10 +42,9 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
fetchBrokers();
|
||||
}, [visible, group]);
|
||||
|
||||
// 处理删除提交
|
||||
const handleDelete = async () => {
|
||||
if (selectedBrokers.length === 0) {
|
||||
notification.warning({ message: t.PLEASE_SELECT_BROKER });
|
||||
notification.warning({message: t.PLEASE_SELECT_BROKER});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,7 +56,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 +86,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,35 @@ 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": "今天接收的总消息数",
|
||||
"LOGIN_SUCCESS": "登录成功",
|
||||
"LOGIN_FAILED": "登录失败",
|
||||
"USERNAME_REQUIRED": "用户名为必填项",
|
||||
"USERNAME_PLACEHOLDER": "用户名",
|
||||
"PASSWORD_REQUIRED": "密码为必填项",
|
||||
"PASSWORD_PLACEHOLDER": "密码",
|
||||
"PLEASE_INPUT_NAME":"请输入名称",
|
||||
"PLEASE_SELECT_CLUSTER": "请选择集群",
|
||||
"CLIENT_INFORMATION": "客户端信息",
|
||||
"CONSUME_TYPE": "消费类型",
|
||||
"MESSAGE_MODEL": "消息模型",
|
||||
"CONSUME_FROM_WHERE": "从何处消费",
|
||||
"CLIENT_CONNECTIONS": "客户端连接",
|
||||
"CLIENT_SUBSCRIPTIONS": "客户端订阅",
|
||||
"CONNECTION_OVERVIEW": "连接概览",
|
||||
"CLIENTID": "客户端 ID",
|
||||
"CLIENTADDR": "客户端地址",
|
||||
"LANGUAGE": "语言",
|
||||
"SUBSCRIPTION_EXPRESSION": "订阅表达式",
|
||||
"EXPRESSION_TYPE": "表达式类型",
|
||||
"SUB_VERSION": "订阅版本",
|
||||
"CODE_SET": "代码集",
|
||||
"TAGS_SET": "标签集"
|
||||
},
|
||||
en: {
|
||||
"DEFAULT": "Default",
|
||||
@@ -294,7 +322,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",
|
||||
@@ -335,16 +363,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 +393,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": "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,7 +563,39 @@ 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",
|
||||
"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",
|
||||
"PLEASE_INPUT_NAME": "Please input name",
|
||||
"PLEASE_SELECT_CLUSTER": "Please select cluster",
|
||||
"SUBSCRIPTION": "Subscription",
|
||||
"CLIENT_INFORMATION": "Client Information",
|
||||
"CONSUME_TYPE": "Consume Type",
|
||||
"MESSAGE_MODEL": "Message Model",
|
||||
"CONSUME_FROM_WHERE": "Consume From Where",
|
||||
"CLIENT_CONNECTIONS": "Client Connections",
|
||||
"CLIENT_SUBSCRIPTIONS": "Client Subscriptions",
|
||||
"CONNECTION_OVERVIEW": "Connection Overview",
|
||||
"CLIENTID": "Client ID",
|
||||
"CLIENTADDR": "Client Address",
|
||||
"LANGUAGE": "Language",
|
||||
"SUBSCRIPTION_EXPRESSION": "Subscription Expression",
|
||||
"EXPRESSION_TYPE": "Expression Type",
|
||||
"SUB_VERSION": "Sub Version",
|
||||
"CODE_SET": "Code 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",
|
||||
}
|
||||
|
||||
};
|
||||
|
@@ -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
|
||||
|
@@ -15,32 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
Input,
|
||||
Tabs,
|
||||
Modal,
|
||||
Form,
|
||||
message,
|
||||
Space,
|
||||
Tag,
|
||||
Popconfirm,
|
||||
Select
|
||||
} from 'antd';
|
||||
import {
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
EyeOutlined,
|
||||
EyeInvisibleOutlined
|
||||
} from '@ant-design/icons';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Form, Input, message, Modal, Popconfirm, Select, Space, Table, Tabs, Tag} from 'antd';
|
||||
import {DeleteOutlined, EditOutlined, EyeInvisibleOutlined, EyeOutlined} from '@ant-design/icons';
|
||||
import {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||
import ResourceInput from '../../components/acl/ResourceInput';
|
||||
import SubjectInput from "../../components/acl/SubjectInput";
|
||||
import {useLanguage} from "../../i18n/LanguageContext";
|
||||
const { TabPane } = Tabs;
|
||||
const { Search } = Input;
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
const {Search} = Input;
|
||||
|
||||
const Acl = () => {
|
||||
const [activeTab, setActiveTab] = useState('users');
|
||||
@@ -55,6 +39,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();
|
||||
@@ -83,17 +68,19 @@ const Acl = () => {
|
||||
// State for the address of the selected broker
|
||||
const [brokerAddress, setBrokerAddress] = useState(undefined);
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
// --- Data Fetching and Initial Setup ---
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const clusterResponse = await remoteApi.getClusterList();
|
||||
if (clusterResponse.status === 0 && clusterResponse.data) {
|
||||
const { clusterInfo } = clusterResponse.data;
|
||||
const {clusterInfo} = clusterResponse.data;
|
||||
setClusterData(clusterInfo); // Store the entire clusterInfo
|
||||
|
||||
// Populate cluster names for the first dropdown
|
||||
const clusterNames = Object.keys(clusterInfo?.clusterAddrTable || {});
|
||||
setClusterNamesOptions(clusterNames.map(name => ({ label: name, value: name })));
|
||||
setClusterNamesOptions(clusterNames.map(name => ({label: name, value: name})));
|
||||
|
||||
// Set initial selections if clusters are available
|
||||
if (clusterNames.length > 0) {
|
||||
@@ -118,20 +105,29 @@ const Acl = () => {
|
||||
console.error('Failed to fetch cluster list:', clusterResponse.errMsg);
|
||||
}
|
||||
};
|
||||
if(!clusterData){
|
||||
fetchData();
|
||||
if (!clusterData) {
|
||||
setLoading(true);
|
||||
fetchData().finally(() => setLoading(false));
|
||||
}
|
||||
if(brokerAddress){
|
||||
// Call fetchUsers or fetchAcls based on activeTab initially
|
||||
if (brokerAddress) {
|
||||
if (activeTab === 'users') {
|
||||
fetchUsers();
|
||||
fetchUsers().finally(() => setLoading(false));
|
||||
} else {
|
||||
fetchAcls();
|
||||
fetchAcls().finally(() => setLoading(false));
|
||||
}
|
||||
}
|
||||
|
||||
}, [activeTab]); // Dependencies for useEffect
|
||||
|
||||
useEffect(() => {
|
||||
const userPermission = localStorage.getItem('userrole');
|
||||
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) {
|
||||
@@ -139,7 +135,7 @@ const Acl = () => {
|
||||
return;
|
||||
}
|
||||
const brokersInCluster = info.clusterAddrTable[clusterName] || [];
|
||||
setBrokerNamesOptions(brokersInCluster.map(broker => ({ label: broker, value: broker })));
|
||||
setBrokerNamesOptions(brokersInCluster.map(broker => ({label: broker, value: broker})));
|
||||
};
|
||||
|
||||
// --- Event Handlers ---
|
||||
@@ -163,12 +159,6 @@ const Acl = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// --- Log selected values for debugging (optional) ---
|
||||
useEffect(() => {
|
||||
console.log('Selected Cluster:', selectedCluster);
|
||||
console.log('Selected Broker:', selectedBroker);
|
||||
console.log('Broker Address:', brokerAddress);
|
||||
}, [selectedCluster, selectedBroker, brokerAddress]);
|
||||
const handleIpChange = value => {
|
||||
// 过滤掉重复的IP地址
|
||||
const uniqueIps = Array.from(new Set(value));
|
||||
@@ -186,7 +176,7 @@ const Acl = () => {
|
||||
}
|
||||
const invalidIps = value.filter(ip => !ipRegex.test(ip));
|
||||
if (invalidIps.length > 0) {
|
||||
return Promise.reject(t.INVALID_IP_ADDRESSES +"ips:" + invalidIps.join(', '));
|
||||
return Promise.reject(t.INVALID_IP_ADDRESSES + "ips:" + invalidIps.join(', '));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
@@ -195,7 +185,7 @@ const Acl = () => {
|
||||
const fetchUsers = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await remoteApi.listUsers(brokerAddress);
|
||||
const result = await remoteApi.listUsers(selectedBroker, selectedCluster);
|
||||
if (result && result.status === 0 && result.data) {
|
||||
const formattedUsers = result.data.map(user => ({
|
||||
...user,
|
||||
@@ -204,7 +194,7 @@ const Acl = () => {
|
||||
}));
|
||||
setUserListData(formattedUsers);
|
||||
} else {
|
||||
messageApi.error(t.GET_USERS_FAILED+result?.errMsg);
|
||||
messageApi.error(t.GET_USERS_FAILED + result?.errMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch users:", error);
|
||||
@@ -214,10 +204,10 @@ const Acl = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAcls = async (value) => {
|
||||
const fetchAcls = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await remoteApi.listAcls(brokerAddress, value);
|
||||
const result = await remoteApi.listAcls(selectedBroker, searchValue, selectedCluster);
|
||||
if (result && result.status === 0) {
|
||||
const formattedAcls = [];
|
||||
|
||||
@@ -234,7 +224,6 @@ const Acl = () => {
|
||||
const resources = Array.isArray(entry.resource) ? entry.resource : (entry.resource ? [entry.resource] : []);
|
||||
|
||||
resources.forEach((singleResource, resourceIndex) => {
|
||||
console.log(singleResource)
|
||||
formattedAcls.push({
|
||||
key: `acl-${aclIndex}-policy-${policyIndex}-entry-${entryIndex}-resource-${singleResource}`,
|
||||
subject: subject,
|
||||
@@ -290,10 +279,10 @@ const Acl = () => {
|
||||
const handleDeleteUser = async (username) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await remoteApi.deleteUser(brokerAddress, username);
|
||||
const result = await remoteApi.deleteUser(selectedBroker, username, selectedCluster);
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.USER_DELETE_SUCCESS);
|
||||
fetchUsers(brokerAddress);
|
||||
fetchUsers();
|
||||
} else {
|
||||
messageApi.error(t.USER_DELETE_FAILED + result.errMsg);
|
||||
}
|
||||
@@ -319,14 +308,14 @@ const Acl = () => {
|
||||
};
|
||||
|
||||
if (currentUser) {
|
||||
result = await remoteApi.updateUser(brokerAddress, userInfoParam);
|
||||
result = await remoteApi.updateUser(selectedBroker, userInfoParam, selectedCluster);
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.USER_UPDATE_SUCCESS);
|
||||
} else {
|
||||
messageApi.error(result.errMsg);
|
||||
}
|
||||
} else {
|
||||
result = await remoteApi.createUser(brokerAddress, userInfoParam);
|
||||
result = await remoteApi.createUser(selectedBroker, userInfoParam, selectedCluster);
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.USER_CREATE_SUCCESS);
|
||||
} else {
|
||||
@@ -368,12 +357,12 @@ const Acl = () => {
|
||||
const handleDeleteAcl = async (subject, resource) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await remoteApi.deleteAcl(brokerAddress, subject, resource);
|
||||
const result = await remoteApi.deleteAcl(selectedBroker, subject, resource, selectedCluster);
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.ACL_DELETE_SUCCESS);
|
||||
fetchAcls();
|
||||
} else {
|
||||
messageApi.error(t.ACL_DELETE_FAILED+result.errMsg);
|
||||
messageApi.error(t.ACL_DELETE_FAILED + result.errMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete ACL:", error);
|
||||
@@ -404,24 +393,23 @@ const Acl = () => {
|
||||
];
|
||||
|
||||
if (isUpdate) { // This condition seems reversed for update/create based on the current logic.
|
||||
result = await remoteApi.updateAcl(brokerAddress, values.subject, policiesParam);
|
||||
result = await remoteApi.updateAcl(selectedBroker, values.subject, policiesParam, selectedCluster);
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.ACL_UPDATE_SUCCESS);
|
||||
setIsAclModalVisible(false);
|
||||
fetchAcls(brokerAddress);
|
||||
fetchAcls();
|
||||
} else {
|
||||
messageApi.error(t.ACL_UPDATE_FAILED+result.errMsg);
|
||||
messageApi.error(t.ACL_UPDATE_FAILED + result.errMsg);
|
||||
}
|
||||
setIsUpdate(false)
|
||||
} else {
|
||||
result = await remoteApi.createAcl(brokerAddress, values.subject, policiesParam);
|
||||
console.log(result)
|
||||
result = await remoteApi.createAcl(selectedBroker, values.subject, policiesParam, selectedCluster);
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.ACL_CREATE_SUCCESS);
|
||||
setIsAclModalVisible(false);
|
||||
fetchAcls(brokerAddress);
|
||||
fetchAcls();
|
||||
} else {
|
||||
messageApi.error(t.ACL_CREATE_FAILED+result.errMsg);
|
||||
messageApi.error(t.ACL_CREATE_FAILED + result.errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +421,10 @@ const Acl = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
};
|
||||
|
||||
// --- Search Functionality ---
|
||||
|
||||
const handleSearch = (value) => {
|
||||
@@ -448,7 +440,7 @@ const Acl = () => {
|
||||
setUserListData(filteredData);
|
||||
}
|
||||
} else {
|
||||
fetchAcls(value);
|
||||
fetchAcls();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -469,9 +461,9 @@ const Acl = () => {
|
||||
{showPassword ? text : '********'}
|
||||
<Button
|
||||
type="link"
|
||||
icon={showPassword ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
||||
icon={showPassword ? <EyeInvisibleOutlined/> : <EyeOutlined/>}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
style={{ marginLeft: 8 }}
|
||||
style={{marginLeft: 8}}
|
||||
>
|
||||
{showPassword ? t.HIDE : t.VIEW}
|
||||
</Button>
|
||||
@@ -488,24 +480,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,248 +546,253 @@ 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
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{msgContextHolder}
|
||||
<div style={{padding: 24}}>
|
||||
<h2>{t.ACL_MANAGEMENT}</h2>
|
||||
return (
|
||||
<>
|
||||
{msgContextHolder}
|
||||
<div style={{padding: 24}}>
|
||||
<h2>{t.ACL_MANAGEMENT}</h2>
|
||||
|
||||
<div style={{ marginBottom: 16, display: 'flex', gap: 16 }}>
|
||||
<Form.Item label={t.PLEASE_SELECT_CLUSTER} style={{ marginBottom: 0 }}>
|
||||
<Select
|
||||
placeholder={t.PLEASE_SELECT_CLUSTER}
|
||||
style={{ width: 200 }}
|
||||
onChange={handleClusterChange}
|
||||
value={selectedCluster}
|
||||
options={clusterNamesOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.PLEASE_SELECT_BROKER} style={{ marginBottom: 0 }}>
|
||||
<Select
|
||||
placeholder={t.PLEASE_SELECT_BROKER}
|
||||
style={{ width: 200 }}
|
||||
onChange={handleBrokerChange}
|
||||
value={selectedBroker}
|
||||
options={brokerNamesOptions} // Now dynamically updated
|
||||
disabled={!selectedCluster} // Disable broker selection if no cluster is chosen
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button type="primary" onClick={activeTab === 'users' ? fetchUsers : fetchAcls}>
|
||||
{t.CONFIRM}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
||||
<TabPane tab={t.ACL_USERS} key="users"/>
|
||||
<TabPane tab={t.ACL_PERMISSIONS} key="acls"/>
|
||||
</Tabs>
|
||||
|
||||
<div style={{marginBottom: 16, display: 'flex', justifyContent: 'space-between'}}>
|
||||
<Button type="primary" onClick={activeTab === 'users' ? handleAddUser : handleAddAcl}>
|
||||
{activeTab === 'users' ? t.ADD_USER : t.ADD_ACL_PERMISSION}
|
||||
</Button>
|
||||
<Search
|
||||
placeholder={t.SEARCH_PLACEHOLDER}
|
||||
allowClear
|
||||
onSearch={handleSearch}
|
||||
style={{width: 300}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{activeTab === 'users' && (
|
||||
<Table
|
||||
columns={userColumns}
|
||||
dataSource={userListData}
|
||||
loading={loading}
|
||||
pagination={{pageSize: 10}}
|
||||
rowKey="username"
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'acls' && (
|
||||
<Table
|
||||
columns={aclColumns}
|
||||
dataSource={aclListData}
|
||||
loading={loading}
|
||||
pagination={{pageSize: 10}}
|
||||
rowKey="key"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* User Management Modal */}
|
||||
<Modal
|
||||
title={currentUser ? t.EDIT_USER : t.ADD_USER}
|
||||
visible={isUserModalVisible}
|
||||
onOk={handleUserModalOk}
|
||||
onCancel={() => setIsUserModalVisible(false)}
|
||||
confirmLoading={loading}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setIsUserModalVisible(false)}>
|
||||
{t.CANCEL}
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" onClick={handleUserModalOk} loading={loading}>
|
||||
{t.CONFIRM}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
form={userForm}
|
||||
layout="vertical"
|
||||
name="user_form"
|
||||
initialValues={{userStatus: 'enable'}}
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t.USERNAME}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_USERNAME}]}
|
||||
>
|
||||
<Input disabled={!!currentUser}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={t.PASSWORD}
|
||||
rules={[{required: !currentUser, message: t.PLEASE_ENTER_PASSWORD}]}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder={t.PASSWORD}
|
||||
iconRender={visible => (visible ? <EyeOutlined/> : <EyeInvisibleOutlined/>)}
|
||||
<div style={{marginBottom: 16, display: 'flex', gap: 16}}>
|
||||
<Form.Item label={t.PLEASE_SELECT_CLUSTER} style={{marginBottom: 0}}>
|
||||
<Select
|
||||
placeholder={t.PLEASE_SELECT_CLUSTER}
|
||||
style={{width: 200}}
|
||||
onChange={handleClusterChange}
|
||||
value={selectedCluster}
|
||||
options={clusterNamesOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="userType"
|
||||
label={t.USER_TYPE}
|
||||
rules={[{required: true, message: t.PLEASE_SELECT_USER_TYPE}]}
|
||||
>
|
||||
<Select mode="single" placeholder="Super, Normal" style={{width: '100%'}}>
|
||||
<Select.Option value="Super">Super</Select.Option>
|
||||
<Select.Option value="Normal">Normal</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="userStatus"
|
||||
label={t.USER_STATUS}
|
||||
rules={[{required: true, message: t.PLEASE_SELECT_USER_STATUS}]}
|
||||
>
|
||||
<Select mode="single" placeholder="enable, disable" style={{width: '100%'}}>
|
||||
<Select.Option value="enable">enable</Select.Option>
|
||||
<Select.Option value="disable">disable</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* ACL Permission Management Modal */}
|
||||
<Modal
|
||||
title={currentAcl ? t.EDIT_ACL_PERMISSION : t.ADD_ACL_PERMISSION}
|
||||
visible={isAclModalVisible}
|
||||
onOk={handleAclModalOk}
|
||||
onCancel={() => setIsAclModalVisible(false)}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={aclForm}
|
||||
layout="vertical"
|
||||
name="acl_form"
|
||||
>
|
||||
<Form.Item
|
||||
name="subject"
|
||||
label={t.SUBJECT_LABEL}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_SUBJECT}]}
|
||||
>
|
||||
<SubjectInput disabled={!!currentAcl}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="policyType"
|
||||
label={t.POLICY_TYPE}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_POLICY_TYPE}]}
|
||||
>
|
||||
<Select mode="single" disabled={isUpdate} placeholder="policyType" style={{width: '100%'}}>
|
||||
<Select.Option value="Custom">Custom</Select.Option>
|
||||
<Select.Option value="Default">Default</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="resource"
|
||||
label={t.RESOURCE}
|
||||
rules={[{required: true, message: t.PLEASE_ADD_RESOURCE}]}
|
||||
>
|
||||
{isUpdate ? (
|
||||
<Input disabled={isUpdate}/>
|
||||
) : (
|
||||
<ResourceInput/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="actions"
|
||||
label={t.OPERATION_TYPE}
|
||||
>
|
||||
<Select mode="multiple" placeholder="action" style={{width: '100%'}}>
|
||||
<Select.Option value="All">All</Select.Option>
|
||||
<Select.Option value="Pub">Pub</Select.Option>
|
||||
<Select.Option value="Sub">Sub</Select.Option>
|
||||
<Select.Option value="Create">Create</Select.Option>
|
||||
<Select.Option value="Update">Update</Select.Option>
|
||||
<Select.Option value="Delete">Delete</Select.Option>
|
||||
<Select.Option value="Get">Get</Select.Option>
|
||||
<Select.Option value="List">List</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="sourceIps"
|
||||
label={t.SOURCE_IP}
|
||||
rules={[
|
||||
{
|
||||
validator: validateIp,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item label={t.PLEASE_SELECT_BROKER} style={{marginBottom: 0}}>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{width: '100%'}}
|
||||
placeholder={t.ENTER_IP_HINT}
|
||||
onChange={handleIpChange}
|
||||
onDeselect={handleIpDeselect}
|
||||
value={ips}
|
||||
tokenSeparators={[',', ' ']}
|
||||
>
|
||||
<Select.Option value="192.168.1.1">192.168.1.1</Select.Option>
|
||||
<Select.Option value="0.0.0.0">0.0.0.0</Select.Option>
|
||||
<Select.Option value="127.0.0.1">127.0.0.1</Select.Option>
|
||||
</Select>
|
||||
placeholder={t.PLEASE_SELECT_BROKER}
|
||||
style={{width: 200}}
|
||||
onChange={handleBrokerChange}
|
||||
value={selectedBroker}
|
||||
options={brokerNamesOptions}
|
||||
disabled={!selectedCluster}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="decision"
|
||||
label={t.DECISION}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_DECISION}]}
|
||||
<Button type="primary" onClick={activeTab === 'users' ? fetchUsers : fetchAcls}>
|
||||
{t.CONFIRM}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
||||
<TabPane tab={t.ACL_USERS} key="users"/>
|
||||
<TabPane tab={t.ACL_PERMISSIONS} key="acls"/>
|
||||
</Tabs>
|
||||
|
||||
<div style={{marginBottom: 16, display: 'flex', justifyContent: 'space-between'}}>
|
||||
<Button type="primary" onClick={activeTab === 'users' ? handleAddUser : handleAddAcl}>
|
||||
{activeTab === 'users' ? t.ADD_USER : t.ADD_ACL_PERMISSION}
|
||||
</Button>
|
||||
<Search
|
||||
placeholder={t.SEARCH_PLACEHOLDER}
|
||||
allowClear
|
||||
onSearch={handleSearch}
|
||||
value={searchValue}
|
||||
onChange={handleInputChange}
|
||||
style={{width: 300}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{activeTab === 'users' && (
|
||||
<Table
|
||||
columns={userColumns}
|
||||
dataSource={userListData}
|
||||
loading={loading}
|
||||
pagination={{pageSize: 10}}
|
||||
rowKey="username"
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'acls' && (
|
||||
<Table
|
||||
columns={aclColumns}
|
||||
dataSource={aclListData}
|
||||
loading={loading}
|
||||
pagination={{pageSize: 10}}
|
||||
rowKey="key"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* User Management Modal */}
|
||||
<Modal
|
||||
title={currentUser ? t.EDIT_USER : t.ADD_USER}
|
||||
visible={isUserModalVisible}
|
||||
onOk={handleUserModalOk}
|
||||
onCancel={() => setIsUserModalVisible(false)}
|
||||
confirmLoading={loading}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setIsUserModalVisible(false)}>
|
||||
{t.CANCEL}
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" onClick={handleUserModalOk} loading={loading}>
|
||||
{t.CONFIRM}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
form={userForm}
|
||||
layout="vertical"
|
||||
name="user_form"
|
||||
initialValues={{userStatus: 'enable'}}
|
||||
>
|
||||
<Select mode="single" placeholder="Allow, Deny" style={{width: '100%'}}>
|
||||
<Select.Option value="Allow">Allow</Select.Option>
|
||||
<Select.Option value="Deny">Deny</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t.USERNAME}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_USERNAME}]}
|
||||
>
|
||||
<Input disabled={!!currentUser}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={t.PASSWORD}
|
||||
rules={[{required: !currentUser, message: t.PLEASE_ENTER_PASSWORD}]}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder={t.PASSWORD}
|
||||
iconRender={visible => (visible ? <EyeOutlined/> : <EyeInvisibleOutlined/>)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="userType"
|
||||
label={t.USER_TYPE}
|
||||
rules={[{required: true, message: t.PLEASE_SELECT_USER_TYPE}]}
|
||||
>
|
||||
<Select mode="single" placeholder="Super, Normal" style={{width: '100%'}}>
|
||||
<Select.Option value="Super">Super</Select.Option>
|
||||
<Select.Option value="Normal">Normal</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="userStatus"
|
||||
label={t.USER_STATUS}
|
||||
rules={[{required: true, message: t.PLEASE_SELECT_USER_STATUS}]}
|
||||
>
|
||||
<Select mode="single" placeholder="enable, disable" style={{width: '100%'}}>
|
||||
<Select.Option value="enable">enable</Select.Option>
|
||||
<Select.Option value="disable">disable</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* ACL Permission Management Modal */}
|
||||
<Modal
|
||||
title={currentAcl ? t.EDIT_ACL_PERMISSION : t.ADD_ACL_PERMISSION}
|
||||
visible={isAclModalVisible}
|
||||
onOk={handleAclModalOk}
|
||||
onCancel={() => setIsAclModalVisible(false)}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={aclForm}
|
||||
layout="vertical"
|
||||
name="acl_form"
|
||||
>
|
||||
<Form.Item
|
||||
name="subject"
|
||||
label={t.SUBJECT_LABEL}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_SUBJECT}]}
|
||||
>
|
||||
<SubjectInput disabled={!!currentAcl} t={t}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="policyType"
|
||||
label={t.POLICY_TYPE}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_POLICY_TYPE}]}
|
||||
>
|
||||
<Select mode="single" disabled={isUpdate} placeholder="policyType" style={{width: '100%'}}>
|
||||
<Select.Option value="Custom">Custom</Select.Option>
|
||||
<Select.Option value="Default">Default</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="resource"
|
||||
label={t.RESOURCE}
|
||||
rules={[{required: true, message: t.PLEASE_ADD_RESOURCE}]}
|
||||
>
|
||||
{isUpdate ? (
|
||||
<Input disabled={isUpdate}/>
|
||||
) : (
|
||||
<ResourceInput/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="actions"
|
||||
label={t.OPERATION_TYPE}
|
||||
>
|
||||
<Select mode="multiple" placeholder="action" style={{width: '100%'}}>
|
||||
<Select.Option value="All">All</Select.Option>
|
||||
<Select.Option value="Pub">Pub</Select.Option>
|
||||
<Select.Option value="Sub">Sub</Select.Option>
|
||||
<Select.Option value="Create">Create</Select.Option>
|
||||
<Select.Option value="Update">Update</Select.Option>
|
||||
<Select.Option value="Delete">Delete</Select.Option>
|
||||
<Select.Option value="Get">Get</Select.Option>
|
||||
<Select.Option value="List">List</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="sourceIps"
|
||||
label={t.SOURCE_IP}
|
||||
rules={[
|
||||
{
|
||||
validator: validateIp,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{width: '100%'}}
|
||||
placeholder={t.ENTER_IP_HINT}
|
||||
onChange={handleIpChange}
|
||||
onDeselect={handleIpDeselect}
|
||||
value={ips}
|
||||
tokenSeparators={[',', ' ']}
|
||||
>
|
||||
<Select.Option value="192.168.1.1">192.168.1.1</Select.Option>
|
||||
<Select.Option value="0.0.0.0">0.0.0.0</Select.Option>
|
||||
<Select.Option value="127.0.0.1">127.0.0.1</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="decision"
|
||||
label={t.DECISION}
|
||||
rules={[{required: true, message: t.PLEASE_ENTER_DECISION}]}
|
||||
>
|
||||
<Select mode="single" placeholder="Allow, Deny" style={{width: '100%'}}>
|
||||
<Select.Option value="Allow">Allow</Select.Option>
|
||||
<Select.Option value="Deny">Deny</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Acl;
|
||||
|
@@ -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,24 +81,29 @@ 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 {
|
||||
messageApi.error({title: t.ERROR, content: response.errMsg});
|
||||
}
|
||||
} catch (error) {
|
||||
messageApi.error({title: t.ERROR, content: t.FAILED_TO_FETCH_DATA});
|
||||
console.error("Error loading consumer groups:", error);
|
||||
messageApi.error({title: t.ERROR, content: t.FAILED_TO_FETCH_DATA});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
}, [t, proxyEnabled, selectedProxy, messageApi, setAllConsumerGroupList, remoteApi, setLoading]);
|
||||
|
||||
const filterByType = (str, type, version) => {
|
||||
if (filterSystem && type === "SYSTEM") return true;
|
||||
@@ -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]);
|
||||
@@ -165,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);
|
||||
@@ -212,7 +288,6 @@ const ConsumerGroupList = () => {
|
||||
setShowConfig(true);
|
||||
};
|
||||
|
||||
// 修改操作按钮的点击处理函数
|
||||
const handleClient = (group, address) => {
|
||||
setSelectedGroup(group);
|
||||
setSelectedAddress(address);
|
||||
@@ -380,7 +455,7 @@ const ConsumerGroupList = () => {
|
||||
filterList(pagination.current, allConsumerGroupList);
|
||||
};
|
||||
|
||||
const closeConfigModal = () =>{
|
||||
const closeConfigModal = () => {
|
||||
setShowConfig(false);
|
||||
setIsAddConfig(false);
|
||||
}
|
||||
@@ -391,14 +466,22 @@ const ConsumerGroupList = () => {
|
||||
{notificationContextHolder}
|
||||
<div style={{padding: '20px'}}>
|
||||
<Spin spinning={loading} tip={t.LOADING}>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '15px'}}>
|
||||
<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'}}>{t.SUBSCRIPTION_GROUP}:</label>
|
||||
<label
|
||||
style={{marginRight: '8px', whiteSpace: 'nowrap'}}>{t.SUBSCRIPTION_GROUP}:</label>
|
||||
<Input
|
||||
style={{width: '200px'}}
|
||||
value={filterStr}
|
||||
onChange={(e) => handleFilterInputChange(e.target.value)}
|
||||
placeholder="输入订阅组名称"
|
||||
/>
|
||||
</div>
|
||||
<Checkbox checked={filterNormal}
|
||||
@@ -423,12 +506,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>
|
||||
|
||||
@@ -448,6 +554,7 @@ const ConsumerGroupList = () => {
|
||||
group={selectedGroup}
|
||||
address={selectedAddress}
|
||||
onCancel={() => setShowClientInfo(false)}
|
||||
messageApi={messageApi}
|
||||
/>
|
||||
|
||||
<ConsumerDetailModal
|
||||
@@ -455,6 +562,7 @@ const ConsumerGroupList = () => {
|
||||
group={selectedGroup}
|
||||
address={selectedAddress}
|
||||
onCancel={() => setShowConsumeDetail(false)}
|
||||
messageApi={messageApi}
|
||||
/>
|
||||
|
||||
<ConsumerConfigModal
|
||||
@@ -471,6 +579,7 @@ const ConsumerGroupList = () => {
|
||||
group={selectedGroup}
|
||||
onCancel={() => setShowDeleteModal(false)}
|
||||
onSuccess={loadConsumerGroups}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@@ -250,17 +250,16 @@ const DashboardPage = () => {
|
||||
const brokerAddrTable = resp.data.clusterInfo.brokerAddrTable; // Corrected to brokerAddrTable
|
||||
const brokerDetail = resp.data.brokerServer;
|
||||
const clusterMap = tools.generateBrokerMap(brokerDetail, clusterAddrTable, 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,
|
||||
}));
|
||||
setBrokerTableData(newData);
|
||||
|
||||
brokerArray.sort((firstBroker, lastBroker) => {
|
||||
const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0);
|
||||
@@ -347,7 +346,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',
|
||||
|
@@ -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))
|
||||
@@ -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,25 +16,26 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Form, Input, Button, message, Typography } from 'antd';
|
||||
import {Button, Form, Input, message, Typography} from 'antd';
|
||||
import {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||
import {useLanguage} from "../../i18n/LanguageContext";
|
||||
|
||||
const { Title } = Typography;
|
||||
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;
|
||||
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>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -33,10 +33,9 @@ const MessageQueryPage = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Topic 查询状态
|
||||
const [allTopicList, setAllTopicList] = useState([]);
|
||||
const [selectedTopic, setSelectedTopic] = useState(null);
|
||||
const [timepickerBegin, setTimepickerBegin] = useState(moment().subtract(1, 'hour')); // 默认一小时前
|
||||
const [timepickerBegin, setTimepickerBegin] = useState(moment().subtract(1, 'hour'));
|
||||
const [timepickerEnd, setTimepickerEnd] = useState(moment());
|
||||
const [messageShowList, setMessageShowList] = useState([]);
|
||||
const [paginationConf, setPaginationConf] = useState({
|
||||
@@ -146,7 +145,6 @@ const MessageQueryPage = () => {
|
||||
message: t.ERROR,
|
||||
description: t.QUERY_FAILED,
|
||||
});
|
||||
console.error("查询失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -182,7 +180,6 @@ const MessageQueryPage = () => {
|
||||
message: t.ERROR,
|
||||
description: t.QUERY_FAILED,
|
||||
});
|
||||
console.error("查询失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -241,7 +238,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 +451,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([]);
|
||||
@@ -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 地址');
|
||||
|
@@ -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([]);
|
||||
@@ -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(() => {
|
||||
@@ -47,7 +51,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 +62,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 +75,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 +89,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 +129,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 +156,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>
|
||||
|
@@ -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);
|
||||
@@ -173,7 +183,6 @@ const DeployHistoryList = () => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
const filterList = (currentPage) => {
|
||||
const lowExceptStr = filterStr.toLowerCase();
|
||||
const canShowList = allTopicList.filter((topic, index) => {
|
||||
@@ -223,7 +232,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 +266,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 +285,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';
|
||||
|
9
pom.xml
@@ -28,14 +28,14 @@
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-dashboard</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<version>2.1.0</version>
|
||||
<name>rocketmq-dashboard</name>
|
||||
|
||||
<scm>
|
||||
<url>git@github.com:apache/rocketmq-dashboard.git</url>
|
||||
<connection>scm:git:git@github.com:apache/rocketmq-dashboard.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:apache/rocketmq-dashboard.git</developerConnection>
|
||||
<tag>1.0.0</tag>
|
||||
<tag>rocketmq-dashboard-2.1.0</tag>
|
||||
</scm>
|
||||
|
||||
<mailingLists>
|
||||
@@ -249,6 +249,7 @@
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
@@ -432,7 +433,7 @@
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node </id>
|
||||
<id>install node</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
@@ -447,7 +448,7 @@
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>install --legacy-peer-deps</arguments>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
@@ -76,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.
|
||||
@@ -119,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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -18,11 +18,12 @@
|
||||
package org.apache.rocketmq.dashboard.controller;
|
||||
|
||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
||||
import org.apache.rocketmq.dashboard.model.UserInfoDto;
|
||||
import org.apache.rocketmq.dashboard.model.request.UserCreateRequest;
|
||||
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,68 +32,74 @@ 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);
|
||||
public List<UserInfoDto> listUsers(@RequestParam(required = false) String brokerName,
|
||||
@RequestParam(required = false) String clusterName) {
|
||||
return aclService.listUsers(clusterName, brokerName);
|
||||
}
|
||||
|
||||
@GetMapping("/listAcls")
|
||||
@GetMapping("/acls.query")
|
||||
@ResponseBody
|
||||
public Object listAcls(
|
||||
@RequestParam(required = false) String brokerAddress,
|
||||
@RequestParam(required = false) String searchParam) {
|
||||
return aclService.listAcls(brokerAddress, searchParam);
|
||||
@RequestParam(required = false) String brokerName,
|
||||
@RequestParam(required = false) String searchParam,
|
||||
@RequestParam(required = false) String clusterName) {
|
||||
return aclService.listAcls(clusterName, brokerName, 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);
|
||||
public Object deleteUser(@RequestParam(required = false) String brokerName,
|
||||
@RequestParam String username,
|
||||
@RequestParam(required = false) String clusterName) {
|
||||
aclService.deleteUser(clusterName, brokerName, 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());
|
||||
aclService.updateUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||
return true;
|
||||
}
|
||||
|
||||
@PostMapping("/createUser")
|
||||
@PostMapping("/createUser.do")
|
||||
@ResponseBody
|
||||
public Object createUser(@RequestBody UserCreateRequest request) {
|
||||
aclService.createUser(request.getBrokerAddress(), request.getUserInfo());
|
||||
aclService.createUser(request.getClusterName(), request.getBrokerName(), request.getUserInfo());
|
||||
return true;
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteAcl")
|
||||
@DeleteMapping("/deleteAcl.do")
|
||||
@ResponseBody
|
||||
public Object deleteAcl(
|
||||
@RequestParam(required = false) String brokerAddress,
|
||||
@RequestParam(required = false) String brokerName,
|
||||
@RequestParam(required = false) String clusterName,
|
||||
@RequestParam String subject,
|
||||
@RequestParam(required = false) String resource) {
|
||||
aclService.deleteAcl(brokerAddress, subject, resource);
|
||||
aclService.deleteAcl(clusterName, brokerName, subject, resource);
|
||||
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 {
|
||||
|
||||
|
209
src/main/java/org/apache/rocketmq/dashboard/model/AclInfo.java
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AclInfo {
|
||||
|
||||
private String subject;
|
||||
private List<PolicyInfo> policies;
|
||||
|
||||
public static AclInfo of(String subject, List<String> resources, List<String> actions,
|
||||
List<String> sourceIps,
|
||||
String decision) {
|
||||
AclInfo aclInfo = new AclInfo();
|
||||
aclInfo.setSubject(subject);
|
||||
PolicyInfo policyInfo = PolicyInfo.of(resources, actions, sourceIps, decision);
|
||||
aclInfo.setPolicies(Collections.singletonList(policyInfo));
|
||||
return aclInfo;
|
||||
}
|
||||
|
||||
public static class PolicyInfo {
|
||||
|
||||
private String policyType;
|
||||
private List<PolicyEntryInfo> entries;
|
||||
|
||||
public static PolicyInfo of(List<String> resources, List<String> actions,
|
||||
List<String> sourceIps, String decision) {
|
||||
PolicyInfo policyInfo = new PolicyInfo();
|
||||
List<PolicyEntryInfo> entries = resources.stream()
|
||||
.map(resource -> PolicyEntryInfo.of(resource, actions, sourceIps, decision))
|
||||
.collect(Collectors.toList());
|
||||
policyInfo.setEntries(entries);
|
||||
return policyInfo;
|
||||
}
|
||||
|
||||
public String getPolicyType() {
|
||||
return policyType;
|
||||
}
|
||||
|
||||
public void setPolicyType(String policyType) {
|
||||
this.policyType = policyType;
|
||||
}
|
||||
|
||||
public List<PolicyEntryInfo> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void setEntries(List<PolicyEntryInfo> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PolicyInfo that = (PolicyInfo) o;
|
||||
return Objects.equals(policyType, that.policyType) &&
|
||||
Objects.equals(entries, that.entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(policyType, entries);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PolicyEntryInfo {
|
||||
private String resource;
|
||||
private List<String> actions;
|
||||
private List<String> sourceIps;
|
||||
private String decision;
|
||||
|
||||
public static PolicyEntryInfo of(String resource, List<String> actions, List<String> sourceIps,
|
||||
String decision) {
|
||||
PolicyEntryInfo policyEntryInfo = new PolicyEntryInfo();
|
||||
policyEntryInfo.setResource(resource);
|
||||
policyEntryInfo.setActions(actions);
|
||||
policyEntryInfo.setSourceIps(sourceIps);
|
||||
policyEntryInfo.setDecision(decision);
|
||||
return policyEntryInfo;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public List<String> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public void setActions(List<String> actions) {
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public List<String> getSourceIps() {
|
||||
return sourceIps;
|
||||
}
|
||||
|
||||
public void setSourceIps(List<String> sourceIps) {
|
||||
this.sourceIps = sourceIps;
|
||||
}
|
||||
|
||||
public String getDecision() {
|
||||
return decision;
|
||||
}
|
||||
|
||||
public void setDecision(String decision) {
|
||||
this.decision = decision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PolicyEntryInfo that = (PolicyEntryInfo) o;
|
||||
return Objects.equals(resource, that.resource) &&
|
||||
Objects.equals(actions, that.actions) &&
|
||||
Objects.equals(sourceIps, that.sourceIps) &&
|
||||
Objects.equals(decision, that.decision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(resource, actions, sourceIps, decision);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public void setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
public List<PolicyInfo> getPolicies() {
|
||||
return policies;
|
||||
}
|
||||
|
||||
public void setPolicies(List<PolicyInfo> policies) {
|
||||
this.policies = policies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AclInfo aclInfo = (AclInfo) o;
|
||||
return Objects.equals(subject, aclInfo.subject) &&
|
||||
Objects.equals(policies, aclInfo.policies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(subject, policies);
|
||||
}
|
||||
|
||||
public void copyFrom(org.apache.rocketmq.remoting.protocol.body.AclInfo source) {
|
||||
this.subject = source.getSubject();
|
||||
if (source.getPolicies() != null) {
|
||||
List<PolicyInfo> copiedPolicies = new ArrayList<>();
|
||||
for (org.apache.rocketmq.remoting.protocol.body.AclInfo.PolicyInfo policy : source.getPolicies()) {
|
||||
PolicyInfo copiedPolicy = new PolicyInfo();
|
||||
copiedPolicy.setPolicyType(policy.getPolicyType());
|
||||
if (policy.getEntries() != null) {
|
||||
List<PolicyEntryInfo> copiedEntries = new ArrayList<>();
|
||||
for (org.apache.rocketmq.remoting.protocol.body.AclInfo.PolicyEntryInfo entry : policy.getEntries()) {
|
||||
PolicyEntryInfo copiedEntry = new PolicyEntryInfo();
|
||||
copiedEntry.setResource(entry.getResource());
|
||||
copiedEntry.setActions(new ArrayList<>(entry.getActions()));
|
||||
copiedEntry.setSourceIps(new ArrayList<>(entry.getSourceIps()));
|
||||
copiedEntry.setDecision(entry.getDecision());
|
||||
copiedEntries.add(copiedEntry);
|
||||
}
|
||||
copiedPolicy.setEntries(copiedEntries);
|
||||
}
|
||||
copiedPolicies.add(copiedPolicy);
|
||||
}
|
||||
this.setPolicies(copiedPolicies);
|
||||
} else {
|
||||
this.setPolicies(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
|
@@ -25,7 +25,8 @@ import java.util.List;
|
||||
@Getter
|
||||
@Setter
|
||||
public class PolicyRequest {
|
||||
private String brokerAddress;
|
||||
private String clusterName;
|
||||
private String brokerName;
|
||||
private String subject;
|
||||
private List<Policy> policies;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserInfoDto {
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String userType;
|
||||
|
||||
private String userStatus;
|
||||
|
||||
public UserInfoDto setUserInfo(UserInfo userInfo) {
|
||||
this.username = userInfo.getUsername();
|
||||
this.password = userInfo.getPassword();
|
||||
this.userType = userInfo.getUserType();
|
||||
this.userStatus = userInfo.getUserStatus();
|
||||
return this;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
@Getter
|
||||
public class UserCreateRequest {
|
||||
private String brokerAddress;
|
||||
private String clusterName;
|
||||
private String brokerName;
|
||||
private UserInfoParam userInfo;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -23,6 +23,7 @@ import lombok.Setter;
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserUpdateRequest {
|
||||
private String brokerAddress;
|
||||
private String clusterName;
|
||||
private String brokerName;
|
||||
private UserInfoParam userInfo;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -20,24 +20,21 @@ package org.apache.rocketmq.dashboard.service;
|
||||
|
||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
||||
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AclService {
|
||||
List<UserInfo> listUsers(String brokerAddress);
|
||||
Object listUsers(String clusterName, String brokerAddress);
|
||||
|
||||
Object listAcls(String brokerAddress, String searchParam);
|
||||
Object listAcls(String clusterName,String brokerAddress, String searchParam);
|
||||
|
||||
List<String> createAcl(PolicyRequest policyRequest);
|
||||
Object createAcl(PolicyRequest policyRequest);
|
||||
|
||||
void deleteUser(String brokerAddress, String username);
|
||||
void deleteUser(String clusterName,String brokerAddress, String username);
|
||||
|
||||
void updateUser(String brokerAddress, UserInfoParam userParam);
|
||||
void updateUser(String clusterName,String brokerAddress, UserInfoParam userParam);
|
||||
|
||||
void createUser(String brokerAddress, UserInfoParam userParam);
|
||||
void createUser(String clusterName,String brokerAddress, UserInfoParam userParam);
|
||||
|
||||
void deleteAcl(String brokerAddress, String subject, String resource);
|
||||
void deleteAcl(String clusterName,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");
|
||||
|
@@ -21,9 +21,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.rocketmq.dashboard.model.Entry;
|
||||
import org.apache.rocketmq.dashboard.model.Policy;
|
||||
import org.apache.rocketmq.dashboard.model.PolicyRequest;
|
||||
import org.apache.rocketmq.dashboard.model.UserInfoDto;
|
||||
import org.apache.rocketmq.dashboard.model.request.UserInfoParam;
|
||||
import org.apache.rocketmq.dashboard.service.AbstractCommonService;
|
||||
import org.apache.rocketmq.dashboard.service.AclService;
|
||||
import org.apache.rocketmq.dashboard.service.ClusterInfoService;
|
||||
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
|
||||
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
|
||||
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
|
||||
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
||||
import org.slf4j.Logger;
|
||||
@@ -37,7 +41,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class AclServiceImpl implements AclService {
|
||||
public class AclServiceImpl extends AbstractCommonService implements AclService {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(AclServiceImpl.class);
|
||||
|
||||
@@ -45,60 +49,117 @@ public class AclServiceImpl implements AclService {
|
||||
@Autowired
|
||||
private MQAdminExt mqAdminExt;
|
||||
|
||||
@Autowired
|
||||
private ClusterInfoService clusterInfoService;
|
||||
|
||||
|
||||
@Override
|
||||
public List<UserInfo> listUsers(String brokerAddress) {
|
||||
List<UserInfo> userList;
|
||||
try {
|
||||
userList = mqAdminExt.listUser(brokerAddress, "");
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to list users from broker: {}", brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to list users", ex);
|
||||
}
|
||||
if (userList == null || userList.isEmpty()) {
|
||||
logger.warn("No users found for broker: {}", brokerAddress);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return userList;
|
||||
}
|
||||
public List<UserInfoDto> listUsers(String clusterName, String brokerName) {
|
||||
|
||||
@Override
|
||||
public Object listAcls(String brokerAddress, String searchParam) {
|
||||
List<AclInfo> aclList;
|
||||
try {
|
||||
String user = searchParam != null ? searchParam : "";
|
||||
String res = searchParam != null ? searchParam : "";
|
||||
aclList = mqAdminExt.listAcl(brokerAddress, user, "");
|
||||
if (aclList == null) {
|
||||
aclList = new ArrayList<>();
|
||||
}
|
||||
List<AclInfo> resAclList = mqAdminExt.listAcl(brokerAddress, "", res);
|
||||
if (resAclList != null) {
|
||||
aclList.addAll(resAclList);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to list ACLs from broker: {}", brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to list ACLs", ex);
|
||||
}
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Set<String> uniqueAclStrings = new HashSet<>();
|
||||
List<AclInfo> resultAclList = new ArrayList<>();
|
||||
|
||||
for (AclInfo acl : aclList) {
|
||||
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||
Set<UserInfoDto> commonUsers = new HashSet<>();
|
||||
final boolean[] firstIteration = {true};
|
||||
brokerAddrList.forEach(address -> {
|
||||
List<UserInfo> userList;
|
||||
try {
|
||||
String aclString = mapper.writeValueAsString(acl);
|
||||
if (uniqueAclStrings.add(aclString)) {
|
||||
resultAclList.add(acl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error serializing AclInfo", e);
|
||||
userList = mqAdminExt.listUser(address, "");
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to list users from broker: {}", address, ex);
|
||||
throw new RuntimeException("Failed to list users", ex);
|
||||
}
|
||||
}
|
||||
return resultAclList;
|
||||
|
||||
List<UserInfoDto> userListDtos = new ArrayList<>();
|
||||
userList.forEach(user -> {
|
||||
UserInfoDto userInfoDto = new UserInfoDto();
|
||||
userListDtos.add(userInfoDto.setUserInfo(user));
|
||||
});
|
||||
if (!userList.isEmpty()) {
|
||||
Set<UserInfoDto> currentUsers = new HashSet<>(userListDtos);
|
||||
if (firstIteration[0]) {
|
||||
commonUsers.addAll(userListDtos);
|
||||
firstIteration[0] = false;
|
||||
} else {
|
||||
commonUsers.retainAll(currentUsers);
|
||||
}
|
||||
} else {
|
||||
logger.warn("No users found for broker: {}", address);
|
||||
}
|
||||
});
|
||||
|
||||
return new ArrayList<>(commonUsers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> createAcl(PolicyRequest policyRequest) {
|
||||
public Object listAcls(String clusterName, String brokerName, String searchParam) {
|
||||
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||
Set<org.apache.rocketmq.dashboard.model.AclInfo> commonAcls = new HashSet<>();
|
||||
final boolean[] firstIteration = {true};
|
||||
ObjectMapper mapper = new ObjectMapper(); // Initialize ObjectMapper once
|
||||
|
||||
brokerAddrList.forEach(address -> {
|
||||
List<AclInfo> aclListForBroker;
|
||||
try {
|
||||
String user = searchParam != null ? searchParam : "";
|
||||
String res = searchParam != null ? searchParam : "";
|
||||
// Combine results from both listAcl calls for a single broker
|
||||
List<AclInfo> byUser = mqAdminExt.listAcl(address, user, "");
|
||||
List<AclInfo> byRes = mqAdminExt.listAcl(address, "", res);
|
||||
|
||||
aclListForBroker = new ArrayList<>();
|
||||
if (byUser != null) {
|
||||
aclListForBroker.addAll(byUser);
|
||||
}
|
||||
if (byRes != null) {
|
||||
aclListForBroker.addAll(byRes);
|
||||
}
|
||||
|
||||
// Deduplicate ACLs for the current broker to ensure accurate intersection
|
||||
Set<AclInfo> uniqueAclsForBroker = new HashSet<>();
|
||||
Set<String> uniqueAclStringsForBroker = new HashSet<>();
|
||||
for (AclInfo acl : aclListForBroker) {
|
||||
try {
|
||||
String aclString = mapper.writeValueAsString(acl);
|
||||
if (uniqueAclStringsForBroker.add(aclString)) {
|
||||
uniqueAclsForBroker.add(acl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error serializing AclInfo for broker {}: {}", address, e.getMessage());
|
||||
}
|
||||
}
|
||||
aclListForBroker = new ArrayList<>(uniqueAclsForBroker);
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to list ACLs from broker: {}", address, ex);
|
||||
throw new RuntimeException("Failed to list ACLs", ex);
|
||||
}
|
||||
List<org.apache.rocketmq.dashboard.model.AclInfo> aclInfoList = new ArrayList<>();
|
||||
aclListForBroker.forEach(acl -> {
|
||||
org.apache.rocketmq.dashboard.model.AclInfo aclInfo = new org.apache.rocketmq.dashboard.model.AclInfo();
|
||||
aclInfo.copyFrom(acl);
|
||||
aclInfoList.add(aclInfo);
|
||||
});
|
||||
if (!aclListForBroker.isEmpty()) {
|
||||
Set<org.apache.rocketmq.dashboard.model.AclInfo> currentAcls = new HashSet<>(aclInfoList);
|
||||
if (firstIteration[0]) {
|
||||
commonAcls.addAll(currentAcls);
|
||||
firstIteration[0] = false;
|
||||
} else {
|
||||
commonAcls.retainAll(currentAcls);
|
||||
}
|
||||
} else {
|
||||
logger.warn("No ACLs found for broker: {}", address);
|
||||
if (firstIteration[0]) {
|
||||
firstIteration[0] = false;
|
||||
} else {
|
||||
commonAcls.clear(); // If any broker has no ACLs, the common set will be empty
|
||||
}
|
||||
}
|
||||
});
|
||||
return new ArrayList<>(commonAcls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createAcl(PolicyRequest policyRequest) {
|
||||
List<String> successfulResources = new ArrayList<>();
|
||||
|
||||
if (policyRequest == null || policyRequest.getPolicies() == null || policyRequest.getPolicies().isEmpty()) {
|
||||
@@ -107,11 +168,13 @@ public class AclServiceImpl implements AclService {
|
||||
}
|
||||
|
||||
String subject = policyRequest.getSubject();
|
||||
|
||||
if (subject == null || subject.isEmpty()) {
|
||||
throw new IllegalArgumentException("Subject cannot be null or empty.");
|
||||
}
|
||||
|
||||
// Get the broker address list for creating ACLs on all relevant brokers
|
||||
List<String> brokerAddrList = getBrokerAddressList(policyRequest.getClusterName(), policyRequest.getBrokerName());
|
||||
|
||||
for (Policy policy : policyRequest.getPolicies()) {
|
||||
if (policy.getEntries() != null && !policy.getEntries().isEmpty()) {
|
||||
for (Entry entry : policy.getEntries()) {
|
||||
@@ -136,90 +199,110 @@ public class AclServiceImpl implements AclService {
|
||||
aclInfo.setPolicies(aclPolicies);
|
||||
aclInfo.setSubject(subject);
|
||||
|
||||
try {
|
||||
logger.info("Attempting to create ACL for subject: {}, resource: {} on broker: {}", subject, resource, policyRequest.getBrokerAddress());
|
||||
mqAdminExt.createAcl(policyRequest.getBrokerAddress(), aclInfo);
|
||||
successfulResources.add(resource);
|
||||
logger.info("Successfully created ACL for subject: {}, resource: {}", subject, resource);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to create ACL for subject: {}, resource: {} on broker: {}", subject, resource, policyRequest.getBrokerAddress(), ex);
|
||||
throw new RuntimeException("Failed to create ACL", ex);
|
||||
for (String brokerAddress : brokerAddrList) {
|
||||
try {
|
||||
logger.info("Attempting to create ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress);
|
||||
mqAdminExt.createAcl(brokerAddress, aclInfo);
|
||||
logger.info("Successfully created ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Failed to create ACL on broker " + brokerAddress + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return successfulResources;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(String brokerAddress, String username) {
|
||||
try {
|
||||
mqAdminExt.deleteUser(brokerAddress, username);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to delete user: {} from broker: {}", username, brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to delete user", ex);
|
||||
public void deleteUser(String clusterName, String brokerName, String username) {
|
||||
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||
|
||||
for (String address : brokerAddrList) {
|
||||
try {
|
||||
mqAdminExt.deleteUser(address, username);
|
||||
logger.info("Successfully deleted user: {} from broker: {}", username, address);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to delete user: {} from broker: {}", username, address, ex);
|
||||
throw new RuntimeException("Failed to delete user on broker " + address + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUser(String brokerAddress, UserInfoParam userParam) {
|
||||
public void updateUser(String clusterName, String brokerName, UserInfoParam userParam) {
|
||||
UserInfo user = new UserInfo();
|
||||
user.setUsername(userParam.getUsername());
|
||||
user.setPassword(userParam.getPassword());
|
||||
user.setUserStatus(userParam.getUserStatus());
|
||||
user.setUserType(userParam.getUserType());
|
||||
|
||||
try {
|
||||
mqAdminExt.updateUser(brokerAddress, user);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to update user: {} on broker: {}", userParam.getUsername(), brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to update user", ex);
|
||||
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||
|
||||
for (String address : brokerAddrList) {
|
||||
try {
|
||||
mqAdminExt.updateUser(address, user);
|
||||
logger.info("Successfully updated user: {} on broker: {}", userParam.getUsername(), address);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to update user: {} on broker: {}", userParam.getUsername(), address, ex);
|
||||
throw new RuntimeException("Failed to update user on broker " + address + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createUser(String brokerAddress, UserInfoParam userParam) {
|
||||
public void createUser(String clusterName, String brokerName, UserInfoParam userParam) {
|
||||
UserInfo user = new UserInfo();
|
||||
user.setUsername(userParam.getUsername());
|
||||
user.setPassword(userParam.getPassword());
|
||||
user.setUserStatus(userParam.getUserStatus());
|
||||
user.setUserType(userParam.getUserType());
|
||||
try {
|
||||
mqAdminExt.createUser(brokerAddress, user);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to create user: {} on broker: {}", userParam.getUsername(), brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to create user", ex);
|
||||
|
||||
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||
|
||||
for (String address : brokerAddrList) {
|
||||
try {
|
||||
mqAdminExt.createUser(address, user);
|
||||
logger.info("Successfully created user: {} on broker: {}", userParam.getUsername(), address);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to create user: {} on broker: {}", userParam.getUsername(), address, ex);
|
||||
throw new RuntimeException("Failed to create user on broker " + address + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAcl(String brokerAddress, String subject, String resource) {
|
||||
try {
|
||||
String res = resource != null ? resource : "";
|
||||
mqAdminExt.deleteAcl(brokerAddress, subject, res);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to delete ACL for subject: {} and resource: {} on broker: {}", subject, resource, brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to delete ACL", ex);
|
||||
public void deleteAcl(String clusterName, String brokerName, String subject, String resource) {
|
||||
List<String> brokerAddrList = getBrokerAddressList(clusterName, brokerName);
|
||||
String res = resource != null ? resource : "";
|
||||
|
||||
for (String address : brokerAddrList) {
|
||||
try {
|
||||
mqAdminExt.deleteAcl(address, subject, res);
|
||||
logger.info("Successfully deleted ACL for subject: {} and resource: {} on broker: {}", subject, resource, address);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to delete ACL for subject: {} and resource: {} on broker: {}", subject, resource, address, ex);
|
||||
throw new RuntimeException("Failed to delete ACL on broker " + address + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAcl(PolicyRequest policyRequest) {
|
||||
|
||||
if (policyRequest == null || policyRequest.getPolicies() == null || policyRequest.getPolicies().isEmpty()) {
|
||||
logger.warn("Policy request is null or policies list is empty. No ACLs to update.");
|
||||
return;
|
||||
}
|
||||
|
||||
assert policyRequest != null;
|
||||
String brokerAddress = policyRequest.getBrokerAddress();
|
||||
String subject = policyRequest.getSubject();
|
||||
|
||||
if (subject == null || subject.isEmpty()) {
|
||||
throw new IllegalArgumentException("Subject cannot be null or empty.");
|
||||
}
|
||||
|
||||
List<String> brokerAddrList = getBrokerAddressList(policyRequest.getClusterName(), policyRequest.getBrokerName());
|
||||
|
||||
for (Policy policy : policyRequest.getPolicies()) {
|
||||
if (policy.getEntries() != null && !policy.getEntries().isEmpty()) {
|
||||
for (Entry entry : policy.getEntries()) {
|
||||
@@ -244,18 +327,52 @@ public class AclServiceImpl implements AclService {
|
||||
aclInfo.setPolicies(aclPolicies);
|
||||
aclInfo.setSubject(subject);
|
||||
|
||||
try {
|
||||
mqAdminExt.updateAcl(brokerAddress, aclInfo);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to update ACL for subject: {} on broker: {}", subject, brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to update ACL", ex);
|
||||
for (String brokerAddress : brokerAddrList) {
|
||||
try {
|
||||
mqAdminExt.updateAcl(brokerAddress, aclInfo);
|
||||
logger.info("Successfully updated ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress);
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to update ACL for subject: {}, resource: {} on broker: {}", subject, resource, brokerAddress, ex);
|
||||
throw new RuntimeException("Failed to update ACL on broker " + brokerAddress + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<String> getBrokerAddressList(String clusterName, String brokerName) {
|
||||
ClusterInfo clusterInfo = clusterInfoService.get();
|
||||
List<String> brokerAddressList = new ArrayList<>();
|
||||
if (brokerName != null) {
|
||||
for (String brokerNameKey : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
|
||||
new ArrayList<>(), List.of(brokerName))) {
|
||||
clusterInfo.getBrokerAddrTable()
|
||||
.get(brokerNameKey)
|
||||
.getBrokerAddrs()
|
||||
.forEach((Long key, String value) -> brokerAddressList.add(value));
|
||||
}
|
||||
} else {
|
||||
if (clusterName == null || clusterName.isEmpty()) {
|
||||
logger.warn("Cluster name is null or empty. Cannot retrieve broker addresses.");
|
||||
throw new IllegalArgumentException("Cluster name cannot be null or empty.");
|
||||
}
|
||||
if (clusterInfo == null || clusterInfo.getBrokerAddrTable() == null || clusterInfo.getBrokerAddrTable().isEmpty()) {
|
||||
logger.warn("Cluster information is not available or has no broker addresses.");
|
||||
throw new RuntimeException("Cluster information is not available or has no broker addresses.");
|
||||
}
|
||||
for (String brokerNameKey : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
|
||||
List.of(clusterName), new ArrayList<>())) {
|
||||
clusterInfo.getBrokerAddrTable()
|
||||
.get(brokerNameKey)
|
||||
.getBrokerAddrs()
|
||||
.forEach((Long key, String value) -> brokerAddressList.add(value));
|
||||
}
|
||||
}
|
||||
return brokerAddressList;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -210,9 +210,16 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
if (SYSTEM_GROUP_SET.contains(consumerGroup)) {
|
||||
consumeInfo.setSubGroupType("SYSTEM");
|
||||
} else {
|
||||
consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL");
|
||||
try {
|
||||
consumeInfo.setSubGroupType(subscriptionGroupTable.get(consumerGroup).isConsumeMessageOrderly() ? "FIFO" : "NORMAL");
|
||||
} catch (NullPointerException e) {
|
||||
logger.warn("SubscriptionGroupConfig not found for consumer group: {}", consumerGroup);
|
||||
boolean isFifoType = examineSubscriptionGroupConfig(consumerGroup)
|
||||
.stream().map(ConsumerConfigInfo::getSubscriptionGroupConfig)
|
||||
.allMatch(SubscriptionGroupConfig::isConsumeMessageOrderly);
|
||||
consumeInfo.setSubGroupType(isFifoType ? "FIFO" : "NORMAL");
|
||||
}
|
||||
}
|
||||
consumeInfo.setGroup(consumerGroup);
|
||||
consumeInfo.setUpdateTime(new Date());
|
||||
groupConsumeInfoList.add(consumeInfo);
|
||||
} catch (Exception e) {
|
||||
@@ -270,26 +277,37 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
|
||||
logger.warn("examineConsumeStats or examineConsumerConnectionInfo exception, "
|
||||
+ consumerGroup, e);
|
||||
}
|
||||
groupConsumeInfo.setGroup(consumerGroup);
|
||||
return groupConsumeInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName, String address) {
|
||||
ConsumeStats consumeStats;
|
||||
groupName = getConsumerGroup(groupName);
|
||||
List<ConsumeStats> consumeStatses = new ArrayList<>();
|
||||
String topic = null;
|
||||
try {
|
||||
String[] addresses = address.split(",");
|
||||
String addr = addresses[0];
|
||||
consumeStats = mqAdminExt.examineConsumeStats(addr, groupName, null, 3000);
|
||||
for (String addr : addresses) {
|
||||
consumeStatses.add(mqAdminExt.examineConsumeStats(addr, groupName, null, 3000));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return toTopicConsumerInfoList(topic, consumeStats, groupName);
|
||||
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, finalGroupName));
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TopicConsumerInfo> queryConsumeStatsList(final String topic, String groupName) {
|
||||
groupName = getConsumerGroup(groupName);
|
||||
ConsumeStats consumeStats = null;
|
||||
try {
|
||||
consumeStats = mqAdminExt.examineConsumeStats(groupName, topic);
|
||||
@@ -301,6 +319,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) {
|
||||
@@ -324,6 +343,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);
|
||||
@@ -402,7 +422,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();
|
||||
@@ -410,9 +431,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;
|
||||
@@ -465,6 +486,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(),
|
||||
@@ -480,6 +502,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);
|
||||
@@ -496,6 +519,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];
|
||||
@@ -508,6 +532,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) {
|
||||
@@ -518,7 +543,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");
|
||||
}
|
||||
@@ -526,7 +550,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));
|
||||
@@ -544,4 +568,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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
// }
|
||||
}
|
||||
|