Compare commits

...

71 Commits

Author SHA1 Message Date
dependabot[bot]
94dda468f9 Bump minimist from 1.2.5 to 1.2.8 in /frontend
Bumps [minimist](https://github.com/minimistjs/minimist) from 1.2.5 to 1.2.8.
- [Release notes](https://github.com/minimistjs/minimist/releases)
- [Changelog](https://github.com/minimistjs/minimist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.8)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 11:55:42 +00:00
zhangjidi2016
86bdb06364 [ISSUE #123]Optimize groupList.query (#124)
Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2022-11-28 16:37:02 +08:00
Oliver
7a54427d9c [ISSUE #111] Update log name (#112) 2022-11-23 08:32:33 +08:00
yannizhou05
fc9781e6fc Fix flaky testList (#120) 2022-11-23 08:30:31 +08:00
yukon
ea8834bacd Update .asf.yaml 2022-10-19 16:22:23 +08:00
zhangjidi2016
63d9da7429 [ISSUE #82]Filter the system topic (clusterName+REPLY_TOPIC) when dashboard was making topic statistics (#83)
Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2022-04-11 08:27:48 +08:00
zhangjidi2016
daa181ccfd [ISSUE #58]Supports adding namesrvAddr cluster management (#66)
Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2022-04-08 15:43:21 +08:00
zhangjidi2016
f5b9bbb9a7 Configure the acl interface path in the authInterceptor (#77)
* Configure the acl interface path in the authInterceptor

* retrigger ci

Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2022-03-14 11:36:36 +08:00
zhangjidi2016
6de5d51661 [ISSUE #74]upgrade rocketmq version to 4.9.3 (#75)
Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2022-03-05 10:35:06 +08:00
Xiaodong Xu
4269879d93 [ISSUE #70] The rocketmq-dashboard supports ACL configuration (#71)
* Add Acl menu, support config acl.

* Optimize one line code.

* Add some unit tests for acl.

* Add permission control by role and optimize some code.

* The secret keys are hidden by asterisks.

* Search acl data will exclude secretKey info if the login role is not admin in the background.

* Optimize some code again.

* recover default application.yml config
2022-03-05 10:34:39 +08:00
dinglei
0fc7ca904d Update README.md
fix style and add detail codecov.
2022-02-16 14:32:12 +08:00
dinglei
636ba51bc3 Update .travis.yml 2022-02-16 12:00:04 +08:00
dinglei
8c5d8747a7 Open code coverage in CI.
Open code coverage in CI.
2022-02-16 11:42:19 +08:00
dinglei
3f02992e13 Update Notice to 2022.
Update Notice to 2022.
2022-02-16 11:04:13 +08:00
dinglei
78b85ebdb6 Update License to the latest.
See https://www.apache.org/licenses/LICENSE-2.0.txt
2022-02-16 11:02:48 +08:00
zhangjidi2016
653332d488 [ISSUE #68]Use multithreading for topic data collection in collectTask (#69)
* [ISSUE #68]Use multithreading for topic data collection in collectTask

* modify ut

* Optimize exception log printing

Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2022-02-11 09:27:53 +08:00
zhangjidi2016
d2da9ca41f [ISSUE #72]The batchResend and batchExport buttons are disabled when there is no dlq message (#73)
Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2022-02-10 15:09:59 +08:00
zhangjidi2016
6da21f480e Configure the dlq message interface path in the authInterceptor (#65)
Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2022-01-12 18:48:44 +08:00
zhangjidi2016
e281b74456 [ISSUE #62]Uses the YML format instead of the Properties format. (#63)
* [ISSUE #62]Uses the YML format instead of the Properties format.

* retrigger ci

* Solve the apache-rat check

Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2022-01-08 22:05:21 +08:00
zhangjidi2016
529501c007 [ISSUE #55]Delete the corresponding DLQ and Retry Topic simultaneously when deleting the consumerGroup. (#57)
* [ISSUE #55]Delete the corresponding DLQ and Retry Topic simultaneously when deleting the consumerGroup.

* modify method name

* Optimize the delete logic

* optimize code comments
2022-01-06 08:32:45 +08:00
zhangjidi2016
560b56e9dc [ISSUE #54]Add filtering function when querying message consumption. (#56) 2022-01-05 09:19:33 +08:00
zhangjidi2016
8acd94c89a [ISSUE #60]Update SpringBoot version. (#61) 2022-01-05 09:15:55 +08:00
cserwen
2d8bf001b3 [ISSUE #58] enable the service to support multiple namesrvs (#59)
* enable the service to support multiple namesrvs

* use @ to split the namesrv of different clusters

Co-authored-by: dengzhiwen1 <dengzhiwen1@xiaomi.com>
2022-01-04 18:02:08 +08:00
zhangjidi2016
1caeb4ce31 [ISSUE #50]Modify the suffix of the interface name. (#52)
* [ISSUE #50]Modify the suffix of the interface name.

* add perm

* fix dlq perm

Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-12-16 21:13:48 +08:00
StyleTang
749e6f15f7 update default role-permission (#51) 2021-12-13 21:18:20 +08:00
meateggmilk
2524ca668a fix query message bug (#49)
Co-authored-by: shizhili <shizhili@cmss.chinamobile.com>
2021-12-06 23:49:51 +08:00
zhangjidi2016
6054c057c8 [ISSUE #43]Give a tip when no message found with topic + time query. (#44)
Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-11-17 13:30:33 +08:00
zhangjidi2016
e660c7d874 [ISSUE #42]Fix deleteSubscriptionGroup not remove consumer offset. (#45)
Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-11-17 13:30:12 +08:00
Anton Kulikov
a5a3659c04 Fix typo (#40) 2021-11-09 08:30:46 +08:00
zhangjidi2016
2160e23b91 [ISSUE #38]update rocketmq version. (#39)
Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-11-05 09:04:59 +08:00
zhangjidi2016
ef7b97e96a [ISSUE #30]Supports batch resending and batch exporting dlq messages. (#37)
* [ISSUE #30]Supports batch resending and batch exporting dlq messages.

* Optimize the code

Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-11-03 21:44:43 +08:00
StyleTang
d73d9b2793 [ISSUE #30]Fix Windows System yarn build fail (#35) 2021-10-31 11:54:37 +08:00
StyleTang
a2234f8517 add Html router for front-end (#29) 2021-10-31 11:30:25 +08:00
zhangjidi2016
4b2b61e394 [ISSUE #30]Added DLQ message management (#31)
* [ISSUE #30]Added DLQ message management

* remove the specific namesrvAddr in application.properties.

Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2021-10-31 11:29:55 +08:00
StyleTang
d5fed12773 Update README (use new docker image) (#34) 2021-10-28 16:22:02 +08:00
zhangjidi2016
cc30bb2373 [ISSUE #25]Add filter in consumer list. (#26)
Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2021-10-09 18:56:58 +08:00
Raymond
d0583b73c3 [ISSUE #10] add react module for dashboard (#12)
* init react module

* fix frontend license
2021-09-29 21:42:54 +08:00
von gosling
193a66e5c2 Update README.md 2021-09-28 09:50:34 +08:00
zhangjidi2016
9e9762ef44 [ISSUE #22]Optimizing the ops page. (#23)
Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2021-09-27 11:35:00 +08:00
StyleTang
8b12e8e7d7 [maven-release-plugin] prepare for next development iteration 2021-09-23 21:14:54 +08:00
StyleTang
f6d4e18b89 [maven-release-plugin] prepare release rocketmq-dashboard-1.0.0 2021-09-23 21:14:35 +08:00
StyleTang
a5d7cce483 1.0.0 release 2021-09-23 21:13:23 +08:00
zhangjidi2016
58336d951a [ISSUE #19]Message track query enhancement (#21)
* [ISSUE #19]Message track query enhancement

* traceTopic can be null and system Topic is used by default

* add unit test

* select messageTrack topic in messageTrace page

Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2021-09-23 21:01:32 +08:00
StyleTang
dc67c660ff [maven-release-plugin] prepare for next development iteration 2021-09-17 22:30:41 +08:00
StyleTang
f4b759dba6 [maven-release-plugin] prepare release rocketmq-dashboard-1.0.0 2021-09-17 22:30:23 +08:00
StyleTang
ef09ceb736 1.0.0 release 2021-09-17 22:23:14 +08:00
StyleTang
91794b5a9c [maven-release-plugin] prepare for next development iteration 2021-09-17 00:05:20 +08:00
StyleTang
d120610686 [maven-release-plugin] prepare release rocketmq-dashboard-1.0.0 2021-09-17 00:05:02 +08:00
StyleTang
ef3a27d642 Prepare for the first release
update pom
2021-09-17 00:03:57 +08:00
StyleTang
0614bc499e rocketmq-dashboard use 1.0.0 version 2021-09-16 23:52:17 +08:00
StyleTang
b0a2c8ff41 update README
java -jar target/rocketmq-dashboard-1.0.0-SNAPSHOT.jar
2021-09-16 23:45:47 +08:00
StyleTang
d5588b1fd0 1. remove unnecessary dependency Font-Awesome
2. use 1.0.0-SNAPSHOT
2021-09-16 23:11:52 +08:00
X.C. Wong
3fd2223cb5 Update README.md (#20)
* Update README.md

typo

* Update docker CMDs

1. Delete `docker run` CMD without tag;
2. Add newest tag (2.0.0) to `docker pull` CMD;
2021-09-12 23:02:13 +08:00
zhangjidi2016
975449e268 [ISSUE #16]Use an object pool to manage DefaultMQAdminExt objects. (#17)
* [ISSUE #16]Use an object pool to manage DefaultMQAdminExt objects.

* Modify the code

* Optimize the code

Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-09-09 00:13:44 +08:00
zhangjidi2016
5b2a027cd8 [ISSUE #5]Add permission control when loginRequired is true. (#6)
* [ISSUE #5]Add permission control when loginRequired is true.

* optimize the code

* Wildcard characters are supported

* add Apache License headers

Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
2021-09-01 20:05:36 +08:00
StyleTang
25129169d5 [ISSUE #13] Addd Rocketmq-Dashboard license announcement (#14)
* Revert the front end resource license modification (c9d4cd1b72 / 635e0ce4d0) and exclude the front end folder from apache-rat-plugin.
(front end resource license can be found in LICENSE file)

* update apache/echarts and h5bp/html5-boilerplate LICENSE

* update Readme docs link
add pagination license
2021-09-01 10:23:49 +08:00
von gosling
a1490d8377 Update NOTICE 2021-08-28 09:59:43 +08:00
von gosling
38e97a4c53 Update NOTICE 2021-08-28 09:56:32 +08:00
von gosling
818cd8d5d1 Update README.md 2021-08-26 18:40:22 +08:00
vongosling
c9d4cd1b72 Just leave vendor i18n unchanged 2021-08-26 17:22:17 +08:00
vongosling
635e0ce4d0 Add license for frontend script 2021-08-26 16:57:25 +08:00
vongosling
ef25e37ca6 Rename 2021-08-26 16:40:30 +08:00
vongosling
b1752d6dd6 Update license for docs 2021-08-26 16:39:18 +08:00
vongosling
0f5a5e1dda Update license for test code 2021-08-26 16:34:18 +08:00
vongosling
417f34130e Update license 2021-08-26 16:28:01 +08:00
von gosling
a977542a0f Update pom.xml 2021-08-25 16:59:13 +08:00
von gosling
51ed6117c2 Update .travis.yml 2021-08-25 16:57:18 +08:00
von gosling
bcfd10b65a Update .gitignore 2021-08-19 15:40:28 +08:00
zhangjidi2016
1a751091b3 [ISSUE #5]Different pages are displayed based on the user role. (#7)
* [ISSUE #5]Different pages are displayed based on the user role.

* Put the role judgment logic into js

Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
2021-08-19 12:29:16 +08:00
von gosling
62a4cafea0 Enable squash 2021-08-13 23:25:30 +08:00
von gosling
39d9db8a48 Merge pull request #3 from apache/init
[#2] Use rocketmq-dashboard instead of rocketmq-console
2021-08-13 23:22:06 +08:00
231 changed files with 35566 additions and 10153 deletions

View File

@@ -5,3 +5,14 @@ github:
issues: true
# Enable wiki
wiki: true
enabled_merge_buttons:
squash: true
merge: false
rebase: false
notifications:
commits: commits@rocketmq.apache.org
issues: commits@rocketmq.apache.org
pullrequests: commits@rocketmq.apache.org
jobs: commits@rocketmq.apache.org
discussions: dev@rocketmq.apache.org

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
/.idea/
/target/
*.iml
.classpath
.project
.factorypath
.settings/

View File

@@ -14,9 +14,11 @@ jdk:
- oraclejdk11
script:
- travis_retry mvn -B clean
- travis_retry mvn -B clean apache-rat:check
- travis_retry mvn -B package findbugs:findbugs jacoco:report coveralls:report
# - travis_retry mvn -B package findbugs:findbugs coveralls:report
#after_success:
# - mvn sonar:sonar
after_success:
- bash <(curl -s https://codecov.io/bash) -t 996e4d8e-8a00-49f2-b649-38c03716d690 || echo 'Codecov failed to upload'

60
LICENSE
View File

@@ -15,7 +15,7 @@
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (properties) the power, direct or indirect, to cause the
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
@@ -357,38 +357,16 @@ SOFTWARE.
echarts
----------------------------------
https://github.com/ecomfe/echarts
https://github.com/apache/echarts
Copyright (c) 2017, Baidu Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
html5-boilerplate
------------------------------------------
https://github.com/h5bp/html5-boilerplate
h5bp/html5-boilerplate is licensed under the MIT License
Copyright (c) HTML5 Boilerplate
Permission is hereby granted, free of charge, to any person obtaining a copy of
@@ -409,23 +387,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Font-Awesome
----------------------
https://github.com/FortAwesome/Font-Awesome
License
The Font Awesome font is licensed under the SIL OFL 1.1:
http://scripts.sil.org/OFL
Font Awesome CSS, LESS, and Sass files are licensed under the MIT License:
https://opensource.org/licenses/mit-license.html
The Font Awesome documentation is licensed under the CC BY 3.0 License:
http://creativecommons.org/licenses/by/3.0/
Attribution is no longer required as of Font Awesome 3.0, but much appreciated:
Font Awesome by Dave Gandy - http://fontawesome.io
Full details: http://fontawesome.io/license/
jquery
---------------------
Copyright JS Foundation and other contributors, https://js.foundation/
@@ -551,6 +512,15 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
pagination
---------------------
https://github.com/317482454/tm.pagination
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
bootstrap-validator
---------------------------

4
NOTICE
View File

@@ -1,5 +1,5 @@
Apache RocketMQ Dashboard
Copyright 2016-2017 The Apache Software Foundation
Apache RocketMQ
Copyright 2016-2022 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@@ -1,52 +1,50 @@
## RocketMQ Dashboard [![Build Status](https://api.travis-ci.com/apache/rocketmq-dashboard.svg?branch=master)](https://travis-ci.com/github/apache/rocketmq-dashboard)[![Coverage Status](https://coveralls.io/repos/github/apache/rocketmq-dashboard/badge.svg?branch=master)](https://coveralls.io/github/apache/rocketmq-dashboard?branch=master)
## [Apache RocketMQ](https://github.com/apache/rocketmq) Dashboard
[![Build Status](https://api.travis-ci.com/apache/rocketmq-dashboard.svg?branch=master)](https://travis-ci.com/github/apache/rocketmq-dashboard) [![Coverage Status](https://coveralls.io/repos/github/apache/rocketmq-dashboard/badge.svg?branch=master)](https://coveralls.io/github/apache/rocketmq-dashboard?branch=master)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![CodeCov](https://codecov.io/gh/apache/rocketmq-dashboard/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/rocketmq-dashboard)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/rocketmq-dashboard.svg)](http://isitmaintained.com/project/apache/rocketmq-dashboard "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/apache/rocketmq-dashboard.svg)](http://isitmaintained.com/project/apache/rocketmq-dashboard "Percentage of issues still open")
[![Twitter Follow](https://img.shields.io/twitter/follow/ApacheRocketMQ?style=social)](https://twitter.com/intent/follow?screen_name=ApacheRocketMQ)
## How To Install
## Quick Start
### With Docker
### Run with docker
* get docker image
#### Pull from [docker hub(rocketmq-dashboard)](https://hub.docker.com/r/apacherocketmq/rocketmq-dashboard/tags)
```
mvn clean package -Dmaven.test.skip=true docker:build
```shell
docker pull apacherocketmq/rocketmq-dashboard:latest
```
or
#### Run it (use your own `rocketmq.namesrv.addr` and `port`)
```
docker pull apacherocketmq/rocketmq-console
```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
```
> currently the newest available docker image is apacherocketmq/rocketmq-console:2.0.0
### Run with source code
* run it (change namesvrAddr and port yourself)
#### Prerequisite
1. 64bit OS, Linux/Unix/Mac is recommended;
2. 64bit JDK 1.8+;
3. Maven 3.2.x;
```
docker run -e "JAVA_OPTS=-Drocketmq.namesrv.addr=127.0.0.1:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -p 8080:8080 -t apacherocketmq/rocketmq-console-ng
```
#### Maven spring-boot run
or
```
docker run -e "JAVA_OPTS=-Drocketmq.namesrv.addr=127.0.0.1:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -p 8080:8080 -t apacherocketmq/rocketmq-console-ng:2.0.0
```
### Without Docker
require java 1.8+
```
```shell
mvn spring-boot:run
```
or
```
#### Maven build and run
```shell
mvn clean package -Dmaven.test.skip=true
java -jar target/rocketmq-dashboard-2.0.0.jar
java -jar target/rocketmq-dashboard-1.0.1-SNAPSHOT.jar
```
#### Tips
* if you download package slow,you can change maven's mirror(maven's settings.xml)
* If you download the package slowly, you can change maven's mirror(maven's settings.xml)
```
<mirrors>
@@ -59,18 +57,17 @@ java -jar target/rocketmq-dashboard-2.0.0.jar
</mirrors>
```
* if you use the rocketmq < 3.5.8,please add -Dcom.rocketmq.sendMessageWithVIPChannel=false when you start rocketmq-dashboard(or you can change it in ops page)
* change the rocketmq.config.namesrvAddr in resource/application.properties.(or you can change it in ops page)
* Change the rocketmq.config.namesrvAddr in resource/application.properties.(or you can change it in ops page)
## UserGuide
[English](https://github.com/apache/rocketmq-dashboard/blob/master/doc/1_0_0/UserGuide_EN.md)
[English](https://github.com/apache/rocketmq-dashboard/blob/master/docs/1_0_0/UserGuide_EN.md)
[中文](https://github.com/apache/rocketmq-dashboard/blob/master/doc/1_0_0/UserGuide_CN.md)
[中文](https://github.com/apache/rocketmq-dashboard/blob/master/docs/1_0_0/UserGuide_CN.md)
## Contributing
We are always very happy to have contributions, whether for trivial cleanups or big new features. Please see the RocketMQ main website to read [details](http://rocketmq.apache.org/docs/how-to-contribute/).
We are always very happy to have contributions, whether for trivial cleanups or big new features. Please see the RocketMQ main website to read the [details](http://rocketmq.apache.org/docs/how-to-contribute/).
## License
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation

View File

@@ -91,7 +91,7 @@ server.port=8443
## 登录访问Dashboard
在访问Dashboard时支持按用户名和密码登录控制台在操作完成后登出。需要做如下的设置:
* 1.在Spring配置文件resources/application.properties中修改 开启登录功能
* 1.在Spring配置文件resources/application.properties中修改rocketmq.config.loginRequired=true开启登录功能
```$xslt
# 开启登录功能
rocketmq.config.loginRequired=true
@@ -112,4 +112,41 @@ admin=admin,1
user1=user1
user2=user2
```
* 3. 启动控制台则开启了登录功能
* 3.启动控制台则开启了登录功能
## 权限检验
如果用户访问console时开启了登录功能会按照登录的角色对访问的接口进行权限控制。
* 1.在Spring配置文件resources/application.properties中修改rocketmq.config.loginRequired=true开启登录功能
```$xslt
# 开启登录功能
rocketmq.config.loginRequired=true
# Dashboard文件目录登录用户配置文件所在目录
rocketmq.config.dataPath=/tmp/rocketmq-console/data
```
* 2.确保${rocketmq.config.dataPath}定义的目录存在,并且该目录下创建访问权限配置文件"role-permission.yml",
如果该目录下不存在此文件则默认使用resources/role-permission.yml文件。该文件保存了普通用户角色所有能访问的接口地址。
role-permission.yml文件格式为:
```$xslt
# 该文件支持热修改即添加和修改用户时不需要重新启动console
# 格式,如果增加和删除接口权限,直接在列表中增加和删除接口地址即可。
# 接口路径配置支持通配符
# * 表示匹配0或多个不是/的字符
# ** 表示匹配0或多个任意字符
# ? 表示匹配1个任意字符
rolePerms:
# 普通用户
ordinary:
- /rocketmq/nsaddr
- /ops/*
- /dashboard/**
- /topic/*.query
- /topic/sendTopicMessage.do
- /producer/*.query
- /message/*
- /messageTrace/*
- /monitor/*
....
```
* 3.前端页面显示上为了更好区分普通用户和admin用户权限关于资源的删除、更新等操作按钮不对普通用户角色显示如果要执行资源相关操作需要退出使用admin角色登录。

View File

@@ -115,4 +115,44 @@ admin=admin,1
user1=user1
user2=user2
```
* 3. Restart Dashboard Application after above configuration setting well.
* 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
# 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

23
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

16635
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
frontend/package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-json-view": "^1.21.3",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"proxy": "http://localhost:8080",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,59 @@
<!--
~ 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.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
frontend/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
frontend/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

54
frontend/src/App.css Normal file
View File

@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 40vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

49
frontend/src/App.js Normal file
View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
import React, {useState, useEffect} from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
const [message, setMessage] = useState("");
useEffect(() => {
fetch('cluster/list.query')
.then(response => response.text())
.then(message => {
setMessage(message);
});
}, [])
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" height="60"/>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
</header>
<h1>ClusterInfo</h1>
<p>
{message}
</p>
</div>
);
}
export default App;

24
frontend/src/App.test.js Normal file
View File

@@ -0,0 +1,24 @@
/*
* 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.
*/
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();
});

29
frontend/src/index.css Normal file
View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
body {
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;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

33
frontend/src/index.js Normal file
View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

17
frontend/src/logo.svg Normal file
View File

@@ -0,0 +1,17 @@
<!--
~ 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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
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);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

11595
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "rocketmq-dashboard",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

178
pom.xml
View File

@@ -1,7 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.apache</groupId>
@@ -10,12 +25,19 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache</groupId>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-dashboard</artifactId>
<packaging>jar</packaging>
<version>2.0.0</version>
<version>1.0.1-SNAPSHOT</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>
</scm>
<mailingLists>
<mailingList>
<name>Development List</name>
@@ -45,6 +67,11 @@
</developer>
</developers>
<organization>
<name>Apache Software Foundation</name>
<url>http://www.apache.org</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
@@ -63,15 +90,24 @@
<commons-lang.version>2.6</commons-lang.version>
<commons-io.version>2.4</commons-io.version>
<commons-cli.version>1.2</commons-cli.version>
<rocketmq.version>4.9.0</rocketmq.version>
<commons-collections.version>3.2.2</commons-collections.version>
<rocketmq.version>4.9.3</rocketmq.version>
<surefire.version>2.19.1</surefire.version>
<aspectj.version>1.9.6</aspectj.version>
<lombok.version>1.18.12</lombok.version>
<main.basedir>${basedir}/../..</main.basedir>
<docker.image.prefix>apacherocketmq</docker.image.prefix>
<spring.boot.version>2.2.2.RELEASE</spring.boot.version>
<spring.boot.version>2.6.0</spring.boot.version>
<mockito-inline.version>3.3.3</mockito-inline.version>
<jaxb-api.version>2.3.1</jaxb-api.version>
<commons-pool2.version>2.4.3</commons-pool2.version>
<easyexcel.version>2.2.10</easyexcel.version>
<asm.version>4.2</asm.version>
<junit.version>4.12</junit.version>
<snakeyaml.version>1.30</snakeyaml.version>
<cglib.version>2.2.2</cglib.version>
<joor.version>0.9.6</joor.version>
<bcpkix-jdk15on.version>1.68</bcpkix-jdk15on.version>
</properties>
<dependencies>
@@ -95,11 +131,22 @@
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
@@ -173,17 +220,17 @@
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
<version>${cglib.version}</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joor</artifactId>
<version>0.9.6</version>
<version>${joor.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
<version>${bcpkix-jdk15on.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
@@ -201,6 +248,33 @@
<version>${mockito-inline.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons-pool2.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
@@ -314,7 +388,87 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<version>0.12</version>
<configuration>
<excludes>
<exclude>.gitignore</exclude>
<exclude>.travis.yml</exclude>
<exclude>.asf.yaml</exclude>
<exclude>README.md</exclude>
<exclude>.github/**</exclude>
<exclude>docs/**</exclude>
<exclude>src/main/resources/static/vendor/**</exclude>
<exclude>src/main/resources/static/src/data/**</exclude>
<exclude>frontend/node_modules/**</exclude>
<exclude>frontend/build/**</exclude>
<exclude>frontend/**.json</exclude>
<exclude>frontend/**.lock</exclude>
<exclude>frontend/public/manifest.json</exclude>
<exclude>package-lock.json</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.11.3</version>
<configuration>
<workingDirectory>frontend</workingDirectory>
<installDirectory>target</installDirectory>
</configuration>
<executions>
<execution>
<id>install node and yarn</id>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v16.2.0</nodeVersion>
<yarnVersion>v1.22.10</yarnVersion>
</configuration>
</execution>
<execution>
<id>yarn install</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>yarn build</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>build</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<target>
<copy todir="${project.build.directory}/classes/public">
<fileset dir="${project.basedir}/frontend/build"/>
</copy>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,3 +1,20 @@
#
# 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.
#
FROM java:8
VOLUME /tmp
ADD rocketmq-dashboard-*.jar rocketmq-dashboard.jar

View File

@@ -19,11 +19,14 @@ package org.apache.rocketmq.dashboard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.jmx.support.RegistrationPolicy;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@ServletComponentScan
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class App {
public static void main(String[] args) {

View File

@@ -0,0 +1,61 @@
/*
* 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.admin;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
import org.apache.rocketmq.tools.admin.MQAdminExt;
@Slf4j
public class MQAdminFactory {
private RMQConfigure rmqConfigure;
public MQAdminFactory(RMQConfigure rmqConfigure) {
this.rmqConfigure = rmqConfigure;
}
private final AtomicLong adminIndex = new AtomicLong(0);
public MQAdminExt getInstance() throws Exception {
RPCHook rpcHook = null;
final String accessKey = rmqConfigure.getAccessKey();
final String secretKey = rmqConfigure.getSecretKey();
boolean isEnableAcl = StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey);
if (isEnableAcl) {
rpcHook = new AclClientRPCHook(new SessionCredentials(accessKey, secretKey));
}
DefaultMQAdminExt mqAdminExt = null;
if (rmqConfigure.getTimeoutMillis() == null) {
mqAdminExt = new DefaultMQAdminExt(rpcHook);
} else {
mqAdminExt = new DefaultMQAdminExt(rpcHook, rmqConfigure.getTimeoutMillis());
}
mqAdminExt.setAdminExtGroup(mqAdminExt.getAdminExtGroup() + "_" + adminIndex.getAndIncrement());
mqAdminExt.setVipChannelEnabled(Boolean.parseBoolean(rmqConfigure.getIsVIPChannel()));
mqAdminExt.setUseTLS(rmqConfigure.isUseTLS());
mqAdminExt.setInstanceName(Long.toString(System.currentTimeMillis()));
mqAdminExt.start();
log.info("create MQAdmin instance {} success.", mqAdminExt);
return mqAdminExt;
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.admin;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.tools.admin.MQAdminExt;
@Slf4j
public class MQAdminPooledObjectFactory implements PooledObjectFactory<MQAdminExt> {
private MQAdminFactory mqAdminFactory;
@Override
public PooledObject<MQAdminExt> makeObject() throws Exception {
DefaultPooledObject<MQAdminExt> pooledObject = new DefaultPooledObject<>(
mqAdminFactory.getInstance());
return pooledObject;
}
@Override
public void destroyObject(PooledObject<MQAdminExt> p) {
MQAdminExt mqAdmin = p.getObject();
if (mqAdmin != null) {
try {
mqAdmin.shutdown();
} catch (Exception e) {
log.warn("MQAdminExt shutdown err", e);
}
}
log.info("destroy object {}", p.getObject());
}
@Override
public boolean validateObject(PooledObject<MQAdminExt> p) {
MQAdminExt mqAdmin = p.getObject();
ClusterInfo clusterInfo = null;
try {
clusterInfo = mqAdmin.examineBrokerClusterInfo();
} catch (Exception e) {
log.warn("validate object {} err", p.getObject(), e);
}
if (clusterInfo == null || MapUtils.isEmpty(clusterInfo.getBrokerAddrTable())) {
log.warn("validateObject failed, clusterInfo = {}", clusterInfo);
return false;
}
return true;
}
@Override
public void activateObject(PooledObject<MQAdminExt> p) {
}
@Override
public void passivateObject(PooledObject<MQAdminExt> p) {
}
public void setMqAdminFactory(MQAdminFactory mqAdminFactory) {
this.mqAdminFactory = mqAdminFactory;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.admin;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MqAdminExtObjectPool {
@Autowired
private RMQConfigure rmqConfigure;
@Bean
public GenericObjectPool<MQAdminExt> mqAdminExtPool() {
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setTestWhileIdle(true);
genericObjectPoolConfig.setMaxWaitMillis(10000);
genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(20000);
MQAdminPooledObjectFactory mqAdminPooledObjectFactory = new MQAdminPooledObjectFactory();
MQAdminFactory mqAdminFactory = new MQAdminFactory(rmqConfigure);
mqAdminPooledObjectFactory.setMqAdminFactory(mqAdminFactory);
GenericObjectPool<MQAdminExt> genericObjectPool = new GenericObjectPool<MQAdminExt>(
mqAdminPooledObjectFactory,
genericObjectPoolConfig);
return genericObjectPool;
}
}

View File

@@ -16,28 +16,24 @@
*/
package org.apache.rocketmq.dashboard.aspect.admin;
import org.apache.rocketmq.dashboard.aspect.admin.annotation.MultiMQAdminCmdMethod;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.rocketmq.dashboard.service.client.MQAdminInstance;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
@Aspect
@Service
@Slf4j
public class MQAdminAspect {
private Logger logger = LoggerFactory.getLogger(MQAdminAspect.class);
@Autowired
private RMQConfigure rmqConfigure;
private GenericObjectPool<MQAdminExt> mqAdminExtPool;
public MQAdminAspect() {
}
@@ -47,30 +43,16 @@ public class MQAdminAspect {
}
@Pointcut("@annotation(org.apache.rocketmq.dashboard.aspect.admin.annotation.MultiMQAdminCmdMethod)")
public void multiMQAdminMethodPointCut() {
}
@Around(value = "mQAdminMethodPointCut() || multiMQAdminMethodPointCut()")
@Around(value = "mQAdminMethodPointCut()")
public Object aroundMQAdminMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object obj = null;
try {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
MultiMQAdminCmdMethod multiMQAdminCmdMethod = method.getAnnotation(MultiMQAdminCmdMethod.class);
if (multiMQAdminCmdMethod != null && multiMQAdminCmdMethod.timeoutMillis() > 0) {
MQAdminInstance.initMQAdminInstance(multiMQAdminCmdMethod.timeoutMillis(),rmqConfigure.getAccessKey(),rmqConfigure.getSecretKey(), rmqConfigure.isUseTLS());
}
else {
MQAdminInstance.initMQAdminInstance(0,rmqConfigure.getAccessKey(),rmqConfigure.getSecretKey(), rmqConfigure.isUseTLS());
}
MQAdminInstance.createMQAdmin(mqAdminExtPool);
obj = joinPoint.proceed();
}
finally {
MQAdminInstance.destroyMQAdminInstance();
logger.debug("op=look method={} cost={}", joinPoint.getSignature().getName(), System.currentTimeMillis() - start);
} finally {
MQAdminInstance.returnMQAdmin(mqAdminExtPool);
log.debug("op=look method={} cost={}", joinPoint.getSignature().getName(), System.currentTimeMillis() - start);
}
return obj;
}

View File

@@ -30,6 +30,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Resource;
@@ -52,6 +53,7 @@ public class AuthWebMVCConfigurerAdapter extends WebMvcConfigurerAdapter {
"/cluster/**",
"/consumer/**",
"/dashboard/**",
"/dlqMessage/**",
"/message/**",
"/messageTrace/**",
"/monitor/**",
@@ -59,7 +61,8 @@ public class AuthWebMVCConfigurerAdapter extends WebMvcConfigurerAdapter {
"/ops/**",
"/producer/**",
"/test/**",
"/topic/**");
"/topic/**",
"/acl/**");
}
}
@@ -86,4 +89,9 @@ public class AuthWebMVCConfigurerAdapter extends WebMvcConfigurerAdapter {
super.addArgumentResolvers(argumentResolvers); //REVIEW ME
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("*.htm").setViewName("forward:/app.html");
}
}

View File

@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.config;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "threadpool.config")
@Data
public class CollectExecutorConfig {
private int coreSize = 20;
private int maxSize = 20;
private long keepAliveTime = 3000L;
private int queueSize = 1000;
@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);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "collectTopicThread_" + this.threadIndex.incrementAndGet());
}
},
new ThreadPoolExecutor.DiscardOldestPolicy()
);
return collectExecutor;
}
}

View File

@@ -16,9 +16,10 @@
*/
package org.apache.rocketmq.dashboard.config;
import java.util.ArrayList;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -30,6 +31,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import java.io.File;
import java.util.List;
import static org.apache.rocketmq.client.ClientConfig.SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY;
@@ -48,8 +50,6 @@ public class RMQConfigure {
private boolean enableDashBoardCollect;
private String msgTrackTopicName;
private boolean loginRequired = false;
private String accessKey;
@@ -58,6 +58,10 @@ public class RMQConfigure {
private boolean useTLS = false;
private Long timeoutMillis;
private List<String> namesrvAddrs = new ArrayList<>();
public String getAccessKey() {
return accessKey;
}
@@ -78,6 +82,17 @@ public class RMQConfigure {
return namesrvAddr;
}
public List<String> getNamesrvAddrs() {
return namesrvAddrs;
}
public void setNamesrvAddrs(List<String> namesrvAddrs) {
this.namesrvAddrs = namesrvAddrs;
if (CollectionUtils.isNotEmpty(namesrvAddrs)) {
this.setNamesrvAddr(namesrvAddrs.get(0));
}
}
public void setNamesrvAddr(String namesrvAddr) {
if (StringUtils.isNotBlank(namesrvAddr)) {
this.namesrvAddr = namesrvAddr;
@@ -121,17 +136,6 @@ public class RMQConfigure {
this.enableDashBoardCollect = Boolean.valueOf(enableDashBoardCollect);
}
public String getMsgTrackTopicNameOrDefault() {
if (StringUtils.isEmpty(msgTrackTopicName)) {
return TopicValidator.RMQ_SYS_TRACE_TOPIC;
}
return msgTrackTopicName;
}
public void setMsgTrackTopicName(String msgTrackTopicName) {
this.msgTrackTopicName = msgTrackTopicName;
}
public boolean isLoginRequired() {
return loginRequired;
}
@@ -148,6 +152,14 @@ public class RMQConfigure {
this.useTLS = useTLS;
}
public Long getTimeoutMillis() {
return timeoutMillis;
}
public void setTimeoutMillis(Long timeoutMillis) {
this.timeoutMillis = timeoutMillis;
}
// Error Page process logic, move to a central configure later
@Bean
public ErrorPageRegistrar errorPageRegistrar() {

View File

@@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.controller;
import com.google.common.base.Preconditions;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.AclConfig;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.model.User;
import org.apache.rocketmq.dashboard.model.UserInfo;
import org.apache.rocketmq.dashboard.model.request.AclRequest;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.AclService;
import org.apache.rocketmq.dashboard.support.JsonResult;
import org.apache.rocketmq.dashboard.util.WebUtil;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/acl")
@Permission
public class AclController {
@Resource
private AclService aclService;
@Resource
private RMQConfigure configure;
@GetMapping("/enable.query")
public Object isEnableAcl() {
return new JsonResult<>(configure.isACLEnabled());
}
@GetMapping("/config.query")
public AclConfig getAclConfig(HttpServletRequest request) {
if (!configure.isLoginRequired()) {
return aclService.getAclConfig(false);
}
UserInfo userInfo = (UserInfo) WebUtil.getValueFromSession(request, WebUtil.USER_INFO);
// if user info is null but reach here, must exclude secret key for safety.
return aclService.getAclConfig(userInfo == null || userInfo.getUser().getType() != User.ADMIN);
}
@PostMapping("/add.do")
public Object addAclConfig(@RequestBody PlainAccessConfig config) {
Preconditions.checkArgument(StringUtils.isNotEmpty(config.getAccessKey()), "accessKey is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(config.getSecretKey()), "secretKey is null");
aclService.addAclConfig(config);
return true;
}
@PostMapping("/delete.do")
public Object deleteAclConfig(@RequestBody PlainAccessConfig config) {
Preconditions.checkArgument(StringUtils.isNotEmpty(config.getAccessKey()), "accessKey is null");
aclService.deleteAclConfig(config);
return true;
}
@PostMapping("/update.do")
public Object updateAclConfig(@RequestBody PlainAccessConfig config) {
Preconditions.checkArgument(StringUtils.isNotEmpty(config.getSecretKey()), "secretKey is null");
aclService.updateAclConfig(config);
return true;
}
@PostMapping("/topic/add.do")
public Object addAclTopicConfig(@RequestBody AclRequest request) {
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getConfig().getAccessKey()), "accessKey is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getConfig().getSecretKey()), "secretKey is null");
Preconditions.checkArgument(CollectionUtils.isNotEmpty(request.getConfig().getTopicPerms()), "topic perms is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getTopicPerm()), "topic perm is null");
aclService.addOrUpdateAclTopicConfig(request);
return true;
}
@PostMapping("/group/add.do")
public Object addAclGroupConfig(@RequestBody AclRequest request) {
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getConfig().getAccessKey()), "accessKey is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getConfig().getSecretKey()), "secretKey is null");
Preconditions.checkArgument(CollectionUtils.isNotEmpty(request.getConfig().getGroupPerms()), "group perms is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getGroupPerm()), "group perm is null");
aclService.addOrUpdateAclGroupConfig(request);
return true;
}
@PostMapping("/perm/delete.do")
public Object deletePermConfig(@RequestBody AclRequest request) {
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getConfig().getAccessKey()), "accessKey is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(request.getConfig().getSecretKey()), "secretKey is null");
aclService.deletePermConfig(request);
return true;
}
@PostMapping("/sync.do")
public Object syncConfig(@RequestBody PlainAccessConfig config) {
Preconditions.checkArgument(StringUtils.isNotEmpty(config.getAccessKey()), "accessKey is null");
Preconditions.checkArgument(StringUtils.isNotEmpty(config.getSecretKey()), "secretKey is null");
aclService.syncData(config);
return true;
}
@PostMapping("/white/list/add.do")
public Object addWhiteList(@RequestBody List<String> whiteList) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(whiteList), "white list is null");
aclService.addWhiteList(whiteList);
return true;
}
@DeleteMapping("/white/list/delete.do")
public Object deleteWhiteAddr(@RequestParam String request) {
aclService.deleteWhiteAddr(request);
return true;
}
@PostMapping("/white/list/sync.do")
public Object synchronizeWhiteList(@RequestBody List<String> whiteList) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(whiteList), "white list is null");
aclService.synchronizeWhiteList(whiteList);
return true;
}
}

View File

@@ -16,6 +16,7 @@
*/
package org.apache.rocketmq.dashboard.controller;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.ClusterService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/cluster")
@Permission
public class ClusterController {
@Resource

View File

@@ -24,6 +24,7 @@ import org.apache.rocketmq.dashboard.model.ConnectionInfo;
import org.apache.rocketmq.dashboard.model.request.ConsumerConfigInfo;
import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.ConsumerService;
import org.apache.rocketmq.dashboard.util.JsonUtil;
import org.slf4j.Logger;
@@ -37,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/consumer")
@Permission
public class ConsumerController {
private Logger logger = LoggerFactory.getLogger(ConsumerController.class);
@@ -45,8 +47,8 @@ public class ConsumerController {
@RequestMapping(value = "/groupList.query")
@ResponseBody
public Object list() {
return consumerService.queryGroupList();
public Object list(@RequestParam(value = "skipSysGroup", required = false) boolean skipSysGroup) {
return consumerService.queryGroupList(skipSysGroup);
}
@RequestMapping(value = "/group.query")

View File

@@ -20,6 +20,7 @@ package org.apache.rocketmq.dashboard.controller;
import javax.annotation.Resource;
import com.google.common.base.Strings;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.DashboardService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/dashboard")
@Permission
public class DashboardController {
@Resource
@@ -49,7 +51,7 @@ public class DashboardController {
return dashboardService.queryTopicData(date,topicName);
}
@RequestMapping(value = "/topicCurrent", method = RequestMethod.GET)
@RequestMapping(value = "/topicCurrent.query", method = RequestMethod.GET)
@ResponseBody
public Object topicCurrent() {
return dashboardService.queryTopicCurrentData();

View File

@@ -0,0 +1,108 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.controller;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.DlqMessageExcelModel;
import org.apache.rocketmq.dashboard.model.DlqMessageRequest;
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.DlqMessageService;
import org.apache.rocketmq.dashboard.util.ExcelUtil;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/dlqMessage")
@Permission
@Slf4j
public class DlqMessageController {
@Resource
private DlqMessageService dlqMessageService;
@Resource
private MQAdminExt mqAdminExt;
@RequestMapping(value = "/queryDlqMessageByConsumerGroup.query", method = RequestMethod.POST)
@ResponseBody
public Object queryDlqMessageByConsumerGroup(@RequestBody MessageQuery query) {
return dlqMessageService.queryDlqMessageByPage(query);
}
@GetMapping(value = "/exportDlqMessage.do")
public void exportDlqMessage(HttpServletResponse response, @RequestParam String consumerGroup,
@RequestParam String msgId) {
MessageExt messageExt = null;
try {
String topic = MixAll.DLQ_GROUP_TOPIC_PREFIX + consumerGroup;
messageExt = mqAdminExt.viewMessage(topic, msgId);
} catch (Exception e) {
throw new ServiceException(-1, String.format("Failed to query message by Id: %s", msgId));
}
DlqMessageExcelModel excelModel = new DlqMessageExcelModel(messageExt);
try {
ExcelUtil.writeExcel(response, Lists.newArrayList(excelModel), "dlq", "dlq", DlqMessageExcelModel.class);
} catch (Exception e) {
throw new ServiceException(-1, String.format("export dlq message failed!"));
}
}
@PostMapping(value = "/batchResendDlqMessage.do")
@ResponseBody
public Object batchResendDlqMessage(@RequestBody List<DlqMessageRequest> dlqMessages) {
return dlqMessageService.batchResendDlqMessage(dlqMessages);
}
@PostMapping(value = "/batchExportDlqMessage.do")
public void batchExportDlqMessage(HttpServletResponse response, @RequestBody List<DlqMessageRequest> dlqMessages) {
List<DlqMessageExcelModel> dlqMessageExcelModelList = new ArrayList<>(dlqMessages.size());
for (DlqMessageRequest dlqMessage : dlqMessages) {
DlqMessageExcelModel excelModel = new DlqMessageExcelModel();
try {
String topic = MixAll.DLQ_GROUP_TOPIC_PREFIX + dlqMessage.getConsumerGroup();
MessageExt messageExt = mqAdminExt.viewMessage(topic, dlqMessage.getMsgId());
excelModel = new DlqMessageExcelModel(messageExt);
} catch (Exception e) {
log.error("Failed to query message by Id:{}", dlqMessage.getMsgId(), e);
excelModel.setMsgId(dlqMessage.getMsgId());
excelModel.setException(e.getMessage());
}
dlqMessageExcelModelList.add(excelModel);
}
try {
ExcelUtil.writeExcel(response, dlqMessageExcelModelList, "dlqs", "dlqs", DlqMessageExcelModel.class);
} catch (Exception e) {
throw new ServiceException(-1, String.format("export dlq message failed!"));
}
}
}

View File

@@ -19,6 +19,7 @@ package org.apache.rocketmq.dashboard.controller;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.model.LoginInfo;
import org.apache.rocketmq.dashboard.model.LoginResult;
import org.apache.rocketmq.dashboard.model.User;
import org.apache.rocketmq.dashboard.model.UserInfo;
import org.apache.rocketmq.dashboard.service.UserService;
@@ -65,7 +66,7 @@ public class LoginController {
@RequestMapping(value = "/login.do", method = RequestMethod.POST)
@ResponseBody
public JsonResult<String> login(@RequestParam("username") String username,
public Object login(@RequestParam("username") String username,
@RequestParam(value = "password") String password,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
@@ -80,7 +81,8 @@ public class LoginController {
WebUtil.setSessionValue(request, WebUtil.USER_INFO, userInfo);
WebUtil.setSessionValue(request, WebUtil.USER_NAME, username);
userInfo.setSessionId(WebUtil.getSessionId(request));
return new JsonResult<>(contextPath);
LoginResult result = new LoginResult(username, user.getType(), contextPath);
return result;
}
}

View File

@@ -22,6 +22,7 @@ import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.dashboard.model.MessagePage;
import org.apache.rocketmq.dashboard.model.MessageView;
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.MessageService;
import org.apache.rocketmq.dashboard.util.JsonUtil;
import org.apache.rocketmq.tools.admin.api.MessageTrack;
@@ -41,6 +42,7 @@ import java.util.Map;
@Controller
@RequestMapping("/message")
@Permission
public class MessageController {
private Logger logger = LoggerFactory.getLogger(MessageController.class);
@Resource

View File

@@ -26,6 +26,7 @@ import javax.annotation.Resource;
import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.dashboard.model.MessageView;
import org.apache.rocketmq.dashboard.model.trace.MessageTraceGraph;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.MessageService;
import org.apache.rocketmq.dashboard.service.MessageTraceService;
import org.apache.rocketmq.tools.admin.api.MessageTrack;
@@ -37,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/messageTrace")
@Permission
public class MessageTraceController {
@Resource
@@ -62,7 +64,8 @@ public class MessageTraceController {
@RequestMapping(value = "/viewMessageTraceGraph.query", method = RequestMethod.GET)
@ResponseBody
public MessageTraceGraph viewMessageTraceGraph(@RequestParam String msgId) {
return messageTraceService.queryMessageTraceGraph(msgId);
public MessageTraceGraph viewMessageTraceGraph(@RequestParam String msgId,
@RequestParam(required = false) String traceTopic) {
return messageTraceService.queryMessageTraceGraph(msgId, traceTopic);
}
}

View File

@@ -18,6 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
import javax.annotation.Resource;
import org.apache.rocketmq.dashboard.model.ConsumerMonitorConfig;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.MonitorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/monitor")
@Permission
public class MonitorController {
private Logger logger = LoggerFactory.getLogger(MonitorController.class);

View File

@@ -18,6 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
import javax.annotation.Resource;
import org.apache.rocketmq.dashboard.aspect.admin.annotation.OriginalControllerReturnValue;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.OpsService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,11 +27,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/rocketmq")
@Permission
public class NamesvrController {
@Resource
private OpsService opsService;
@RequestMapping(value = "/nsaddr", method = RequestMethod.GET)
@RequestMapping(value = "/nsaddr.query", method = RequestMethod.GET)
@ResponseBody
@OriginalControllerReturnValue
public Object nsaddr() {

View File

@@ -16,7 +16,10 @@
*/
package org.apache.rocketmq.dashboard.controller;
import com.google.common.base.Preconditions;
import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.OpsService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,6 +29,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/ops")
@Permission
public class OpsController {
@Resource
@@ -44,6 +48,15 @@ public class OpsController {
return true;
}
@RequestMapping(value = "/addNameSvrAddr.do", method = RequestMethod.POST)
@ResponseBody
public Object addNameSvrAddr(@RequestParam String newNamesrvAddr) {
Preconditions.checkArgument(StringUtils.isNotEmpty(newNamesrvAddr),
"namesrvAddr can not be blank");
opsService.addNameSvrAddr(newNamesrvAddr);
return true;
}
@RequestMapping(value = "/updateIsVIPChannel.do", method = RequestMethod.POST)
@ResponseBody
public Object updateIsVIPChannel(@RequestParam String useVIPChannel) {
@@ -51,14 +64,12 @@ public class OpsController {
return true;
}
@RequestMapping(value = "/rocketMqStatus.query", method = RequestMethod.GET)
@ResponseBody
public Object clusterStatus() {
return opsService.rocketMqStatusCheck();
}
@RequestMapping(value = "/updateUseTLS.do", method = RequestMethod.POST)
@ResponseBody
public Object updateUseTLS(@RequestParam String useTLS) {

View File

@@ -19,6 +19,7 @@ package org.apache.rocketmq.dashboard.controller;
import javax.annotation.Resource;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
import org.apache.rocketmq.dashboard.model.ConnectionInfo;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.ProducerService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/producer")
@Permission
public class ProducerController {
@Resource

View File

@@ -17,6 +17,7 @@
package org.apache.rocketmq.dashboard.controller;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.dashboard.model.request.SendTopicMessageRequest;
import org.apache.rocketmq.dashboard.model.request.TopicConfigInfo;
@@ -38,6 +39,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/topic")
@Permission
public class TopicController {
private Logger logger = LoggerFactory.getLogger(TopicController.class);
@@ -49,13 +51,9 @@ public class TopicController {
@RequestMapping(value = "/list.query", method = RequestMethod.GET)
@ResponseBody
public Object list(@RequestParam(value = "skipSysProcess", required = false) String skipSysProcess)
throws MQClientException, RemotingException, InterruptedException {
boolean flag = false;
if ("true".equals(skipSysProcess)) {
flag = true;
}
return topicService.fetchAllTopicList(flag);
public Object list(@RequestParam(value = "skipSysProcess", required = false) boolean skipSysProcess,
@RequestParam(value = "skipRetryAndDlq", required = false) boolean skipRetryAndDlq) {
return topicService.fetchAllTopicList(skipSysProcess, skipRetryAndDlq);
}
@RequestMapping(value = "/stats.query", method = RequestMethod.GET)

View File

@@ -0,0 +1,87 @@
/*
* 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 com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.util.DateUtils;
import com.google.common.base.Charsets;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.rocketmq.common.message.MessageExt;
@Data
@NoArgsConstructor
public class DlqMessageExcelModel extends BaseRowModel implements Serializable {
@ExcelProperty(value = "topic", index = 0)
@ColumnWidth(value = 15)
private String topic;
@ExcelProperty(value = "msgId", index = 1)
@ColumnWidth(value = 15)
private String msgId;
@ExcelProperty(value = "bornHost", index = 2)
@ColumnWidth(value = 15)
private String bornHost;
@ExcelProperty(value = "bornTimestamp", index = 3)
@ColumnWidth(value = 25)
private String bornTimestamp;
@ExcelProperty(value = "storeTimestamp", index = 4)
@ColumnWidth(value = 25)
private String storeTimestamp;
@ExcelProperty(value = "reconsumeTimes", index = 5)
@ColumnWidth(value = 25)
private int reconsumeTimes;
@ExcelProperty(value = "properties", index = 6)
@ColumnWidth(value = 20)
private String properties;
@ExcelProperty(value = "messageBody", index = 7)
@ColumnWidth(value = 20)
private String messageBody;
@ExcelProperty(value = "bodyCRC", index = 8)
@ColumnWidth(value = 15)
private int bodyCRC;
@ExcelProperty(value = "exception", index = 9)
@ColumnWidth(value = 30)
private String exception;
public DlqMessageExcelModel(MessageExt messageExt) {
this.topic = messageExt.getTopic();
this.msgId = messageExt.getMsgId();
this.bornHost = messageExt.getBornHostString();
this.bornTimestamp = DateUtils.format(new Date(messageExt.getBornTimestamp()), DateUtils.DATE_FORMAT_19);
this.storeTimestamp = DateUtils.format(new Date(messageExt.getStoreTimestamp()), DateUtils.DATE_FORMAT_19);
this.reconsumeTimes = messageExt.getReconsumeTimes();
this.properties = messageExt.getProperties().toString();
this.messageBody = new String(messageExt.getBody(), Charsets.UTF_8);
this.bodyCRC = messageExt.getBodyCRC();
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.Data;
@Data
public class DlqMessageRequest {
private String topicName;
private String consumerGroup;
private String msgId;
private String clientId;
}

View File

@@ -0,0 +1,34 @@
/*
* 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.Data;
import org.apache.rocketmq.common.protocol.body.CMResult;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
@Data
public class DlqMessageResendResult {
private CMResult consumeResult;
private String remark;
private String msgId;
public DlqMessageResendResult(ConsumeMessageDirectlyResult consumeMessageDirectlyResult, String msgId) {
this.consumeResult = consumeMessageDirectlyResult.getConsumeResult();
this.remark = consumeMessageDirectlyResult.getRemark();
this.msgId = msgId;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginResult {
private String loginUserName;
/**
* 0: normal 1: admin
*/
private int loginUserRole;
private String contextPath;
}

View File

@@ -0,0 +1,30 @@
/*
* 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.request;
import lombok.Data;
import org.apache.rocketmq.common.PlainAccessConfig;
@Data
public class AclRequest {
private PlainAccessConfig config;
private String topicPerm;
private String groupPerm;
}

View File

@@ -14,18 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.permisssion;
package org.apache.rocketmq.dashboard.aspect.admin.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiMQAdminCmdMethod {
long timeoutMillis() default 0;
public @interface Permission {
}

View File

@@ -0,0 +1,69 @@
/*
* 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.permisssion;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.UserInfo;
import org.apache.rocketmq.dashboard.service.PermissionService;
import org.apache.rocketmq.dashboard.util.WebUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
public class PermissionAspect {
@Resource
private RMQConfigure configure;
@Resource
private PermissionService permissionService;
/**
* @Permission can be applied to the Controller class to implement Permission verification on all methods in the class
* can also be applied to methods in a class for fine control
*/
@Pointcut("@annotation(org.apache.rocketmq.dashboard.permisssion.Permission) || @within(org.apache.rocketmq.dashboard.permisssion.Permission)")
private void permission() {
}
@Around("permission()")
public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
if (configure.isLoginRequired()) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String url = request.getRequestURI();
UserInfo userInfo = (UserInfo) request.getSession().getAttribute(WebUtil.USER_INFO);
if (userInfo == null || userInfo.getUser() == null) {
throw new ServiceException(-1, "user not login");
}
boolean checkResult = permissionService.checkUrlAvailable(userInfo, url);
if (!checkResult) {
throw new ServiceException(-1, "no permission");
}
}
return joinPoint.proceed();
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.permisssion;
public enum UserRoleEnum {
ADMIN(1, "admin"),
ORDINARY(0, "ordinary");
private int roleType;
private String roleName;
UserRoleEnum(int roleType, String roleName) {
this.roleType = roleType;
this.roleName = roleName;
}
public int getRoleType() {
return roleType;
}
public String getRoleName() {
return roleName;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service;
import java.util.List;
import org.apache.rocketmq.common.AclConfig;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.dashboard.model.request.AclRequest;
public interface AclService {
AclConfig getAclConfig(boolean excludeSecretKey);
void addAclConfig(PlainAccessConfig config);
void deleteAclConfig(PlainAccessConfig config);
void updateAclConfig(PlainAccessConfig config);
void addOrUpdateAclTopicConfig(AclRequest request);
void addOrUpdateAclGroupConfig(AclRequest request);
void deletePermConfig(AclRequest request);
void syncData(PlainAccessConfig config);
void addWhiteList(List<String> whiteList);
void deleteWhiteAddr(String addr);
void synchronizeWhiteList(List<String> whiteList);
}

View File

@@ -31,7 +31,7 @@ import java.util.Map;
import java.util.Set;
public interface ConsumerService {
List<GroupConsumeInfo> queryGroupList();
List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup);
GroupConsumeInfo queryGroup(String consumerGroup);

View File

@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service;
import java.util.List;
import org.apache.rocketmq.dashboard.model.DlqMessageResendResult;
import org.apache.rocketmq.dashboard.model.DlqMessageRequest;
import org.apache.rocketmq.dashboard.model.MessagePage;
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
public interface DlqMessageService {
MessagePage queryDlqMessageByPage(MessageQuery query);
List<DlqMessageResendResult> batchResendDlqMessage(List<DlqMessageRequest> dlqMessages);
}

View File

@@ -27,5 +27,5 @@ public interface MessageTraceService {
List<MessageTraceView> queryMessageTraceByTopicAndKey(final String topic, final String key);
MessageTraceGraph queryMessageTraceGraph(final String key);
MessageTraceGraph queryMessageTraceGraph(final String key, final String traceTopic);
}

View File

@@ -31,4 +31,6 @@ public interface OpsService {
boolean updateIsVIPChannel(String useVIPChannel);
boolean updateUseTLS(boolean useTLS);
void addNameSvrAddr(String namesrvAddr);
}

View File

@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service;
import org.apache.rocketmq.dashboard.model.UserInfo;
public interface PermissionService {
boolean checkUrlAvailable(UserInfo userInfo, String url);
}

View File

@@ -29,7 +29,7 @@ import org.apache.rocketmq.dashboard.model.request.TopicConfigInfo;
import java.util.List;
public interface TopicService {
TopicList fetchAllTopicList(boolean skipSysProcess);
TopicList fetchAllTopicList(boolean skipSysProcess, boolean skipRetryAndDlq);
TopicStatsTable stats(String topic);

View File

@@ -91,29 +91,34 @@ public class MQAdminExtImpl implements MQAdminExt {
MQAdminInstance.threadLocalMQAdminExt().createAndUpdateTopicConfig(addr, config);
}
@Override public void createAndUpdatePlainAccessConfig(String addr,
@Override
public void createAndUpdatePlainAccessConfig(String addr,
PlainAccessConfig plainAccessConfig) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createAndUpdatePlainAccessConfig(addr, plainAccessConfig);
}
@Override public void deletePlainAccessConfig(String addr,
@Override
public void deletePlainAccessConfig(String addr,
String accessKey) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().deletePlainAccessConfig(addr, accessKey);
}
@Override public void updateGlobalWhiteAddrConfig(String addr,
@Override
public void updateGlobalWhiteAddrConfig(String addr,
String globalWhiteAddrs) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().updateGlobalWhiteAddrConfig(addr, globalWhiteAddrs);
}
@Override public ClusterAclVersionInfo examineBrokerClusterAclVersionInfo(
@Override
public ClusterAclVersionInfo examineBrokerClusterAclVersionInfo(
String addr) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return null;
}
@Override public AclConfig examineBrokerClusterAclConfig(
@Override
public AclConfig examineBrokerClusterAclConfig(
String addr) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return null;
return MQAdminInstance.threadLocalMQAdminExt().examineBrokerClusterAclConfig(addr);
}
@Override
@@ -235,6 +240,12 @@ public class MQAdminExtImpl implements MQAdminExt {
return MQAdminInstance.threadLocalMQAdminExt().wipeWritePermOfBroker(namesrvAddr, brokerName);
}
@Override
public int addWritePermOfBroker(String namesrvAddr,
String brokerName) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().addWritePermOfBroker(namesrvAddr, brokerName);
}
@Override
public void putKVConfig(String namespace, String key, String value) {
MQAdminInstance.threadLocalMQAdminExt().putKVConfig(namespace, key, value);
@@ -272,8 +283,9 @@ public class MQAdminExtImpl implements MQAdminExt {
}
@Override
public void deleteSubscriptionGroup(String addr, String groupName, boolean removeOffset) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
throw new UnsupportedOperationException();
public void deleteSubscriptionGroup(String addr, String groupName, boolean removeOffset)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().deleteSubscriptionGroup(addr, groupName, removeOffset);
}
@Override
@@ -511,9 +523,21 @@ public class MQAdminExtImpl implements MQAdminExt {
}
@Override
public TopicConfigSerializeWrapper getAllTopicGroup(String brokerAddr,
public SubscriptionGroupWrapper getUserSubscriptionGroup(String brokerAddr,
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQBrokerException {
return MQAdminInstance.threadLocalMQAdminExt().getAllTopicGroup(brokerAddr, timeoutMillis);
return MQAdminInstance.threadLocalMQAdminExt().getUserSubscriptionGroup(brokerAddr, timeoutMillis);
}
@Override
public TopicConfigSerializeWrapper getAllTopicConfig(String brokerAddr,
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 {
return MQAdminInstance.threadLocalMQAdminExt().getUserTopicConfig(brokerAddr, specialTopic, timeoutMillis);
}
@Override

View File

@@ -16,29 +16,27 @@
*/
package org.apache.rocketmq.dashboard.service.client;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.rocketmq.client.impl.MQClientAPIImpl;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.RemotingClient;
import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
import org.apache.rocketmq.tools.admin.DefaultMQAdminExtImpl;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.joor.Reflect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MQAdminInstance {
private static final ThreadLocal<DefaultMQAdminExt> MQ_ADMIN_EXT_THREAD_LOCAL = new ThreadLocal<DefaultMQAdminExt>();
private static final ThreadLocal<Integer> INIT_COUNTER = new ThreadLocal<Integer>();
private static final Logger LOGGER = LoggerFactory.getLogger(MQAdminInstance.class);
private static final ThreadLocal<MQAdminExt> MQ_ADMIN_EXT_THREAD_LOCAL = new ThreadLocal<>();
public static MQAdminExt threadLocalMQAdminExt() {
DefaultMQAdminExt defaultMQAdminExt = MQ_ADMIN_EXT_THREAD_LOCAL.get();
if (defaultMQAdminExt == null) {
MQAdminExt mqAdminExt = MQ_ADMIN_EXT_THREAD_LOCAL.get();
if (mqAdminExt == null) {
throw new IllegalStateException("defaultMQAdminExt should be init before you get this");
}
return defaultMQAdminExt;
return mqAdminExt;
}
public static RemotingClient threadLocalRemotingClient() {
@@ -51,44 +49,27 @@ public class MQAdminInstance {
DefaultMQAdminExtImpl defaultMQAdminExtImpl = Reflect.on(MQAdminInstance.threadLocalMQAdminExt()).get("defaultMQAdminExtImpl");
return Reflect.on(defaultMQAdminExtImpl).get("mqClientInstance");
}
public static void initMQAdminInstance(long timeoutMillis,String accessKey,String secretKey, boolean useTLS) throws MQClientException {
Integer nowCount = INIT_COUNTER.get();
if (nowCount == null) {
RPCHook rpcHook = null;
boolean isEnableAcl = !StringUtils.isEmpty(accessKey) && !StringUtils.isEmpty(secretKey);
if (isEnableAcl) {
rpcHook = new AclClientRPCHook(new SessionCredentials(accessKey, secretKey));
public static void createMQAdmin(GenericObjectPool<MQAdminExt> mqAdminExtPool) {
try {
// Get the mqAdmin instance from the object pool
MQAdminExt mqAdminExt = mqAdminExtPool.borrowObject();
MQ_ADMIN_EXT_THREAD_LOCAL.set(mqAdminExt);
} catch (Exception e) {
LOGGER.error("get mqAdmin from pool error", e);
}
DefaultMQAdminExt defaultMQAdminExt;
if (timeoutMillis > 0) {
defaultMQAdminExt = new DefaultMQAdminExt(rpcHook,timeoutMillis);
}
else {
defaultMQAdminExt = new DefaultMQAdminExt(rpcHook);
}
defaultMQAdminExt.setUseTLS(useTLS);
defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis()));
defaultMQAdminExt.start();
MQ_ADMIN_EXT_THREAD_LOCAL.set(defaultMQAdminExt);
INIT_COUNTER.set(1);
}
else {
INIT_COUNTER.set(nowCount + 1);
}
}
public static void destroyMQAdminInstance() {
Integer nowCount = INIT_COUNTER.get() - 1;
if (nowCount > 0) {
INIT_COUNTER.set(nowCount);
return;
}
public static void returnMQAdmin(GenericObjectPool<MQAdminExt> mqAdminExtPool) {
MQAdminExt mqAdminExt = MQ_ADMIN_EXT_THREAD_LOCAL.get();
if (mqAdminExt != null) {
mqAdminExt.shutdown();
MQ_ADMIN_EXT_THREAD_LOCAL.remove();
INIT_COUNTER.remove();
try {
// After execution, return the mqAdmin instance to the object pool
mqAdminExtPool.returnObject(mqAdminExt);
} catch (Exception e) {
LOGGER.error("return mqAdmin to pool error", e);
}
}
MQ_ADMIN_EXT_THREAD_LOCAL.remove();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.srvutil.FileWatchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractFileStore {
public final Logger log = LoggerFactory.getLogger(this.getClass());
public String filePath;
public AbstractFileStore(RMQConfigure configure, String fileName) {
filePath = configure.getRocketMqDashboardDataPath() + File.separator + fileName;
if (!new File(filePath).exists()) {
// Use the default path
InputStream inputStream = getClass().getResourceAsStream("/" + fileName);
if (inputStream == null) {
log.error(String.format("Can not found the file %s in Spring Boot jar", fileName));
System.exit(1);
} else {
try {
load(inputStream);
} catch (Exception e) {
log.error("fail to load file {}", filePath, e);
} finally {
try {
inputStream.close();
} catch (IOException e) {
log.error("inputStream close exception", e);
}
}
}
} else {
log.info(String.format("configure file is %s", filePath));
load();
watch();
}
}
abstract void load(InputStream inputStream);
private void load() {
load(null);
}
private boolean watch() {
try {
FileWatchService fileWatchService = new FileWatchService(new String[] {filePath}, new FileWatchService.Listener() {
@Override
public void onChanged(String path) {
log.info("The file changed, reload the context");
load();
}
});
fileWatchService.start();
log.info("Succeed to start FileWatcherService");
return true;
} catch (Exception e) {
log.error("Failed to start FileWatcherService", e);
}
return false;
}
}

View File

@@ -0,0 +1,359 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Throwables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.AclConfig;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.dashboard.model.request.AclRequest;
import org.apache.rocketmq.dashboard.service.AbstractCommonService;
import org.apache.rocketmq.dashboard.service.AclService;
import org.apache.rocketmq.remoting.exception.RemotingConnectException;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class AclServiceImpl extends AbstractCommonService implements AclService {
@Override
public AclConfig getAclConfig(boolean excludeSecretKey) {
try {
Optional<String> addr = getMasterSet().stream().findFirst();
if (addr.isPresent()) {
if (!excludeSecretKey) {
return mqAdminExt.examineBrokerClusterAclConfig(addr.get());
} else {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr.get());
if (CollectionUtils.isNotEmpty(aclConfig.getPlainAccessConfigs())) {
aclConfig.getPlainAccessConfigs().forEach(pac -> pac.setSecretKey(null));
}
return aclConfig;
}
}
} catch (Exception e) {
log.error("getAclConfig error.", e);
throw Throwables.propagate(e);
}
AclConfig aclConfig = new AclConfig();
aclConfig.setGlobalWhiteAddrs(Collections.emptyList());
aclConfig.setPlainAccessConfigs(Collections.emptyList());
return aclConfig;
}
@Override
public void addAclConfig(PlainAccessConfig config) {
try {
Set<String> masterSet = getMasterSet();
if (masterSet.isEmpty()) {
throw new IllegalStateException("broker addr list is empty");
}
// check to see if account is exists
for (String addr : masterSet) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
List<PlainAccessConfig> plainAccessConfigs = aclConfig.getPlainAccessConfigs();
for (PlainAccessConfig pac : plainAccessConfigs) {
if (pac.getAccessKey().equals(config.getAccessKey())) {
throw new IllegalArgumentException(String.format("broker: %s, exist accessKey: %s", addr, config.getAccessKey()));
}
}
}
// all broker
for (String addr : getBrokerAddrs()) {
mqAdminExt.createAndUpdatePlainAccessConfig(addr, config);
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void deleteAclConfig(PlainAccessConfig config) {
try {
for (String addr : getBrokerAddrs()) {
log.info("Start to delete acl [{}] from broker [{}]", config.getAccessKey(), addr);
if (isExistAccessKey(config.getAccessKey(), addr)) {
mqAdminExt.deletePlainAccessConfig(addr, config.getAccessKey());
}
log.info("Delete acl [{}] from broker [{}] complete", config.getAccessKey(), addr);
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void updateAclConfig(PlainAccessConfig config) {
try {
for (String addr : getBrokerAddrs()) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
if (aclConfig.getPlainAccessConfigs() != null) {
PlainAccessConfig remoteConfig = null;
for (PlainAccessConfig pac : aclConfig.getPlainAccessConfigs()) {
if (pac.getAccessKey().equals(config.getAccessKey())) {
remoteConfig = pac;
break;
}
}
if (remoteConfig != null) {
remoteConfig.setSecretKey(config.getSecretKey());
remoteConfig.setAdmin(config.isAdmin());
config = remoteConfig;
}
}
mqAdminExt.createAndUpdatePlainAccessConfig(addr, config);
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void addOrUpdateAclTopicConfig(AclRequest request) {
try {
PlainAccessConfig addConfig = request.getConfig();
for (String addr : getBrokerAddrs()) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
PlainAccessConfig remoteConfig = null;
if (aclConfig.getPlainAccessConfigs() != null) {
for (PlainAccessConfig config : aclConfig.getPlainAccessConfigs()) {
if (config.getAccessKey().equals(addConfig.getAccessKey())) {
remoteConfig = config;
break;
}
}
}
if (remoteConfig == null) {
// Maybe the broker no acl config of the access key, therefore add it;
mqAdminExt.createAndUpdatePlainAccessConfig(addr, addConfig);
} else {
if (remoteConfig.getTopicPerms() == null) {
remoteConfig.setTopicPerms(new ArrayList<>());
}
removeExist(remoteConfig.getTopicPerms(), request.getTopicPerm().split("=")[0]);
remoteConfig.getTopicPerms().add(request.getTopicPerm());
mqAdminExt.createAndUpdatePlainAccessConfig(addr, remoteConfig);
}
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void addOrUpdateAclGroupConfig(AclRequest request) {
try {
PlainAccessConfig addConfig = request.getConfig();
for (String addr : getBrokerAddrs()) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
PlainAccessConfig remoteConfig = null;
if (aclConfig.getPlainAccessConfigs() != null) {
for (PlainAccessConfig config : aclConfig.getPlainAccessConfigs()) {
if (config.getAccessKey().equals(addConfig.getAccessKey())) {
remoteConfig = config;
break;
}
}
}
if (remoteConfig == null) {
// May be the broker no acl config of the access key, therefore add it;
mqAdminExt.createAndUpdatePlainAccessConfig(addr, addConfig);
} else {
if (remoteConfig.getGroupPerms() == null) {
remoteConfig.setGroupPerms(new ArrayList<>());
}
removeExist(remoteConfig.getGroupPerms(), request.getGroupPerm().split("=")[0]);
remoteConfig.getGroupPerms().add(request.getGroupPerm());
mqAdminExt.createAndUpdatePlainAccessConfig(addr, remoteConfig);
}
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void deletePermConfig(AclRequest request) {
try {
PlainAccessConfig deleteConfig = request.getConfig();
String topic = StringUtils.isNotEmpty(request.getTopicPerm()) ? request.getTopicPerm().split("=")[0] : null;
String group = StringUtils.isNotEmpty(request.getGroupPerm()) ? request.getGroupPerm().split("=")[0] : null;
if (deleteConfig.getTopicPerms() != null && topic != null) {
removeExist(deleteConfig.getTopicPerms(), topic);
}
if (deleteConfig.getGroupPerms() != null && group != null) {
removeExist(deleteConfig.getGroupPerms(), group);
}
for (String addr : getBrokerAddrs()) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
PlainAccessConfig remoteConfig = null;
if (aclConfig.getPlainAccessConfigs() != null) {
for (PlainAccessConfig config : aclConfig.getPlainAccessConfigs()) {
if (config.getAccessKey().equals(deleteConfig.getAccessKey())) {
remoteConfig = config;
break;
}
}
}
if (remoteConfig == null) {
// Maybe the broker no acl config of the access key, therefore add it;
mqAdminExt.createAndUpdatePlainAccessConfig(addr, deleteConfig);
} else {
if (remoteConfig.getTopicPerms() != null && topic != null) {
removeExist(remoteConfig.getTopicPerms(), topic);
}
if (remoteConfig.getGroupPerms() != null && group != null) {
removeExist(remoteConfig.getGroupPerms(), group);
}
mqAdminExt.createAndUpdatePlainAccessConfig(addr, remoteConfig);
}
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void syncData(PlainAccessConfig config) {
try {
for (String addr : getBrokerAddrs()) {
mqAdminExt.createAndUpdatePlainAccessConfig(addr, config);
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void addWhiteList(List<String> whiteList) {
if (whiteList == null) {
return;
}
try {
for (String addr : getBrokerAddrs()) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
if (aclConfig.getGlobalWhiteAddrs() != null) {
aclConfig.setGlobalWhiteAddrs(Stream.of(whiteList, aclConfig.getGlobalWhiteAddrs()).flatMap(Collection::stream).distinct().collect(Collectors.toList()));
} else {
aclConfig.setGlobalWhiteAddrs(whiteList);
}
mqAdminExt.updateGlobalWhiteAddrConfig(addr, StringUtils.join(aclConfig.getGlobalWhiteAddrs(), ","));
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void deleteWhiteAddr(String deleteAddr) {
try {
for (String addr : getBrokerAddrs()) {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
if (aclConfig.getGlobalWhiteAddrs() == null || aclConfig.getGlobalWhiteAddrs().isEmpty()) {
continue;
}
aclConfig.getGlobalWhiteAddrs().remove(deleteAddr);
mqAdminExt.updateGlobalWhiteAddrConfig(addr, StringUtils.join(aclConfig.getGlobalWhiteAddrs(), ","));
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void synchronizeWhiteList(List<String> whiteList) {
if (whiteList == null) {
return;
}
try {
for (String addr : getBrokerAddrs()) {
mqAdminExt.updateGlobalWhiteAddrConfig(addr, StringUtils.join(whiteList, ","));
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
private void removeExist(List<String> list, String name) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String v = iterator.next();
String cmp = v.split("=")[0];
if (cmp.equals(name)) {
iterator.remove();
}
}
}
private boolean isExistAccessKey(String accessKey,
String addr) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
AclConfig aclConfig = mqAdminExt.examineBrokerClusterAclConfig(addr);
List<PlainAccessConfig> plainAccessConfigs = aclConfig.getPlainAccessConfigs();
if (plainAccessConfigs == null || plainAccessConfigs.isEmpty()) {
return false;
}
for (PlainAccessConfig config : plainAccessConfigs) {
if (accessKey.equals(config.getAccessKey())) {
return true;
}
}
return false;
}
private Set<BrokerData> getBrokerDataSet() throws InterruptedException, RemotingConnectException, RemotingTimeoutException, RemotingSendRequestException, MQBrokerException {
ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
Map<String, BrokerData> brokerDataMap = clusterInfo.getBrokerAddrTable();
return new HashSet<>(brokerDataMap.values());
}
private Set<String> getMasterSet() throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
return getBrokerDataSet().stream().map(data -> data.getBrokerAddrs().get(MixAll.MASTER_ID)).collect(Collectors.toSet());
}
private Set<String> getBrokerAddrs() throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
Set<String> brokerAddrs = new HashSet<>();
getBrokerDataSet().forEach(data -> brokerAddrs.addAll(data.getBrokerAddrs().values()));
return brokerAddrs;
}
}

View File

@@ -23,14 +23,27 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.admin.ConsumeStats;
import org.apache.rocketmq.common.admin.RollbackStats;
import org.apache.rocketmq.common.message.MessageQueue;
@@ -43,7 +56,8 @@ import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.dashboard.aspect.admin.annotation.MultiMQAdminCmdMethod;
import org.apache.rocketmq.common.utils.ThreadUtils;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat;
import org.apache.rocketmq.dashboard.model.GroupConsumeInfo;
import org.apache.rocketmq.dashboard.model.QueueStatInfo;
@@ -55,17 +69,59 @@ import org.apache.rocketmq.dashboard.service.AbstractCommonService;
import org.apache.rocketmq.dashboard.service.ConsumerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import static com.google.common.base.Throwables.propagate;
@Service
public class ConsumerServiceImpl extends AbstractCommonService implements ConsumerService {
public class ConsumerServiceImpl extends AbstractCommonService implements ConsumerService, InitializingBean, DisposableBean {
private Logger logger = LoggerFactory.getLogger(ConsumerServiceImpl.class);
@Resource
private RMQConfigure configure;
private static final Set<String> SYSTEM_GROUP_SET = new HashSet<>();
private ExecutorService executorService;
@Override
@MultiMQAdminCmdMethod
public List<GroupConsumeInfo> queryGroupList() {
public void afterPropertiesSet() {
Runtime runtime = Runtime.getRuntime();
int corePoolSize = Math.max(10, runtime.availableProcessors() * 2);
int maximumPoolSize = Math.max(20, runtime.availableProcessors() * 2);
ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicLong threadIndex = new AtomicLong(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "QueryGroup_" + this.threadIndex.incrementAndGet());
}
};
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
this.executorService = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5000), threadFactory, handler);
}
@Override
public void destroy() {
ThreadUtils.shutdownGracefully(executorService, 10L, TimeUnit.SECONDS);
}
static {
SYSTEM_GROUP_SET.add(MixAll.TOOLS_CONSUMER_GROUP);
SYSTEM_GROUP_SET.add(MixAll.FILTERSRV_CONSUMER_GROUP);
SYSTEM_GROUP_SET.add(MixAll.SELF_TEST_CONSUMER_GROUP);
SYSTEM_GROUP_SET.add(MixAll.ONS_HTTP_PROXY_GROUP);
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_PULL_GROUP);
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_PERMISSION_GROUP);
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_OWNER_GROUP);
SYSTEM_GROUP_SET.add(MixAll.CID_SYS_RMQ_TRANS);
}
@Override
public List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup) {
Set<String> consumerGroupSet = Sets.newHashSet();
try {
ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
@@ -77,16 +133,39 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
catch (Exception err) {
throw Throwables.propagate(err);
}
List<GroupConsumeInfo> groupConsumeInfoList = Lists.newArrayList();
List<GroupConsumeInfo> groupConsumeInfoList = Collections.synchronizedList(Lists.newArrayList());
CountDownLatch countDownLatch = new CountDownLatch(consumerGroupSet.size());
for (String consumerGroup : consumerGroupSet) {
groupConsumeInfoList.add(queryGroup(consumerGroup));
executorService.submit(() -> {
try {
GroupConsumeInfo consumeInfo = queryGroup(consumerGroup);
groupConsumeInfoList.add(consumeInfo);
} catch (Exception e) {
logger.error("queryGroup exception, consumerGroup: {}", consumerGroup, e);
} finally {
countDownLatch.countDown();
}
});
}
try {
countDownLatch.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.error("query consumerGroup countDownLatch await Exception", e);
}
if (!skipSysGroup) {
groupConsumeInfoList.stream().map(group -> {
if (SYSTEM_GROUP_SET.contains(group.getGroup())) {
group.setGroup(String.format("%s%s", "%SYS%", group.getGroup()));
}
return group;
}).collect(Collectors.toList());
}
Collections.sort(groupConsumeInfoList);
return groupConsumeInfoList;
}
@Override
@MultiMQAdminCmdMethod
public GroupConsumeInfo queryGroup(String consumerGroup) {
GroupConsumeInfo groupConsumeInfo = new GroupConsumeInfo();
try {
@@ -133,7 +212,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
@MultiMQAdminCmdMethod
public List<TopicConsumerInfo> queryConsumeStatsList(final String topic, String groupName) {
ConsumeStats consumeStats = null;
try {
@@ -184,7 +262,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
@MultiMQAdminCmdMethod
public Map<String /*groupName*/, TopicConsumerInfo> queryConsumeStatsListByTopicName(String topic) {
Map<String, TopicConsumerInfo> group2ConsumerInfoMap = Maps.newHashMap();
try {
@@ -206,7 +283,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
@MultiMQAdminCmdMethod
public Map<String, ConsumerGroupRollBackStat> resetOffset(ResetOffsetRequest resetOffsetRequest) {
Map<String, ConsumerGroupRollBackStat> groupRollbackStats = Maps.newHashMap();
for (String consumerGroup : resetOffsetRequest.getConsumerGroupList()) {
@@ -251,7 +327,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
@MultiMQAdminCmdMethod
public List<ConsumerConfigInfo> examineSubscriptionGroupConfig(String group) {
List<ConsumerConfigInfo> consumerConfigInfoList = Lists.newArrayList();
try {
@@ -272,13 +347,22 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
@MultiMQAdminCmdMethod
public boolean deleteSubGroup(DeleteSubGroupRequest deleteSubGroupRequest) {
Set<String> brokerSet = this.fetchBrokerNameSetBySubscriptionGroup(deleteSubGroupRequest.getGroupName());
List<String> brokerList = deleteSubGroupRequest.getBrokerNameList();
boolean deleteInNsFlag = false;
// If the list of brokers passed in by the request contains the list of brokers that the consumer is in, delete RETRY and DLQ topic in namesrv
if (brokerList.containsAll(brokerSet)) {
deleteInNsFlag = true;
}
try {
ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
for (String brokerName : deleteSubGroupRequest.getBrokerNameList()) {
logger.info("addr={} groupName={}", clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), deleteSubGroupRequest.getGroupName());
mqAdminExt.deleteSubscriptionGroup(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), deleteSubGroupRequest.getGroupName());
mqAdminExt.deleteSubscriptionGroup(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), deleteSubGroupRequest.getGroupName(), true);
// Delete %RETRY%+Group and %DLQ%+Group in broker and namesrv
deleteResources(MixAll.RETRY_GROUP_TOPIC_PREFIX + deleteSubGroupRequest.getGroupName(), brokerName, clusterInfo, deleteInNsFlag);
deleteResources(MixAll.DLQ_GROUP_TOPIC_PREFIX + deleteSubGroupRequest.getGroupName(), brokerName, clusterInfo, deleteInNsFlag);
}
}
catch (Exception e) {
@@ -287,6 +371,18 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
return true;
}
private void deleteResources(String topic, String brokerName, ClusterInfo clusterInfo, boolean deleteInNsFlag) throws Exception {
mqAdminExt.deleteTopicInBroker(Sets.newHashSet(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr()), topic);
Set<String> nameServerSet = null;
if (StringUtils.isNotBlank(configure.getNamesrvAddr())) {
String[] ns = configure.getNamesrvAddr().split(";");
nameServerSet = new HashSet<>(Arrays.asList(ns));
}
if (deleteInNsFlag) {
mqAdminExt.deleteTopicInNameServer(nameServerSet, topic);
}
}
@Override
public boolean createAndUpdateSubscriptionGroupConfig(ConsumerConfigInfo consumerConfigInfo) {
try {
@@ -303,7 +399,6 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
@MultiMQAdminCmdMethod
public Set<String> fetchBrokerNameSetBySubscriptionGroup(String group) {
Set<String> brokerNameSet = Sets.newHashSet();
try {

View File

@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Throwables;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.dashboard.model.DlqMessageResendResult;
import org.apache.rocketmq.dashboard.model.DlqMessageRequest;
import org.apache.rocketmq.dashboard.model.MessagePage;
import org.apache.rocketmq.dashboard.model.MessageView;
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
import org.apache.rocketmq.dashboard.service.DlqMessageService;
import org.apache.rocketmq.dashboard.service.MessageService;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class DlqMessageServiceImpl implements DlqMessageService {
@Resource
private MQAdminExt mqAdminExt;
@Resource
private MessageService messageService;
@Override
public MessagePage queryDlqMessageByPage(MessageQuery query) {
List<MessageView> messageViews = new ArrayList<>();
PageRequest page = PageRequest.of(query.getPageNum(), query.getPageSize());
String topic = query.getTopic();
try {
mqAdminExt.examineTopicRouteInfo(topic);
} 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) {
return new MessagePage(new PageImpl<>(messageViews, page, 0), query.getTaskId());
} else {
throw Throwables.propagate(e);
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
return messageService.queryMessageByPage(query);
}
@Override
public List<DlqMessageResendResult> batchResendDlqMessage(List<DlqMessageRequest> dlqMessages) {
List<DlqMessageResendResult> batchResendResults = new LinkedList<>();
for (DlqMessageRequest dlqMessage : dlqMessages) {
ConsumeMessageDirectlyResult result = messageService.consumeMessageDirectly(dlqMessage.getTopicName(),
dlqMessage.getMsgId(), dlqMessage.getConsumerGroup(),
dlqMessage.getClientId());
DlqMessageResendResult resendResult = new DlqMessageResendResult(result, dlqMessage.getMsgId());
batchResendResults.add(resendResult);
}
return batchResendResults;
}
}

View File

@@ -31,6 +31,7 @@ import org.apache.rocketmq.acl.common.SessionCredentials;
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;
@@ -111,6 +112,9 @@ public class MessageServiceImpl implements MessageService {
}
});
} catch (Exception err) {
if (err instanceof MQClientException) {
throw new ServiceException(-1, ((MQClientException) err).getErrorMessage());
}
throw Throwables.propagate(err);
}
}
@@ -325,7 +329,7 @@ public class MessageServiceImpl implements MessageService {
List<MessageExt> msgFoundList = pullResult.getMsgFoundList();
for (int i = msgFoundList.size() - 1; i >= 0; i--) {
MessageExt messageExt = msgFoundList.get(i);
if (messageExt.getStoreTimestamp() < query.getBegin()) {
if (messageExt.getStoreTimestamp() > query.getEnd()) {
end--;
} else {
hasIllegalOffset = false;

View File

@@ -20,8 +20,6 @@ package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.base.Throwables;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -33,7 +31,9 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.trace.TraceType;
import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.MessageTraceView;
import org.apache.rocketmq.dashboard.model.trace.ProducerNode;
import org.apache.rocketmq.dashboard.model.trace.MessageTraceGraph;
@@ -65,7 +65,7 @@ public class MessageTraceServiceImpl implements MessageTraceService {
@Override
public List<MessageTraceView> queryMessageTraceKey(String key) {
String queryTopic = configure.getMsgTrackTopicNameOrDefault();
String queryTopic = TopicValidator.RMQ_SYS_TRACE_TOPIC;
logger.info("query data topic name is:{}", queryTopic);
return queryMessageTraceByTopicAndKey(queryTopic, key);
}
@@ -81,13 +81,16 @@ public class MessageTraceServiceImpl implements MessageTraceService {
}
return messageTraceViews;
} catch (Exception err) {
throw Throwables.propagate(err);
throw new ServiceException(-1, String.format("Failed to query message trace by msgId %s", key));
}
}
@Override
public MessageTraceGraph queryMessageTraceGraph(String key) {
List<MessageTraceView> messageTraceViews = queryMessageTraceKey(key);
public MessageTraceGraph queryMessageTraceGraph(String key, String topic) {
if (StringUtils.isEmpty(topic)) {
topic = TopicValidator.RMQ_SYS_TRACE_TOPIC;
}
List<MessageTraceView> messageTraceViews = queryMessageTraceByTopicAndKey(topic, key);
return buildMessageTraceGraph(messageTraceViews);
}

View File

@@ -16,16 +16,18 @@
*/
package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.service.AbstractCommonService;
import org.apache.rocketmq.dashboard.service.OpsService;
import org.apache.rocketmq.dashboard.service.checker.CheckerType;
import org.apache.rocketmq.dashboard.service.checker.RocketMqChecker;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@@ -34,13 +36,17 @@ public class OpsServiceImpl extends AbstractCommonService implements OpsService
@Resource
private RMQConfigure configure;
@Autowired
private GenericObjectPool<MQAdminExt> mqAdminExtPool;
@Resource
private List<RocketMqChecker> rocketMqCheckerList;
@Override
public Map<String, Object> homePageInfo() {
Map<String, Object> homePageInfoMap = Maps.newHashMap();
homePageInfoMap.put("namesvrAddrList", Splitter.on(";").splitToList(configure.getNamesrvAddr()));
homePageInfoMap.put("currentNamesrv", configure.getNamesrvAddr());
homePageInfoMap.put("namesvrAddrList", configure.getNamesrvAddrs());
homePageInfoMap.put("useVIPChannel", Boolean.valueOf(configure.getIsVIPChannel()));
homePageInfoMap.put("useTLS", configure.isUseTLS());
return homePageInfoMap;
@@ -49,6 +55,8 @@ public class OpsServiceImpl extends AbstractCommonService implements OpsService
@Override
public void updateNameSvrAddrList(String nameSvrAddrList) {
configure.setNamesrvAddr(nameSvrAddrList);
// when update namesrvAddr, clean the mqAdminExt objects pool.
mqAdminExtPool.clear();
}
@Override
@@ -65,14 +73,26 @@ public class OpsServiceImpl extends AbstractCommonService implements OpsService
return checkResultMap;
}
@Override public boolean updateIsVIPChannel(String useVIPChannel) {
@Override
public boolean updateIsVIPChannel(String useVIPChannel) {
configure.setIsVIPChannel(useVIPChannel);
mqAdminExtPool.clear();
return true;
}
@Override
public boolean updateUseTLS(boolean useTLS) {
configure.setUseTLS(useTLS);
mqAdminExtPool.clear();
return true;
}
@Override
public void addNameSvrAddr(String namesrvAddr) {
List<String> namesrvAddrs = configure.getNamesrvAddrs();
if (namesrvAddrs != null && !namesrvAddrs.contains(namesrvAddr)) {
namesrvAddrs.add(namesrvAddr);
}
configure.setNamesrvAddrs(namesrvAddrs);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.service.impl;
import com.alibaba.fastjson.JSONObject;
import java.io.FileReader;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Resource;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.UserInfo;
import org.apache.rocketmq.dashboard.service.PermissionService;
import org.apache.rocketmq.dashboard.util.MatcherUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.yaml.snakeyaml.Yaml;
import static org.apache.rocketmq.dashboard.permisssion.UserRoleEnum.ADMIN;
import static org.apache.rocketmq.dashboard.permisssion.UserRoleEnum.ORDINARY;
@Service
public class PermissionServiceImpl implements PermissionService, InitializingBean {
@Resource
private RMQConfigure configure;
private PermissionFileStore permissionFileStore;
@Override
public void afterPropertiesSet() {
if (configure.isLoginRequired()) {
permissionFileStore = new PermissionFileStore(configure);
}
}
@Override
public boolean checkUrlAvailable(UserInfo userInfo, String url) {
int type = userInfo.getUser().getType();
// if it is admin, it could access all resources
if (type == ADMIN.getRoleType()) {
return true;
}
String loginUserRole = ORDINARY.getRoleName();
Map<String, List<String>> rolePerms = PermissionFileStore.rolePerms;
List<String> perms = rolePerms.get(loginUserRole);
for (String perm : perms) {
if (MatcherUtil.match(perm, url)) {
return true;
}
}
return false;
}
public static class PermissionFileStore extends AbstractFileStore {
private static final String FILE_NAME = "role-permission.yml";
private static Map<String/**role**/, List<String>/**accessUrls**/> rolePerms = new ConcurrentHashMap<>();
public PermissionFileStore(RMQConfigure configure) {
super(configure, FILE_NAME);
}
@Override
public void load(InputStream inputStream) {
Yaml yaml = new Yaml();
JSONObject rolePermsData = null;
try {
if (inputStream == null) {
rolePermsData = yaml.loadAs(new FileReader(filePath), JSONObject.class);
} else {
rolePermsData = yaml.loadAs(inputStream, JSONObject.class);
}
} catch (Exception e) {
log.error("load user-permission.yml failed", e);
throw new ServiceException(0, String.format("Failed to load rolePermission property file: %s", filePath));
}
rolePerms.clear();
rolePerms.putAll(rolePermsData.getObject("rolePerms", Map.class));
}
}
}

View File

@@ -20,6 +20,7 @@ package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
@@ -36,6 +37,7 @@ import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.model.request.SendTopicMessageRequest;
import org.apache.rocketmq.dashboard.model.request.TopicConfigInfo;
@@ -61,23 +63,23 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
private RMQConfigure configure;
@Override
public TopicList fetchAllTopicList(boolean skipSysProcess) {
public TopicList fetchAllTopicList(boolean skipSysProcess, boolean skipRetryAndDlq) {
try {
TopicList allTopics = mqAdminExt.fetchAllTopicList();
if (skipSysProcess) {
return allTopics;
}
TopicList sysTopics = getSystemTopicList();
Set<String> topics = new HashSet<>();
for (String topic : allTopics.getTopicList()) {
if (sysTopics.getTopicList().contains(topic)) {
topics.add(String.format("%s%s", "%SYS%", topic));
} else {
topics.add(topic);
Set<String> topics =
allTopics.getTopicList().stream().map(topic -> {
if (!skipSysProcess && sysTopics.getTopicList().contains(topic)) {
topic = String.format("%s%s", "%SYS%", topic);
}
return topic;
}).filter(topic -> {
if (skipRetryAndDlq) {
return !(topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)
|| topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX));
}
return true;
}).collect(Collectors.toSet());
allTopics.getTopicList().clear();
allTopics.getTopicList().addAll(topics);
return allTopics;
@@ -133,10 +135,10 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
ClusterInfo clusterInfo = null;
try {
clusterInfo = mqAdminExt.examineBrokerClusterInfo();
return mqAdminExt.examineTopicConfig(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), topic);
} catch (Exception e) {
throw Throwables.propagate(e);
}
return mqAdminExt.examineTopicConfig(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), topic);
}
@Override
@@ -209,7 +211,7 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
}
public DefaultMQProducer buildDefaultMQProducer(String producerGroup, RPCHook rpcHook, boolean traceEnabled) {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer(producerGroup, rpcHook, traceEnabled, configure.getMsgTrackTopicNameOrDefault());
DefaultMQProducer defaultMQProducer = new DefaultMQProducer(producerGroup, rpcHook, traceEnabled, TopicValidator.RMQ_SYS_TRACE_TOPIC);
defaultMQProducer.setUseTLS(configure.isUseTLS());
return defaultMQProducer;
}

View File

@@ -21,15 +21,11 @@ import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.User;
import org.apache.rocketmq.dashboard.service.UserService;
import org.apache.rocketmq.srvutil.FileWatchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.util.HashMap;
@@ -40,9 +36,9 @@ import java.util.concurrent.ConcurrentHashMap;
@Service
public class UserServiceImpl implements UserService, InitializingBean {
@Resource
RMQConfigure configure;
private RMQConfigure configure;
FileBasedUserInfoStore fileBasedUserInfoStore;
private FileBasedUserInfoStore fileBasedUserInfoStore;
@Override
public User queryByName(String name) {
@@ -61,40 +57,17 @@ public class UserServiceImpl implements UserService, InitializingBean {
}
}
public static class FileBasedUserInfoStore {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public static class FileBasedUserInfoStore extends AbstractFileStore {
private static final String FILE_NAME = "users.properties";
private String filePath;
private final Map<String, User> userMap = new ConcurrentHashMap<>();
private static Map<String, User> userMap = new ConcurrentHashMap<>();
public FileBasedUserInfoStore(RMQConfigure configure) {
filePath = configure.getRocketMqDashboardDataPath() + File.separator + FILE_NAME;
if (!new File(filePath).exists()) {
//Use the default path
InputStream inputStream = getClass().getResourceAsStream("/" + FILE_NAME);
if (inputStream == null) {
log.error(String.format("Can not found the file %s in Spring Boot jar", FILE_NAME));
System.out.printf(String.format("Can not found file %s in Spring Boot jar or %s, stop the dashboard starting",
FILE_NAME, configure.getRocketMqDashboardDataPath()));
System.exit(1);
} else {
load(inputStream);
}
} else {
log.info(String.format("Login Users configure file is %s", filePath));
load();
watch();
}
super(configure, FILE_NAME);
}
private void load() {
load(null);
}
private void load(InputStream inputStream) {
@Override
public void load(InputStream inputStream) {
Properties prop = new Properties();
try {
if (inputStream == null) {
@@ -112,7 +85,8 @@ public class UserServiceImpl implements UserService, InitializingBean {
int role;
for (String key : prop.stringPropertyNames()) {
String v = prop.getProperty(key);
if (v == null) continue;
if (v == null)
continue;
arrs = v.split(",", 2);
if (arrs.length == 0) {
continue;
@@ -125,30 +99,10 @@ public class UserServiceImpl implements UserService, InitializingBean {
loadUserMap.put(key, new User(key, arrs[0].trim(), role));
}
userMap.clear();
userMap.putAll(loadUserMap);
}
private boolean watch() {
try {
FileWatchService fileWatchService = new FileWatchService(new String[]{filePath}, new FileWatchService.Listener() {
@Override
public void onChanged(String path) {
log.info("The loginUserInfo property file changed, reload the context");
load();
}
});
fileWatchService.start();
log.info("Succeed to start LoginUserWatcherService");
return true;
} catch (Exception e) {
log.error("Failed to start LoginUserWatcherService", e);
}
return false;
}
public User queryByName(String name) {
return userMap.get(name);
}
@@ -158,7 +112,6 @@ public class UserServiceImpl implements UserService, InitializingBean {
if (user != null && password.equals(user.getPassword())) {
return user.cloneOne();
}
return null;
}
}

View File

@@ -34,11 +34,12 @@ public class GlobalExceptionHandler {
public JsonResult<Object> jsonErrorHandler(HttpServletRequest req, Exception ex) throws Exception {
JsonResult<Object> value = null;
if (ex != null) {
logger.error("op=global_exception_handler_print_error", ex);
if (ex instanceof ServiceException) {
logger.error("Occur service exception: {}", ex.getMessage());
value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage());
}
else {
logger.error("op=global_exception_handler_print_error", ex);
value = new JsonResult<Object>(-1, ex.getMessage() == null ? ex.toString() : ex.getMessage());
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.task;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.protocol.body.BrokerStatsData;
import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.dashboard.service.DashboardCollectService;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.apache.rocketmq.tools.command.stats.StatsAllSubCommand;
@Slf4j
public class CollectTaskRunnble implements Runnable {
private String topic;
private MQAdminExt mqAdminExt;
private DashboardCollectService dashboardCollectService;
public CollectTaskRunnble(String topic, MQAdminExt mqAdminExt,
DashboardCollectService dashboardCollectService) {
this.topic = topic;
this.mqAdminExt = mqAdminExt;
this.dashboardCollectService = dashboardCollectService;
}
@Override
public void run() {
Date date = new Date();
try {
TopicRouteData topicRouteData = mqAdminExt.examineTopicRouteInfo(topic);
GroupList groupList = mqAdminExt.queryTopicConsumeByWho(topic);
double inTPS = 0;
long inMsgCntToday = 0;
double outTPS = 0;
long outMsgCntToday = 0;
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
String masterAddr = bd.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
try {
BrokerStatsData bsd = mqAdminExt.viewBrokerStatsData(masterAddr, BrokerStatsManager.TOPIC_PUT_NUMS, topic);
inTPS += bsd.getStatsMinute().getTps();
inMsgCntToday += StatsAllSubCommand.compute24HourSum(bsd);
} catch (Exception e) {
log.warn("Exception caught: mqAdminExt get broker stats data TOPIC_PUT_NUMS failed, topic [{}]", topic, e.getMessage());
}
}
}
if (groupList != null && !groupList.getGroupList().isEmpty()) {
for (String group : groupList.getGroupList()) {
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
String masterAddr = bd.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
try {
String statsKey = String.format("%s@%s", topic, group);
BrokerStatsData bsd = mqAdminExt.viewBrokerStatsData(masterAddr, BrokerStatsManager.GROUP_GET_NUMS, statsKey);
outTPS += bsd.getStatsMinute().getTps();
outMsgCntToday += StatsAllSubCommand.compute24HourSum(bsd);
} catch (Exception e) {
log.warn("Exception caught: mqAdminExt get broker stats data GROUP_GET_NUMS failed, topic [{}], group [{}]", topic, group, e.getMessage());
}
}
}
}
}
List<String> list;
try {
list = dashboardCollectService.getTopicMap().get(topic);
} catch (ExecutionException e) {
throw Throwables.propagate(e);
}
if (null == list) {
list = Lists.newArrayList();
}
list.add(date.getTime() + "," + new BigDecimal(inTPS).setScale(5, BigDecimal.ROUND_HALF_UP) + "," + inMsgCntToday + "," + new BigDecimal(outTPS).setScale(5, BigDecimal.ROUND_HALF_UP) + "," + outMsgCntToday);
dashboardCollectService.getTopicMap().put(topic, list);
} catch (Exception e) {
log.error("Failed to collect topic: {} data", topic, e);
}
}
}

View File

@@ -16,18 +16,6 @@
*/
package org.apache.rocketmq.dashboard.task;
import com.google.common.base.Stopwatch;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.dashboard.aspect.admin.annotation.MultiMQAdminCmdMethod;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.apache.rocketmq.tools.command.stats.StatsAllSubCommand;
import com.google.common.base.Throwables;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
@@ -43,13 +31,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import javax.annotation.Resource;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.protocol.body.BrokerStatsData;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.service.DashboardCollectService;
import org.apache.rocketmq.dashboard.util.JsonUtil;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
@@ -68,15 +61,14 @@ public class DashboardCollectTask {
private final static Logger log = LoggerFactory.getLogger(DashboardCollectTask.class);
@Resource
private ExecutorService collectExecutor;
@Scheduled(cron = "30 0/1 * * * ?")
@MultiMQAdminCmdMethod(timeoutMillis = 5000)
public void collectTopic() {
if (!rmqConfigure.isEnableDashBoardCollect()) {
return;
}
Date date = new Date();
Stopwatch stopwatch = Stopwatch.createUnstarted();
try {
TopicList topicList = mqAdminExt.fetchAllTopicList();
Set<String> topicSet = topicList.getTopicList();
@@ -87,77 +79,9 @@ public class DashboardCollectTask {
|| TopicValidator.isSystemTopic(topic)) {
continue;
}
TopicRouteData topicRouteData = mqAdminExt.examineTopicRouteInfo(topic);
GroupList groupList = mqAdminExt.queryTopicConsumeByWho(topic);
double inTPS = 0;
long inMsgCntToday = 0;
double outTPS = 0;
long outMsgCntToday = 0;
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
String masterAddr = bd.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
try {
stopwatch.start();
log.info("start time: {}", stopwatch.toString());
BrokerStatsData bsd = mqAdminExt.viewBrokerStatsData(masterAddr, BrokerStatsManager.TOPIC_PUT_NUMS, topic);
stopwatch.stop();
log.info("stop time : {}", stopwatch.toString());
inTPS += bsd.getStatsMinute().getTps();
inMsgCntToday += StatsAllSubCommand.compute24HourSum(bsd);
CollectTaskRunnble collectTask = new CollectTaskRunnble(topic, mqAdminExt, dashboardCollectService);
collectExecutor.submit(collectTask);
}
catch (Exception e) {
stopwatch.reset();
log.warn("Exception caught: mqAdminExt get broker stats data TOPIC_PUT_NUMS failed");
log.warn("Response [{}] ", e.getMessage());
}
}
}
if (groupList != null && !groupList.getGroupList().isEmpty()) {
for (String group : groupList.getGroupList()) {
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
String masterAddr = bd.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
try {
String statsKey = String.format("%s@%s", topic, group);
BrokerStatsData bsd = mqAdminExt.viewBrokerStatsData(masterAddr, BrokerStatsManager.GROUP_GET_NUMS, statsKey);
outTPS += bsd.getStatsMinute().getTps();
outMsgCntToday += StatsAllSubCommand.compute24HourSum(bsd);
}
catch (Exception e) {
log.warn("Exception caught: mqAdminExt get broker stats data GROUP_GET_NUMS failed");
log.warn("Response [{}] ", e.getMessage());
}
}
}
}
}
List<String> list;
try {
list = dashboardCollectService.getTopicMap().get(topic);
}
catch (ExecutionException e) {
throw Throwables.propagate(e);
}
if (null == list) {
list = Lists.newArrayList();
}
list.add(date.getTime() + "," + new BigDecimal(inTPS).setScale(5, BigDecimal.ROUND_HALF_UP) + "," + inMsgCntToday + "," + new BigDecimal(outTPS).setScale(5, BigDecimal.ROUND_HALF_UP) + "," + outMsgCntToday);
dashboardCollectService.getTopicMap().put(topic, list);
}
log.debug("Topic Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getTopicMap().asMap()));
}
catch (Exception err) {
throw Throwables.propagate(err);
@@ -330,6 +254,7 @@ public class DashboardCollectTask {
for (Map.Entry<String, Set<String>> entry : clusterTable.entrySet()) {
String clusterName = entry.getKey();
TopicValidator.addSystemTopic(clusterName);
TopicValidator.addSystemTopic(MixAll.getReplyTopic(clusterName));
Set<String> brokerNames = entry.getValue();
for (String brokerName : brokerNames) {
TopicValidator.addSystemTopic(brokerName);

View File

@@ -0,0 +1,56 @@
/*
* 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.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
public class ExcelUtil {
public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName,
String sheetName, Class clazz) throws Exception {
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
WriteFont writeFont = new WriteFont();
writeFont.setFontHeightInPoints((short)12);
writeFont.setFontName("Microsoft YaHei UI");
headWriteCellStyle.setWriteFont(writeFont);
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setWriteFont(writeFont);
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
EasyExcel.write(getOutputStream(fileName, response), clazz)
.excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data);
}
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
return response.getOutputStream();
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.util;
import java.util.regex.Pattern;
public class MatcherUtil {
public static boolean match(String accessUrl, String reqPath) {
String regPath = getRegPath(accessUrl);
return Pattern.compile(regPath).matcher(reqPath).matches();
}
private static String getRegPath(String path) {
char[] chars = path.toCharArray();
int len = chars.length;
StringBuilder sb = new StringBuilder();
boolean preX = false;
for (int i = 0; i < len; i++) {
if (chars[i] == '*') {
if (preX) {
sb.append(".*");
preX = false;
} else if (i + 1 == len) {
sb.append("[^/]*");
} else {
preX = true;
continue;
}
} else {
if (preX) {
sb.append("[^/]*");
preX = false;
}
if (chars[i] == '?') {
sb.append('.');
} else {
sb.append(chars[i]);
}
}
}
return sb.toString();
}
}

View File

@@ -1,35 +0,0 @@
server.address=0.0.0.0
server.port=8080
### SSL setting
#server.ssl.key-store=classpath:rmqcngkeystore.jks
#server.ssl.key-store-password=rocketmq
#server.ssl.keyStoreType=PKCS12
#server.ssl.keyAlias=rmqcngkey
#spring.application.index=true
spring.application.name=rocketmq-dashboard
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.level.root=INFO
logging.config=classpath:logback.xml
#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=
#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=
#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data
#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true
#set the message track trace topic if you don't want use the default one
rocketmq.config.msgTrackTopicName=
rocketmq.config.ticketKey=ticket
#Must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
rocketmq.config.loginRequired=false
#set the accessKey and secretKey if you used acl
#rocketmq.config.accessKey=
#rocketmq.config.secretKey=
rocketmq.config.useTLS=false

View File

@@ -0,0 +1,69 @@
#
# 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.
#
server:
port: 8080
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
## SSL setting
# ssl:
# key-store: classpath:rmqcngkeystore.jks
# key-store-password: rocketmq
# key-store-type: PKCS12
# key-alias: rmqcngkey
spring:
application:
name: rocketmq-dashboard
logging:
config: classpath:logback.xml
rocketmq:
config:
# if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, default localhost:9876
# configure multiple namesrv addresses to manage multiple different clusters
namesrvAddrs:
- 127.0.0.1:9876
- 127.0.0.2:9876
# if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
isVIPChannel:
# timeout for mqadminExt, default 5000ms
timeoutMillis:
# rocketmq-console's data path:dashboard/monitor
dataPath: /tmp/rocketmq-console/data
# set it false if you don't want use dashboard.default true
enableDashBoardCollect: true
# set the message track trace topic if you don't want use the default one
msgTrackTopicName:
ticketKey: ticket
# must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
loginRequired: false
useTLS: false
# set the accessKey and secretKey if you used acl
accessKey: # if version > 4.4.0
secretKey: # if version > 4.4.0
threadpool:
config:
coreSize: 10
maxSize: 10
keepAliveTime: 3000
queueSize: 5000

View File

@@ -1,17 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %5p %m%n</pattern>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %p %t - %m%n</pattern>
</encoder>
</appender>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${user.home}/logs/consolelogs/rocketmq-console.log</file>
<file>${user.home}/logs/dashboardlogs/rocketmq-dashboard.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.home}/logs/consolelogs/rocketmq-console-%d{yyyy-MM-dd}.%i.log
<fileNamePattern>${user.home}/logs/dashboardlogs/rocketmq-dashboard-%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
@@ -20,7 +37,7 @@
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %5p %m%n</pattern>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>

View File

@@ -0,0 +1,40 @@
#
# 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.
#
# This file supports hot change, any change will be auto-reloaded without Console restarting.
# 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:
- /rocketmq/*.query
- /ops/*.query
- /dashboard/*.query
- /topic/*.query
- /topic/sendTopicMessage.do
- /producer/*.query
- /message/*.query
- /messageTrace/*.query
- /monitor/*.query
- /consumer/*.query
- /cluster/*.query
- /dlqMessage/*.query
- /dlqMessage/exportDlqMessage.do
- /dlqMessage/batchResendDlqMessage.do
- /acl/*.query

View File

@@ -41,8 +41,6 @@
<link rel="stylesheet" href="vendor/ng-dialog/ngDialog-theme-default.css">
<link rel="stylesheet" href="vendor/dropdown/jquery.dropdown.css">
<link rel="stylesheet" href="vendor/datatimepicker/bootstrap-datetimepicker.min.css">
<link rel="stylesheet" href="vendor/font-awesome-4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="vendor/font-awesome-4.7.0/fonts/fontawesome-webfont.svg">
<link rel="stylesheet" type="text/css" href="vendor/chosen/chosen.css"/>
<link rel="stylesheet" type="text/css" href="vendor/chosen/chosen-spinner.css"/>
<link rel="stylesheet" type="text/css" href="vendor/angular-material/angular-material.min.css"/>
@@ -109,10 +107,12 @@
<script type="text/javascript" src="src/consumer.js?timestamp=6"></script>
<script type="text/javascript" src="src/producer.js"></script>
<script type="text/javascript" src="src/message.js"></script>
<script type="text/javascript" src="src/dlqMessage.js"></script>
<script type="text/javascript" src="src/messageTrace.js"></script>
<script type="text/javascript" src="src/ops.js?timestamp=7"></script>
<script type="text/javascript" src="src/remoteApi/remoteApi.js"></script>
<script type="text/javascript" src="vendor/preLoading/main.js"></script>
<script type="text/javascript" src="src/login.js"></script>
<script type="text/javascript" src="src/acl.js"></script>
</body>
</html>

View File

@@ -0,0 +1,540 @@
/*
* 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.
*/
var module = app;
module.controller('aclController', ['$scope', 'ngDialog', '$http', 'Notification', '$window', function ($scope, ngDialog, $http, Notification, $window) {
$scope.paginationConf = {
currentPage: 1,
totalItems: 0,
itemsPerPage: 10,
pagesLength: 15,
perPageOptions: [10],
rememberPerPage: 'perPageItems',
onChange: function () {
$scope.showPlainAccessConfigs(this.currentPage, this.totalItems);
}
};
$scope.plainAccessConfigs = [];
$scope.allPlainAccessConfigs = [];
$scope.globalWhiteAddrs = [];
$scope.allGlobalWhiteAddrs = [];
$scope.userRole = $window.sessionStorage.getItem("userrole");
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
$scope.showSecretKeyType = {};
$scope.refreshPlainAccessConfigs = function () {
$http({
method: "GET",
url: "acl/config.query",
params: {}
}).success(function (resp) {
// globalWhiteAddrs
// plainAccessConfigs
if (resp.status == 0) {
$scope.allPlainAccessConfigs = resp.data.plainAccessConfigs;
$scope.allGlobalWhiteAddrs = resp.data.globalWhiteAddrs;
$scope.showSecretKeyType = {};
$scope.allPlainAccessConfigs.forEach(e => $scope.showSecretKeyType[e.accessKey] = {
type: 'password',
action: 'SHOW'
});
$scope.showPlainAccessConfigs(1, $scope.allPlainAccessConfigs.length);
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.refreshPlainAccessConfigs();
$scope.filterStr = "";
$scope.$watch('filterStr', function () {
$scope.paginationConf.currentPage = 1;
$scope.filterList(1);
});
$scope.filterList = function (currentPage) {
var lowExceptStr = $scope.filterStr.toLowerCase();
var canShowList = [];
$scope.allPlainAccessConfigs.forEach(function (element) {
if (element.accessKey.toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push(element);
}
});
$scope.paginationConf.totalItems = canShowList.length;
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage) > canShowList.length ? canShowList.length : from + perPage;
$scope.plainAccessConfigs = canShowList.slice(from, to);
};
$scope.showPlainAccessConfigs = function (currentPage, totalItem) {
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
$scope.plainAccessConfigs = $scope.allPlainAccessConfigs.slice(from, to);
$scope.paginationConf.totalItems = totalItem;
$scope.filterList($scope.paginationConf.currentPage)
};
// add acl account
$scope.openAddDialog = function () {
var request = {};
request.accessKey = '';
request.secretKey = '';
request.admin = false;
request.defaultTopicPerm = 'DENY';
request.defaultGroupPerm = 'SUB';
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'addAclAccountDialog',
controller: 'addAclAccountDialogController',
data: request
});
}
$scope.deleteAclConfig = function (accessKey) {
$http({
method: "POST",
url: "acl/delete.do",
data: {accessKey: accessKey}
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
$scope.refreshPlainAccessConfigs();
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.openUpdateDialog = function (request) {
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'updateAclAccountDialog',
controller: 'updateAclAccountDialogController',
data: request
});
}
// add acl topic permission
$scope.openAddTopicDialog = function (request) {
$.extend(request, {pub: true, sub: true, deny: false})
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'addAclTopicDialog',
controller: 'addAclTopicDialogController',
data: request
});
}
// update acl topic permission
$scope.openUpdateTopicDialog = function (request, topic) {
var perm = {pub: false, sub: false, deny: false};
var topicInfo = topic.split('=');
$.each(topicInfo[1].split('|'), function (i, e) {
switch (e) {
case 'PUB':
perm.pub = true;
break;
case 'SUB':
perm.sub = true;
break;
case 'DENY':
perm.deny = true;
break;
default:
break;
}
});
$.extend(request, perm, {topic: topicInfo[0]});
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'updateAclTopicDialog',
controller: 'updateAclTopicDialogController',
data: request
});
}
// add acl group permission
$scope.openAddGroupDialog = function (request) {
$.extend(request, {pub: true, sub: true, deny: false})
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'addAclGroupDialog',
controller: 'addAclGroupDialogController',
data: request
});
}
// update acl group permission
$scope.openUpdateGroupDialog = function (request, group) {
var perm = {pub: false, sub: false, deny: false};
var groupInfo = group.split('=');
$.each(groupInfo[1].split('|'), function (i, e) {
switch (e) {
case 'PUB':
perm.pub = true;
break;
case 'SUB':
perm.sub = true;
break;
case 'DENY':
perm.deny = true;
break;
default:
break;
}
});
$.extend(request, perm, {group: groupInfo[0]});
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'updateAclGroupDialog',
controller: 'updateAclGroupDialogController',
data: request
});
}
$scope.deletePermConfig = function (config, name, type) {
var request = {config: config};
switch (type) {
case 'topic':
request.topicPerm = name;
break;
case 'group':
request.groupPerm = name;
break;
default:
break;
}
$http({
method: "POST",
url: "acl/perm/delete.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
$scope.refreshPlainAccessConfigs();
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.synchronizeData = function (request) {
$http({
method: "POST",
url: "acl/sync.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
$scope.refreshPlainAccessConfigs();
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.openAddAddrDialog = function () {
ngDialog.open({
preCloseCallback: function (value) {
$scope.refreshPlainAccessConfigs();
},
template: 'addWhiteListDialog',
controller: 'addWhiteListDialogController'
});
}
$scope.deleteGlobalWhiteAddr = function (request) {
$http({
method: "DELETE",
url: "acl/white/list/delete.do?request=" + request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
$scope.refreshPlainAccessConfigs();
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.synchronizeWhiteList = function (request) {
$http({
method: "POST",
url: "acl/white/list/sync.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
$scope.refreshPlainAccessConfigs();
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.switchSecretKeyType = function (accessKey) {
if ($scope.showSecretKeyType[accessKey].type == 'password') {
$scope.showSecretKeyType[accessKey] = {type: 'text', action: 'HIDE'};
} else {
$scope.showSecretKeyType[accessKey] = {type: 'password', action: 'SHOW'};
}
}
}]);
module.controller('addAclAccountDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.addRequest = function (requestItem) {
$http({
method: "POST",
url: "acl/add.do",
data: requestItem
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('updateAclAccountDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.updateAclAccountRequest = function (requestItem) {
$http({
method: "POST",
url: "acl/update.do",
data: requestItem
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('addAclTopicDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.updateAclAccountRequest = function (requestItem) {
if ((requestItem.deny && requestItem.sub) || (requestItem.deny && requestItem.pub)) {
alert("Forbid deny && pub/sub.");
return false;
}
if (!requestItem.topic) {
alert("topic is null");
return false;
}
//var request = requestItem.originalData;
var originalData = $.extend(true, {}, requestItem.originalData);
if (!originalData.topicPerms) {
originalData.topicPerms = new Array();
}
var topicPerm = concatPerm(requestItem.topic, requestItem.pub ? 0x01 : 0, requestItem.sub ? 0x02 : 0, requestItem.deny ? 0x04 : 0);
originalData.topicPerms.push(topicPerm);
var request = {topicPerm: topicPerm, config: originalData};
$http({
method: "POST",
url: "acl/topic/add.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('updateAclTopicDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.updateAclAccountRequest = function (requestItem) {
if ((requestItem.deny && requestItem.sub) || (requestItem.deny && requestItem.pub)) {
alert("Forbid deny && pub/sub.");
return false;
}
var originalData = $.extend(true, {}, requestItem.originalData);
if (!originalData.topicPerms) {
originalData.topicPerms = new Array();
}
var topicPerm = concatPerm(requestItem.topic, requestItem.pub ? 0x01 : 0, requestItem.sub ? 0x02 : 0, requestItem.deny ? 0x04 : 0);
for (var i = 0; i < originalData.topicPerms.length; i++) {
if (originalData.topicPerms[i].split('=')[0] == requestItem.topic) {
originalData.topicPerms[i] = topicPerm;
}
}
var request = {topicPerm: topicPerm, config: originalData};
$http({
method: "POST",
url: "acl/topic/add.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('addAclGroupDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.updateAclAccountRequest = function (requestItem) {
if ((requestItem.deny && requestItem.sub) || (requestItem.deny && requestItem.pub)) {
alert("Forbid deny && pub/sub.");
return false;
}
//var request = requestItem.originalData;
var originalData = $.extend(true, {}, requestItem.originalData);
if (!originalData.groupPerms) {
originalData.groupPerms = new Array();
}
var groupPerm = concatPerm(requestItem.group, requestItem.pub ? 0x01 : 0, requestItem.sub ? 0x02 : 0, requestItem.deny ? 0x04 : 0);
originalData.groupPerms.push(groupPerm);
var request = {groupPerm: groupPerm, config: originalData};
$http({
method: "POST",
url: "acl/group/add.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('updateAclGroupDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.updateAclAccountRequest = function (requestItem) {
if ((requestItem.deny && requestItem.sub) || (requestItem.deny && requestItem.pub)) {
alert("Forbid deny && pub/sub.");
return false;
}
var originalData = $.extend(true, {}, requestItem.originalData);
if (!originalData.groupPerms) {
originalData.groupPerms = new Array();
}
var groupPerm = concatPerm(requestItem.group, requestItem.pub ? 0x01 : 0, requestItem.sub ? 0x02 : 0, requestItem.deny ? 0x04 : 0);
for (var i = 0; i < originalData.groupPerms.length; i++) {
if (originalData.groupPerms[i].split('=')[0] == requestItem.group) {
originalData.groupPerms[i] = groupPerm;
}
}
var request = {groupPerm: groupPerm, config: originalData};
$http({
method: "POST",
url: "acl/group/add.do",
data: request
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
/**
*
* pub: 0x01, sub: 0x02, deny: 0x04
*/
function concatPerm(name, pub, sub, deny) {
var perm = '';
switch (pub | sub | deny) {
case 0x01:
perm = 'PUB';
break;
case 0x02:
perm = 'SUB';
break;
case 0x03:
perm = 'PUB|SUB';
break;
case 0x04:
perm = 'DENY';
break;
default:
perm = 'DENY';
break;
}
return name + '=' + perm;
}
module.controller('addWhiteListDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.addWhiteListRequest = function (requestItem) {
$http({
method: "POST",
url: "acl/white/list/add.do",
data: requestItem.split(',')
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('aclBelongItemDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.postBelongItemRequest = function (topicRequestItem) {
topicRequestItem.type = 1
$http({
method: "POST",
url: "acl/belong/item/add.do",
data: topicRequestItem
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);

View File

@@ -49,6 +49,15 @@ var app = angular.module('app', [
}
console.log('initFlag0='+ initFlag + ' loginFlag0==='+loginFlag);
$http({
method: "GET",
url: "acl/enable.query"
}).success(function (resp) {
if (resp && resp.status == 0) {
$rootScope.show = resp.data;
}
});
$rootScope.$on('$locationChangeStart', function (event, next, current) {
// redirect to login page if not logged in and trying to access a restricted page
init(function(resp){
@@ -195,12 +204,18 @@ app.config(['$routeProvider', '$httpProvider','$cookiesProvider','getDictNamePro
}).when('/message', {
templateUrl: 'view/pages/message.html',
controller:'messageController'
}).when('/dlqMessage', {
templateUrl: 'view/pages/dlqMessage.html',
controller:'dlqMessageController'
}).when('/messageTrace', {
templateUrl: 'view/pages/messageTrace.html',
controller:'messageTraceController'
}).when('/ops', {
templateUrl: 'view/pages/ops.html',
controller:'opsController'
}).when('/acl', {
templateUrl: 'view/pages/acl.html',
controller: 'aclController'
}).when('/404', {
templateUrl: 'view/pages/404.html'
}).otherwise('/404');

View File

@@ -17,7 +17,7 @@
var module = app;
module.controller('consumerController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notification', '$window', function ($scope, ngDialog, $http, Notification, $window) {
$scope.paginationConf = {
currentPage: 1,
totalItems: 0,
@@ -26,34 +26,46 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http','Notifica
perPageOptions: [10],
rememberPerPage: 'perPageItems',
onChange: function () {
$scope.showConsumerGroupList(this.currentPage,this.totalItems);
$scope.showConsumerGroupList(this.currentPage, this.totalItems);
}
};
$scope.sortKey = null;
$scope.sortOrder=1;
$scope.sortOrder = 1;
$scope.intervalProcessSwitch = false;
$scope.intervalProcess = null;
$scope.allConsumerGrouopList = [];
$scope.consumerGroupShowList = [];
$scope.sortByKey = function (key) {
$scope.paginationConf.currentPage=1;
$scope.paginationConf.currentPage = 1;
$scope.sortOrder = -$scope.sortOrder;
$scope.sortKey = key;
$scope.doSort();
};
$scope.userRole = $window.sessionStorage.getItem("userrole");
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
$scope.filterNormal = true;
$scope.filterSystem = false;
$scope.doSort = function (){// todo how to change this fe's code ? (it's dirty)
if($scope.sortKey == 'diffTotal'){
$scope.allConsumerGrouopList.sort(function(a,b) {return (a.diffTotal > b.diffTotal) ? $scope.sortOrder : ((b.diffTotal > a.diffTotal) ? -$scope.sortOrder : 0);} );
$scope.doSort = function () {// todo how to change this fe's code ? (it's dirty)
if ($scope.sortKey == 'diffTotal') {
$scope.allConsumerGrouopList.sort(function (a, b) {
return (a.diffTotal > b.diffTotal) ? $scope.sortOrder : ((b.diffTotal > a.diffTotal) ? -$scope.sortOrder : 0);
});
}
if($scope.sortKey == 'group'){
$scope.allConsumerGrouopList.sort(function(a,b) {return (a.group > b.group) ? $scope.sortOrder : ((b.group > a.group) ? -$scope.sortOrder : 0);} );
if ($scope.sortKey == 'group') {
$scope.allConsumerGrouopList.sort(function (a, b) {
return (a.group > b.group) ? $scope.sortOrder : ((b.group > a.group) ? -$scope.sortOrder : 0);
});
}
if($scope.sortKey == 'count'){
$scope.allConsumerGrouopList.sort(function(a,b) {return (a.count > b.count) ? $scope.sortOrder : ((b.count > a.count) ? -$scope.sortOrder : 0);} );
if ($scope.sortKey == 'count') {
$scope.allConsumerGrouopList.sort(function (a, b) {
return (a.count > b.count) ? $scope.sortOrder : ((b.count > a.count) ? -$scope.sortOrder : 0);
});
}
if($scope.sortKey == 'consumeTps'){
$scope.allConsumerGrouopList.sort(function(a,b) {return (a.consumeTps > b.consumeTps) ? $scope.sortOrder : ((b.consumeTps > a.consumeTps) ? -$scope.sortOrder : 0);} );
if ($scope.sortKey == 'consumeTps') {
$scope.allConsumerGrouopList.sort(function (a, b) {
return (a.consumeTps > b.consumeTps) ? $scope.sortOrder : ((b.consumeTps > a.consumeTps) ? -$scope.sortOrder : 0);
});
}
$scope.filterList($scope.paginationConf.currentPage)
};
@@ -65,30 +77,30 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http','Notifica
method: "GET",
url: "consumer/groupList.query"
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
$scope.allConsumerGrouopList = resp.data;
console.log($scope.allConsumerGrouopList);
console.log(JSON.stringify(resp));
$scope.showConsumerGroupList($scope.paginationConf.currentPage,$scope.allConsumerGrouopList.length);
$scope.showConsumerGroupList($scope.paginationConf.currentPage, $scope.allConsumerGrouopList.length);
//Hide loader
$('#loaderConsumer').addClass("hide-myloader");
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.monitor = function(consumerGroupName){
$scope.monitor = function (consumerGroupName) {
$http({
method: "GET",
url: "monitor/consumerMonitorConfigByGroupName.query",
params:{consumeGroupName:consumerGroupName}
params: {consumeGroupName: consumerGroupName}
}).success(function (resp) {
// if(resp.status ==0){
ngDialog.open({
template: 'consumerMonitorDialog',
controller: 'consumerMonitorDialogController',
data:{consumerGroupName:consumerGroupName,data:resp.data}
data: {consumerGroupName: consumerGroupName, data: resp.data}
});
// }else {
// Notification.error({message: resp.errMsg, delay: 2000});
@@ -109,35 +121,59 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http','Notifica
$scope.refreshConsumerData();
$scope.filterStr="";
$scope.$watch('filterStr', function() {
$scope.paginationConf.currentPage=1;
$scope.filterStr = "";
$scope.$watch('filterStr', function () {
$scope.paginationConf.currentPage = 1;
$scope.filterList(1)
});
$scope.$watch('filterNormal', function () {
$scope.filterList(1);
});
$scope.$watch('filterSystem', function () {
$scope.filterList(1);
});
$scope.filterByType = function (str) {
if ($scope.filterSystem) {
if (str.startsWith("%S")) {
return true
}
}
if ($scope.filterNormal) {
if (str.startsWith("%") == false) {
return true
}
}
return false;
};
$scope.filterList = function (currentPage) {
var lowExceptStr = $scope.filterStr.toLowerCase();
var canShowList = [];
$scope.allConsumerGrouopList.forEach(function(element) {
$scope.allConsumerGrouopList.forEach(function (element) {
console.log(element)
if (element.group.toLowerCase().indexOf(lowExceptStr) != -1){
if ($scope.filterByType(element.group)) {
if (element.group.toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push(element);
}
}
});
$scope.paginationConf.totalItems =canShowList.length;
$scope.paginationConf.totalItems = canShowList.length;
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage)>canShowList.length?canShowList.length:from + perPage;
var to = (from + perPage) > canShowList.length ? canShowList.length : from + perPage;
$scope.consumerGroupShowList = canShowList.slice(from, to);
};
$scope.showConsumerGroupList = function (currentPage,totalItem) {
$scope.showConsumerGroupList = function (currentPage, totalItem) {
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage)>totalItem?totalItem:from + perPage;
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
$scope.consumerGroupShowList = $scope.allConsumerGrouopList.slice(from, to);
$scope.paginationConf.totalItems = totalItem ;
$scope.paginationConf.totalItems = totalItem;
console.log($scope.consumerGroupShowList)
console.log($scope.paginationConf.totalItems)
$scope.doSort()
@@ -145,9 +181,9 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http','Notifica
$scope.openAddDialog = function () {
$scope.openCreateOrUpdateDialog(null);
};
$scope.openCreateOrUpdateDialog = function(request){
$scope.openCreateOrUpdateDialog = function (request) {
var bIsUpdate = true;
if(request == null){
if (request == null) {
request = [{
brokerNameList: [],
subscriptionGroupConfig: {
@@ -168,123 +204,126 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http','Notifica
method: "GET",
url: "cluster/list.query"
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
console.log(resp);
ngDialog.open({
preCloseCallback: function(value) {
preCloseCallback: function (value) {
// Refresh topic list
$scope.refreshConsumerData();
},
template: 'consumerModifyDialog',
controller: 'consumerModifyDialogController',
data:{
consumerRequestList:request,
allClusterNameList:Object.keys(resp.data.clusterInfo.clusterAddrTable),
allBrokerNameList:Object.keys(resp.data.brokerServer),
bIsUpdate:bIsUpdate
data: {
consumerRequestList: request,
allClusterNameList: Object.keys(resp.data.clusterInfo.clusterAddrTable),
allBrokerNameList: Object.keys(resp.data.brokerServer),
bIsUpdate: bIsUpdate,
writeOperationEnabled: $scope.writeOperationEnabled
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.detail = function(consumerGroupName){
$scope.detail = function (consumerGroupName) {
$http({
method: "GET",
url: "consumer/queryTopicByConsumer.query",
params:{consumerGroup:consumerGroupName}
params: {consumerGroup: consumerGroupName}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
console.log(resp);
ngDialog.open({
template: 'consumerTopicViewDialog',
controller: 'consumerTopicViewDialogController',
data:{consumerGroupName:consumerGroupName,data:resp.data}
data: {consumerGroupName: consumerGroupName, data: resp.data}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.client = function(consumerGroupName){
$scope.client = function (consumerGroupName) {
$http({
method: "GET",
url: "consumer/consumerConnection.query",
params:{consumerGroup:consumerGroupName}
params: {consumerGroup: consumerGroupName}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
console.log(resp);
ngDialog.open({
template: 'clientInfoDialog',
// controller: 'addTopicDialogController',
data:{data:resp.data,consumerGroupName:consumerGroupName}
data: {data: resp.data, consumerGroupName: consumerGroupName}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.updateConfigDialog = function(consumerGroupName){
$scope.updateConfigDialog = function (consumerGroupName) {
$http({
method: "GET",
url: "consumer/examineSubscriptionGroupConfig.query",
params:{consumerGroup:consumerGroupName}
params: {consumerGroup: consumerGroupName}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
console.log(resp);
$scope.openCreateOrUpdateDialog(resp.data);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.delete = function(consumerGroupName){
$scope.delete = function (consumerGroupName) {
$http({
method: "GET",
url: "consumer/fetchBrokerNameList.query",
params:{
consumerGroup:consumerGroupName
params: {
consumerGroup: consumerGroupName
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
console.log(resp);
ngDialog.open({
preCloseCallback: function(value) {
preCloseCallback: function (value) {
// Refresh topic list
$scope.refreshConsumerData();
},
template: 'deleteConsumerDialog',
controller: 'deleteConsumerDialogController',
data:{
data: {
// allClusterList:Object.keys(resp.data.clusterInfo.clusterAddrTable),
allBrokerNameList:resp.data,
consumerGroupName:consumerGroupName
allBrokerNameList: resp.data,
consumerGroupName: consumerGroupName
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}])
module.controller('consumerMonitorDialogController', function ($scope, ngDialog, $http,Notification) {
module.controller('consumerMonitorDialogController', function ($scope, ngDialog, $http, Notification) {
$scope.createOrUpdateConsumerMonitor = function () {
$http({
method: "POST",
url: "monitor/createOrUpdateConsumerMonitor.do",
params:{consumeGroupName:$scope.ngDialogData.consumerGroupName,
minCount:$scope.ngDialogData.data.minCount,
maxDiffTotal:$scope.ngDialogData.data.maxDiffTotal}
params: {
consumeGroupName: $scope.ngDialogData.consumerGroupName,
minCount: $scope.ngDialogData.data.minCount,
maxDiffTotal: $scope.ngDialogData.data.maxDiffTotal
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
Notification.info({message: "update success!", delay: 2000});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -293,7 +332,7 @@ module.controller('consumerMonitorDialogController', function ($scope, ngDialog,
);
module.controller('deleteConsumerDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('deleteConsumerDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.selectedClusterList = [];
$scope.selectedBrokerNameList = [];
$scope.delete = function () {
@@ -303,13 +342,15 @@ module.controller('deleteConsumerDialogController', ['$scope', 'ngDialog', '$htt
$http({
method: "POST",
url: "consumer/deleteSubGroup.do",
data:{groupName:$scope.ngDialogData.consumerGroupName,
brokerNameList:$scope.selectedBrokerNameList}
data: {
groupName: $scope.ngDialogData.consumerGroupName,
brokerNameList: $scope.selectedBrokerNameList
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
Notification.info({message: "delete success!", delay: 2000});
ngDialog.close(this);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -317,19 +358,19 @@ module.controller('deleteConsumerDialogController', ['$scope', 'ngDialog', '$htt
}]
);
module.controller('consumerModifyDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('consumerModifyDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.postConsumerRequest = function (consumerRequest) {
var request = JSON.parse(JSON.stringify(consumerRequest));
console.log(request);
$http({
method: "POST",
url: "consumer/createOrUpdate.do",
data:request
data: request
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
Notification.info({message: "update success!", delay: 2000});
ngDialog.close(this);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -351,8 +392,10 @@ module.controller('consumerTopicViewDialogController', ['$scope', 'ngDialog', '$
if (resp.status == 0) {
ngDialog.open({
template: 'consumerClientDialog',
data:{consumerClientInfo:resp.data,
clientId:clientId}
data: {
consumerClientInfo: resp.data,
clientId: clientId
}
});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
@@ -361,6 +404,3 @@ module.controller('consumerTopicViewDialogController', ['$scope', 'ngDialog', '$
};
}]
);

View File

@@ -0,0 +1,297 @@
/*
* 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.
*/
var module = app;
const SYS_GROUP_TOPIC_PREFIX = "%SYS%";
module.controller('dlqMessageController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.allConsumerGroupList = [];
$scope.selectedConsumerGroup = [];
$scope.messageId = "";
$scope.queryDlqMessageByConsumerGroupResult = [];
$scope.queryDlqMessageByMessageIdResult = {};
$http({
method: "GET",
url: "consumer/groupList.query"
}).success(function (resp) {
if (resp.status == 0) {
for (const consumerGroup of resp.data) {
if (!consumerGroup.group.startsWith(SYS_GROUP_TOPIC_PREFIX)) {
$scope.allConsumerGroupList.push(consumerGroup.group);
}
}
$scope.allConsumerGroupList.sort();
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
$scope.timepickerBegin = moment().subtract(3, 'hour').format('YYYY-MM-DD HH:mm');
$scope.timepickerEnd = moment().format('YYYY-MM-DD HH:mm');
$scope.timepickerOptions = {format: 'YYYY-MM-DD HH:mm', showClear: true};
$scope.taskId = "";
$scope.paginationConf = {
currentPage: 1,
totalItems: 0,
itemsPerPage: 20,
pagesLength: 15,
perPageOptions: [10],
rememberPerPage: 'perPageItems',
onChange: function () {
$scope.queryDlqMessageByConsumerGroup()
}
};
$scope.queryDlqMessageByConsumerGroup = function () {
$("#noMsgTip").css("display", "none");
if ($scope.timepickerEnd < $scope.timepickerBegin) {
Notification.error({message: "endTime is later than beginTime!", delay: 2000});
return
}
if ($scope.selectedConsumerGroup === [] || (typeof $scope.selectedConsumerGroup) == "object") {
return
}
$http({
method: "POST",
url: "dlqMessage/queryDlqMessageByConsumerGroup.query",
data: {
topic: DLQ_GROUP_TOPIC_PREFIX + $scope.selectedConsumerGroup,
begin: $scope.timepickerBegin.valueOf(),
end: $scope.timepickerEnd.valueOf(),
pageNum: $scope.paginationConf.currentPage,
pageSize: $scope.paginationConf.itemsPerPage,
taskId: $scope.taskId
}
}).success(function (resp) {
if (resp.status === 0) {
$scope.messageShowList = resp.data.page.content;
if ($scope.messageShowList.length == 0){
$("#noMsgTip").removeAttr("style");
}
for (const message of $scope.messageShowList) {
message.checked = false;
}
console.log($scope.messageShowList);
if (resp.data.page.first) {
$scope.paginationConf.currentPage = 1;
}
$scope.paginationConf.currentPage = resp.data.page.number + 1;
$scope.paginationConf.totalItems = resp.data.page.totalElements;
$scope.taskId = resp.data.taskId
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
$scope.queryDlqMessageDetail = function (messageId, consumerGroup) {
$http({
method: "GET",
url: "messageTrace/viewMessage.query",
params: {
msgId: messageId,
topic: DLQ_GROUP_TOPIC_PREFIX + consumerGroup
}
}).success(function (resp) {
if (resp.status == 0) {
console.log(resp);
ngDialog.open({
template: 'dlqMessageDetailViewDialog',
data: resp.data
});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.queryDlqMessageByMessageId = function (messageId, consumerGroup) {
$http({
method: "GET",
url: "messageTrace/viewMessage.query",
params: {
msgId: messageId,
topic: DLQ_GROUP_TOPIC_PREFIX + consumerGroup
}
}).success(function (resp) {
if (resp.status == 0) {
$scope.queryDlqMessageByMessageIdResult = resp.data;
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.changeShowMessageList = function (currentPage, totalItem) {
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
$scope.messageShowList = $scope.queryMessageByTopicResult.slice(from, to);
$scope.paginationConf.totalItems = totalItem;
};
$scope.onChangeQueryCondition = function () {
console.log("change")
$scope.taskId = "";
$scope.paginationConf.currentPage = 1;
$scope.paginationConf.totalItems = 0;
}
$scope.resendDlqMessage = function (messageView, consumerGroup) {
const topic = messageView.properties.RETRY_TOPIC;
const msgId = messageView.properties.ORIGIN_MESSAGE_ID;
$http({
method: "POST",
url: "message/consumeMessageDirectly.do",
params: {
msgId: msgId,
consumerGroup: consumerGroup,
topic: topic
}
}).success(function (resp) {
if (resp.status == 0) {
ngDialog.open({
template: 'operationResultDialog',
data: {
result: resp.data
}
});
} else {
ngDialog.open({
template: 'operationResultDialog',
data: {
result: resp.errMsg
}
});
}
});
};
$scope.exportDlqMessage = function (msgId, consumerGroup) {
window.location.href = "dlqMessage/exportDlqMessage.do?msgId=" + msgId + "&consumerGroup=" + consumerGroup;
};
$scope.selectedDlqMessage = [];
$scope.batchResendDlqMessage = function (consumerGroup) {
if ($("#batchResendBtn").hasClass("disabled")) {
return;
}
for (const message of $scope.messageCheckedList) {
const dlqMessage = {};
dlqMessage.topic = message.properties.RETRY_TOPIC;
dlqMessage.msgId = message.properties.ORIGIN_MESSAGE_ID;
dlqMessage.consumerGroup = consumerGroup;
$scope.selectedDlqMessage.push(dlqMessage);
}
$http({
method: "POST",
url: "dlqMessage/batchResendDlqMessage.do",
data: $scope.selectedDlqMessage
}).success(function (resp) {
$scope.selectedDlqMessage = [];
if (resp.status == 0) {
ngDialog.open({
template: 'operationResultDialog',
data: {
result: resp.data
}
});
} else {
ngDialog.open({
template: 'operationResultDialog',
data: {
result: resp.errMsg
}
});
}
});
};
$scope.batchExportDlqMessage = function (consumerGroup) {
if ($("#batchExportBtn").hasClass("disabled")) {
return;
}
for (const message of $scope.messageCheckedList) {
const dlqMessage = {};
dlqMessage.msgId = message.msgId;
dlqMessage.consumerGroup = consumerGroup;
$scope.selectedDlqMessage.push(dlqMessage);
}
$http({
method: "POST",
url: "dlqMessage/batchExportDlqMessage.do",
data: $scope.selectedDlqMessage,
headers: {
'Content-type': 'application/json'
},
responseType: "arraybuffer"
}).success(function (resp) {
$scope.selectedDlqMessage = [];
const blob = new Blob([resp], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
const objectUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.download = 'dlqs.xlsx';
a.href = objectUrl;
a.click();
document.body.removeChild(a)
});
};
$scope.checkedAll = false;
$scope.messageCheckedList = [];
$scope.selectAll = function () {
$scope.messageCheckedList = [];
if ($scope.checkedAll == true) {
angular.forEach($scope.messageShowList, function (item, index) {
item.checked = true;
$scope.messageCheckedList.push(item);
});
} else {
angular.forEach($scope.messageShowList, function (item, index) {
item.checked = false;
});
}
checkBtn($scope.messageCheckedList)
console.log($scope.messageCheckedList)
}
$scope.selectItem = function () {
var flag = true;
$scope.messageCheckedList = [];
angular.forEach($scope.messageShowList, function (item, index) {
if (item.checked) {
$scope.messageCheckedList.push(item);
} else {
flag = false;
}
})
$scope.checkedAll = flag;
checkBtn($scope.messageCheckedList)
console.log($scope.messageCheckedList);
}
function checkBtn(messageCheckList) {
if (messageCheckList.length == 0) {
$("#batchResendBtn").addClass("disabled");
$("#batchExportBtn").addClass("disabled");
} else {
$("#batchResendBtn").removeClass("disabled");
$("#batchExportBtn").removeClass("disabled");
}
}
}]);

View File

@@ -1,11 +1,28 @@
/*
* 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.
*/
var en = {
"TITLE": "RocketMQ-Dashboard",
"CLOSE": "Close",
"NO": "NO.",
"ADDRESS": "Address",
"VERSION": "Version",
"PRO_MSG_TPS": "Produce Massage TPS",
"CUS_MSG_TPS": "Consumer Massage TPS",
"PRO_MSG_TPS": "Produce Message TPS",
"CUS_MSG_TPS": "Consumer Message TPS",
"YESTERDAY_PRO_COUNT": "Yesterday Produce Count",
"YESTERDAY_CUS_COUNT": "Yesterday Consume Count",
"TODAY_PRO_COUNT": "Today Produce Count",
@@ -20,7 +37,11 @@ var en = {
"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",
@@ -85,5 +106,22 @@ var en = {
"NO_DATA":"Don't have ",
"SYSTEM":"SYSTEM",
"WELCOME":"Hi, welcome using RocketMQ Dashboard",
"ENABLE_MESSAGE_TRACE":"Enable Message Trace"
"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"
}

View File

@@ -1,3 +1,20 @@
/*
* 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.
*/
var zh = {
"TITLE": "RocketMQ仪表板",
"CLOSE": "关闭",
@@ -21,6 +38,10 @@ var zh = {
"CONSUMER":"消费者",
"PRODUCER":"生产者",
"MESSAGE":"消息",
"MESSAGE_DETAIL":"消息详情",
"RESEND_MESSAGE":"重新发送",
"VIEW_EXCEPTION":"查看异常",
"DLQ_MESSAGE":"死信消息",
"MESSAGETRACE":"消息轨迹",
"OPERATION": "操作",
"ADD": "新增",
@@ -86,5 +107,22 @@ var zh = {
"NO_DATA":"不存在 ",
"SYSTEM":"系统",
"WELCOME":"您好欢迎使用RocketMQ仪表盘",
"ENABLE_MESSAGE_TRACE":"开启消息轨迹"
"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":"隐藏"
}

View File

@@ -15,13 +15,13 @@
* limitations under the License.
*/
app.controller('loginController', ['$scope','$location','$http','Notification','$cookies','$window', function ($scope,$location,$http,Notification,$cookies, $window) {
app.controller('loginController', ['$scope', '$location', '$http', 'Notification', '$cookies', '$window', function ($scope, $location, $http, Notification, $cookies, $window) {
$scope.login = function () {
if(!$("#username").val()) {
if (!$("#username").val()) {
alert("用户名不能为空");
return;
}
if(!$("#password").val()) {
if (!$("#password").val()) {
alert("密码不能为空");
return;
}
@@ -29,14 +29,15 @@ app.controller('loginController', ['$scope','$location','$http','Notification','
$http({
method: "POST",
url: "login/login.do",
params:{username:$("#username").val(), password:$("#password").val()}
params: {username: $("#username").val(), password: $("#password").val()}
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: 'Login successful, redirect now', delay: 2000});
$window.sessionStorage.setItem("username", $("#username").val());
window.location = resp.data;
$window.sessionStorage.setItem("username", resp.data.loginUserName);
$window.sessionStorage.setItem("userrole", resp.data.loginUserRole);
window.location = resp.data.contextPath;
initFlag = false;
} else{
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});

View File

@@ -17,31 +17,31 @@
var module = app;
module.controller('messageController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('messageController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.allTopicList = [];
$scope.selectedTopic =[];
$scope.key ="";
$scope.messageId ="";
$scope.queryMessageByTopicResult=[];
$scope.queryMessageByTopicAndKeyResult=[];
$scope.queryMessageByMessageIdResult={};
$scope.selectedTopic = [];
$scope.key = "";
$scope.messageId = "";
$scope.queryMessageByTopicResult = [];
$scope.queryMessageByTopicAndKeyResult = [];
$scope.queryMessageByMessageIdResult = {};
$http({
method: "GET",
url: "topic/list.query",
params: {
skipSysProcess: 'true'
skipSysProcess: true
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
$scope.allTopicList = resp.data.topicList.sort();
console.log($scope.allTopicList);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
$scope.timepickerBegin = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm');
$scope.timepickerEnd = moment().add(1,'hour').format('YYYY-MM-DD HH:mm');
$scope.timepickerOptions ={format: 'YYYY-MM-DD HH:mm', showClear: true};
$scope.timepickerBegin = moment().subtract(3, 'hour').format('YYYY-MM-DD HH:mm');
$scope.timepickerEnd = moment().format('YYYY-MM-DD HH:mm');
$scope.timepickerOptions = {format: 'YYYY-MM-DD HH:mm', showClear: true};
$scope.taskId = "";
@@ -58,11 +58,12 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
};
$scope.queryMessagePageByTopic = function () {
$("#noMsgTip").css("display", "none");
if ($scope.timepickerEnd < $scope.timepickerBegin) {
Notification.error({message: "endTime is later than beginTime!", delay: 2000});
return
}
if( $scope.selectedTopic === [] || (typeof $scope.selectedTopic) == "object"){
if ($scope.selectedTopic === [] || (typeof $scope.selectedTopic) == "object") {
return
}
$http({
@@ -80,13 +81,16 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
if (resp.status === 0) {
console.log(resp);
$scope.messageShowList = resp.data.page.content;
if(resp.data.page.first){
if ($scope.messageShowList.length == 0){
$("#noMsgTip").removeAttr("style");
}
if (resp.data.page.first) {
$scope.paginationConf.currentPage = 1;
}
$scope.paginationConf.currentPage = resp.data.page.number + 1;
$scope.paginationConf.totalItems = resp.data.page.totalElements;
$scope.taskId = resp.data.taskId
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -114,10 +118,10 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
if (resp.status == 0) {
console.log(resp);
$scope.queryMessageByTopicResult = resp.data;
$scope.changeShowMessageList(1,$scope.queryMessageByTopicResult.length);
$scope.changeShowMessageList(1, $scope.queryMessageByTopicResult.length);
// todo
// console.log($scope.queryMessageByTopicResult);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -131,26 +135,26 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
url: "message/queryMessageByTopicAndKey.query",
params: {
topic: $scope.selectedTopic,
key:$scope.key
key: $scope.key
}
}).success(function (resp) {
if (resp.status == 0) {
console.log(resp);
$scope.queryMessageByTopicAndKeyResult = resp.data;
console.log($scope.queryMessageByTopicAndKeyResult);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.queryMessageByBrokerAndOffset = function (storeHost,commitLogOffset) {
$scope.queryMessageByBrokerAndOffset = function (storeHost, commitLogOffset) {
$http({
method: "GET",
url: "message/viewMessageByBrokerAndOffset.query",
params: {
brokerHost: storeHost.address,
port:storeHost.port,
port: storeHost.port,
offset: commitLogOffset
}
}).success(function (resp) {
@@ -167,13 +171,13 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
});
};
$scope.queryMessageByMessageId = function (messageId,topic) {
$scope.queryMessageByMessageId = function (messageId, topic) {
$http({
method: "GET",
url: "message/viewMessage.query",
params: {
msgId: messageId,
topic:topic
topic: topic
}
}).success(function (resp) {
if (resp.status == 0) {
@@ -181,23 +185,23 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
ngDialog.open({
template: 'messageDetailViewDialog',
controller: 'messageDetailViewDialogController',
data:resp.data
data: resp.data
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.changeShowMessageList = function (currentPage,totalItem) {
$scope.changeShowMessageList = function (currentPage, totalItem) {
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage)>totalItem?totalItem:from + perPage;
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
$scope.messageShowList = $scope.queryMessageByTopicResult.slice(from, to);
$scope.paginationConf.totalItems = totalItem ;
$scope.paginationConf.totalItems = totalItem;
};
$scope.onChangeQueryCondition = function (){
$scope.onChangeQueryCondition = function () {
console.log("change")
$scope.taskId = "";
$scope.paginationConf.currentPage = 1;
@@ -205,12 +209,13 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http','Notificat
}
}]);
module.controller('messageDetailViewDialogController',['$scope', 'ngDialog', '$http','Notification', function ($scope, ngDialog, $http,Notification) {
$scope.resendMessage = function (messageView,consumerGroup) {
module.controller('messageDetailViewDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.messageTrackList = $scope.ngDialogData.messageTrackList;
$scope.messageTrackShowList = $scope.ngDialogData.messageTrackList;
$scope.resendMessage = function (messageView, consumerGroup) {
var topic = messageView.topic;
var msgId = messageView.msgId;
console.log('==='+topic+'==='+msgId);
console.log('===' + topic + '===' + msgId);
if (topic.startsWith('%DLQ%')) {
if (messageView.properties.hasOwnProperty("RETRY_TOPIC")) {
topic = messageView.properties.RETRY_TOPIC;
@@ -220,44 +225,56 @@ module.controller('messageDetailViewDialogController',['$scope', 'ngDialog', '$h
}
}
console.log('==='+topic+'==='+msgId);
console.log('===' + topic + '===' + msgId);
$http({
method: "POST",
url: "message/consumeMessageDirectly.do",
params: {
msgId: msgId,
consumerGroup:consumerGroup,
topic:topic
consumerGroup: consumerGroup,
topic: topic
}
}).success(function (resp) {
if (resp.status == 0) {
ngDialog.open({
template: 'operationResultDialog',
data:{
result:resp.data
data: {
result: resp.data
}
});
}
else {
} else {
ngDialog.open({
template: 'operationResultDialog',
data:{
result:resp.errMsg
data: {
result: resp.errMsg
}
});
}
});
};
$scope.showExceptionDesc = function (errmsg) {
if(errmsg == null){
if (errmsg == null) {
errmsg = "Don't have Exception"
}
ngDialog.open({
template: 'operationResultDialog',
data:{
result:errmsg
data: {
result: errmsg
}
});
};
$scope.filterConsumerGroup = "";
$scope.$watch('filterConsumerGroup', function () {
const lowExceptStr = $scope.filterConsumerGroup.toLowerCase();
const canShowList = [];
$scope.messageTrackList.forEach(function (element) {
if (element.consumerGroup.toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push(element);
}
});
$scope.messageTrackShowList = canShowList;
});
}]
);

View File

@@ -26,9 +26,13 @@ const TIME_FORMAT_PATTERN = "YYYY-MM-DD HH:mm:ss.SSS";
const DEFAULT_DISPLAY_DURATION = 10 * 1000
// transactionTraceNode do not have costTime, assume it cost 50ms
const TRANSACTION_CHECK_COST_TIME = 50;
const RETRY_GROUP_TOPIC_PREFIX = "%RETRY%";
const DLQ_GROUP_TOPIC_PREFIX = "%DLQ%";
module.controller('messageTraceController', ['$scope', '$routeParams', 'ngDialog', '$http', 'Notification', function ($scope, $routeParams, ngDialog, $http, Notification) {
$scope.allTopicList = [];
$scope.selectedTopic = [];
$scope.allTraceTopicList = [];
$scope.selectedTraceTopic = [];
$scope.key = "";
$scope.messageId = $routeParams.messageId;
$scope.queryMessageByTopicAndKeyResult = [];
@@ -39,16 +43,25 @@ module.controller('messageTraceController', ['$scope', '$routeParams', 'ngDialog
method: "GET",
url: "topic/list.query",
params: {
skipSysProcess: "true"
skipSysProcess: true
}
}).success(function (resp) {
if (resp.status == 0) {
$scope.allTopicList = resp.data.topicList.sort();
console.log($scope.allTopicList);
console.log($scope.allTopicList)
for (const topic of $scope.allTopicList) {
if (topic.startsWith(RETRY_GROUP_TOPIC_PREFIX)
|| topic.startsWith(DLQ_GROUP_TOPIC_PREFIX)) {
continue;
}
$scope.allTraceTopicList.push(topic);
}
console.log($scope.allTraceTopicList)
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
$scope.timepickerBegin = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm');
$scope.timepickerEnd = moment().add(1, 'hour').format('YYYY-MM-DD HH:mm');
$scope.timepickerOptions = {format: 'YYYY-MM-DD HH:mm', showClear: true};
@@ -99,7 +112,7 @@ module.controller('messageTraceController', ['$scope', '$routeParams', 'ngDialog
url: "messageTrace/viewMessageTraceGraph.query",
params: {
msgId: messageId,
topic: topic
traceTopic: topic
}
}).success(function (resp) {
if (resp.status == 0) {

View File

@@ -15,45 +15,74 @@
* limitations under the License.
*/
app.controller('opsController', ['$scope','$location','$http','Notification','remoteApi','tools', function ($scope,$location,$http,Notification,remoteApi,tools) {
$scope.namesvrAddrList = "";
app.controller('opsController', ['$scope', '$location', '$http', 'Notification', 'remoteApi', 'tools', '$window', function ($scope, $location, $http, Notification, remoteApi, tools, $window) {
$scope.namesvrAddrList = [];
$scope.useVIPChannel = true;
$scope.useTLS = false;
$scope.userRole = $window.sessionStorage.getItem("userrole");
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
$scope.inputReadonly = !$scope.writeOperationEnabled;
$scope.newNamesrvAddr = "";
$http({
method: "GET",
url: "ops/homePage.query"
}).success(function (resp) {
if (resp.status == 0) {
$scope.namesvrAddrList = resp.data.namesvrAddrList.join(";");
$scope.namesvrAddrList = resp.data.namesvrAddrList;
$scope.useVIPChannel = resp.data.useVIPChannel;
$scope.useTLS = resp.data.useTLS;
}else{
$scope.selectedNamesrv = resp.data.currentNamesrv;
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
$scope.eleChange = function (data){
$scope.namesvrAddrList = data;
}
$scope.updateNameSvrAddr = function () {
$http({
method: "POST",
url: "ops/updateNameSvrAddr.do",
params:{nameSvrAddrList:$scope.namesvrAddrList}
params: {nameSvrAddrList: $scope.selectedNamesrv}
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "SUCCESS", delay: 2000});
}else{
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.addNameSvrAddr = function () {
$http({
method: "POST",
url: "ops/addNameSvrAddr.do",
params: {newNamesrvAddr: $scope.newNamesrvAddr}
}).success(function (resp) {
if (resp.status == 0) {
if ($scope.namesvrAddrList.indexOf($scope.newNamesrvAddr) == -1) {
$scope.namesvrAddrList.push($scope.newNamesrvAddr);
}
$("#namesrvAddr").val("");
$scope.newNamesrvAddr = "";
Notification.info({message: "SUCCESS", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.updateIsVIPChannel = function () {
$http({
method: "POST",
url: "ops/updateIsVIPChannel.do",
params:{useVIPChannel:$scope.useVIPChannel}
params: {useVIPChannel: $scope.useVIPChannel}
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "SUCCESS", delay: 2000});
}else{
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -62,11 +91,11 @@ app.controller('opsController', ['$scope','$location','$http','Notification','re
$http({
method: "POST",
url: "ops/updateUseTLS.do",
params:{useTLS:$scope.useTLS}
params: {useTLS: $scope.useTLS}
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "SUCCESS", delay: 2000});
}else{
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});

View File

@@ -23,7 +23,7 @@ module.controller('producerController', ['$scope', '$http','Notification',functi
method: "GET",
url: "topic/list.query",
params:{
skipSysProcess:"true"
skipSysProcess: true
}
}).success(function (resp) {
if(resp.status ==0){

View File

@@ -54,7 +54,7 @@ app.service('remoteApi', ['$http','tools', function ($http,tools) {
}
var queryTopicCurrentData = function(callback){
var url = 'dashboard/topicCurrent';
var url = 'dashboard/topicCurrent.query';
var setting = {
type: "GET",
timeout:15000,//data is too large,so master set time out is long enough

View File

@@ -1,23 +1,37 @@
/**
* Created by tcrow on 2017/1/12 0012.
/*
* 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.
*/
var module = app;
module.directive('ngConfirmClick', [
function(){
function () {
return {
link: function (scope, element, attr) {
var msg = attr.ngConfirmClick || "Are you sure?";
var clickAction = attr.confirmedClick;
element.bind('click',function (event) {
if ( window.confirm(msg) ) {
element.bind('click', function (event) {
if (window.confirm(msg)) {
scope.$eval(clickAction)
}
});
}
};
}]);
module.controller('topicController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notification', '$window', function ($scope, ngDialog, $http, Notification, $window) {
$scope.paginationConf = {
currentPage: 1,
totalItems: 0,
@@ -26,7 +40,7 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
perPageOptions: [10],
rememberPerPage: 'perPageItems',
onChange: function () {
$scope.showTopicList(this.currentPage,this.totalItems);
$scope.showTopicList(this.currentPage, this.totalItems);
}
};
@@ -36,19 +50,21 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
$scope.filterSystem = false
$scope.allTopicList = [];
$scope.topicShowList = [];
$scope.userRole = $window.sessionStorage.getItem("userrole");
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
$scope.refreshTopicList = function () {
$http({
method: "GET",
url: "topic/list.query"
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
$scope.allTopicList = resp.data.topicList.sort();
console.log($scope.allTopicList);
console.log(JSON.stringify(resp));
$scope.showTopicList(1,$scope.allTopicList.length);
$scope.showTopicList(1, $scope.allTopicList.length);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 5000});
}
});
@@ -56,93 +72,93 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
$scope.refreshTopicList();
$scope.filterStr="";
$scope.$watch('filterStr', function() {
$scope.filterStr = "";
$scope.$watch('filterStr', function () {
$scope.filterList(1);
});
$scope.$watch('filterNormal', function() {
$scope.$watch('filterNormal', function () {
$scope.filterList(1);
});
$scope.$watch('filterRetry', function() {
$scope.$watch('filterRetry', function () {
$scope.filterList(1);
});
$scope.$watch('filterDLQ', function() {
$scope.$watch('filterDLQ', function () {
$scope.filterList(1);
});
$scope.$watch('filterSystem', function() {
$scope.$watch('filterSystem', function () {
$scope.filterList(1);
});
$scope.filterList = function (currentPage) {
var lowExceptStr = $scope.filterStr.toLowerCase();
var canShowList = [];
$scope.allTopicList.forEach(function(element) {
if($scope.filterByType(element)){
if (element.toLowerCase().indexOf(lowExceptStr) != -1){
$scope.allTopicList.forEach(function (element) {
if ($scope.filterByType(element)) {
if (element.toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push(element);
}
}
});
$scope.paginationConf.totalItems =canShowList.length;
$scope.paginationConf.totalItems = canShowList.length;
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage)>canShowList.length?canShowList.length:from + perPage;
var to = (from + perPage) > canShowList.length ? canShowList.length : from + perPage;
$scope.topicShowList = canShowList.slice(from, to);
};
$scope.filterByType = function(str){
if($scope.filterRetry){
if(str.startsWith("%R")){
$scope.filterByType = function (str) {
if ($scope.filterRetry) {
if (str.startsWith("%R")) {
return true
}
}
if($scope.filterDLQ){
if(str.startsWith("%D")){
if ($scope.filterDLQ) {
if (str.startsWith("%D")) {
return true
}
}
if($scope.filterSystem){
if(str.startsWith("%S")){
if ($scope.filterSystem) {
if (str.startsWith("%S")) {
return true
}
}
if($scope.filterNormal){
if(str.startsWith("%") == false){
if ($scope.filterNormal) {
if (str.startsWith("%") == false) {
return true
}
}
return false;
};
$scope.showTopicList = function (currentPage,totalItem) {
if($scope.filterStr != ""){
$scope.showTopicList = function (currentPage, totalItem) {
if ($scope.filterStr != "") {
$scope.filterList(currentPage);
return;
}
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage)>totalItem?totalItem:from + perPage;
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
console.log($scope.allTopicList);
console.log(from)
console.log(to)
$scope.topicShowList = $scope.allTopicList.slice(from, to);
$scope.paginationConf.totalItems = totalItem ;
$scope.paginationConf.totalItems = totalItem;
console.log($scope.topicShowList)
console.log($scope.paginationConf.totalItems)
$scope.filterList(currentPage);
};
$scope.deleteTopic= function (topic) {
$scope.deleteTopic = function (topic) {
$http({
method: "POST",
url: "topic/deleteTopic.do",
params:{
topic:topic
params: {
topic: topic
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
Notification.info({message: "delete success!", delay: 2000});
$scope.refreshTopicList();
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -157,13 +173,13 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
console.log(JSON.stringify(resp));
ngDialog.open({
template: 'statsViewDialog',
trapFocus:false,
data:{
topic:topic,
statsData:resp.data
trapFocus: false,
data: {
topic: topic,
statsData: resp.data
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
@@ -179,13 +195,13 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
ngDialog.open({
template: 'routerViewDialog',
controller: 'routerViewDialogController',
trapFocus:false,
data:{
topic:topic,
routeData:resp.data
trapFocus: false,
data: {
topic: topic,
routeData: resp.data
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
@@ -202,13 +218,13 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
console.log(JSON.stringify(resp));
ngDialog.open({
template: 'consumerViewDialog',
data:{
topic:topic,
consumerData:resp.data,
consumerGroupCount:Object.keys(resp.data).length
data: {
topic: topic,
consumerData: resp.data,
consumerGroupCount: Object.keys(resp.data).length
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
@@ -217,9 +233,9 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
ngDialog.open({
template: 'deleteTopicDialog',
controller: 'deleteTopicDialogController',
data:{
topic:topic,
consumerData:"asd"
data: {
topic: topic,
consumerData: "asd"
}
});
};
@@ -229,25 +245,25 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
$http({
method: "GET",
url: "topic/queryTopicConsumerInfo.query",
params:{
topic:topic
params: {
topic: topic
}
}).success(function (resp) {
if(resp.status ==0){
if(resp.data.groupList == null){
if (resp.status == 0) {
if (resp.data.groupList == null) {
Notification.error({message: "don't have consume group!", delay: 2000});
return
}
ngDialog.open({
template: 'consumerResetOffsetDialog',
controller: 'consumerResetOffsetDialogController',
data:{
data: {
topic: topic,
selectedConsumerGroup:[],
allConsumerGroupList:resp.data.groupList
selectedConsumerGroup: [],
allConsumerGroupList: resp.data.groupList
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -258,25 +274,25 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
$http({
method: "GET",
url: "topic/queryTopicConsumerInfo.query",
params:{
topic:topic
params: {
topic: topic
}
}).success(function (resp) {
if(resp.status ==0){
if(resp.data.groupList == null){
if (resp.status == 0) {
if (resp.data.groupList == null) {
Notification.error({message: "don't have consume group!", delay: 2000});
return
}
ngDialog.open({
template: 'skipMessageAccumulateDialog',
controller: 'skipMessageAccumulateDialogController',
data:{
data: {
topic: topic,
selectedConsumerGroup:[],
allConsumerGroupList:resp.data.groupList
selectedConsumerGroup: [],
allConsumerGroupList: resp.data.groupList
}
});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -296,13 +312,13 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
$http({
method: "GET",
url: "topic/examineTopicConfig.query",
params:{
topic:topic
params: {
topic: topic
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
$scope.openCreateOrUpdateDialog(resp.data, sysFlag);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
@@ -310,14 +326,14 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
$scope.openCreateOrUpdateDialog = function (request, sysFlag) {
var bIsUpdate = true;
if(request == null){
if (request == null) {
request = [{
writeQueueNums:16,
readQueueNums:16,
perm:6,
order:false,
topicName:"",
brokerNameList:[]
writeQueueNums: 16,
readQueueNums: 16,
perm: 6,
order: false,
topicName: "",
brokerNameList: []
}];
bIsUpdate = false;
}
@@ -325,21 +341,22 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
method: "GET",
url: "cluster/list.query"
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
console.log(resp);
ngDialog.open({
preCloseCallback: function(value) {
preCloseCallback: function (value) {
// Refresh topic list
$scope.refreshTopicList();
},
template: 'topicModifyDialog',
controller: 'topicModifyDialogController',
data:{
sysFlag:sysFlag,
topicRequestList:request,
allClusterNameList:Object.keys(resp.data.clusterInfo.clusterAddrTable),
allBrokerNameList:Object.keys(resp.data.brokerServer),
bIsUpdate:bIsUpdate
data: {
sysFlag: sysFlag,
topicRequestList: request,
allClusterNameList: Object.keys(resp.data.clusterInfo.clusterAddrTable),
allBrokerNameList: Object.keys(resp.data.brokerServer),
bIsUpdate: bIsUpdate,
writeOperationEnabled: $scope.writeOperationEnabled
}
});
}
@@ -352,7 +369,7 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http','Notificatio
}]);
module.controller('topicModifyDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('topicModifyDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.postTopicRequest = function (topicRequestItem) {
console.log(topicRequestItem);
var request = JSON.parse(JSON.stringify(topicRequestItem));
@@ -360,19 +377,19 @@ module.controller('topicModifyDialogController', ['$scope', 'ngDialog', '$http',
$http({
method: "POST",
url: "topic/createOrUpdate.do",
data:request
data: request
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
Notification.info({message: "success!", delay: 2000});
ngDialog.close(this);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
}
}]
);
module.controller('consumerResetOffsetDialogController',['$scope', 'ngDialog', '$http','Notification', function ($scope, ngDialog, $http,Notification) {
module.controller('consumerResetOffsetDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.timepicker = {};
$scope.timepicker.date = moment().format('YYYY-MM-DD HH:mm');
$scope.timepicker.options = {format: 'YYYY-MM-DD HH:mm', showClear: true};
@@ -386,19 +403,19 @@ module.controller('consumerResetOffsetDialogController',['$scope', 'ngDialog', '
data: {
resetTime: $scope.timepicker.date.valueOf(),
consumerGroupList: $scope.ngDialogData.selectedConsumerGroup,
topic:$scope.ngDialogData.topic,
force:true
topic: $scope.ngDialogData.topic,
force: true
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
ngDialog.open({
template: 'resetOffsetResultDialog',
data:{
result:resp.data
data: {
result: resp.data
}
});
ngDialog.close(this);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
@@ -406,7 +423,7 @@ module.controller('consumerResetOffsetDialogController',['$scope', 'ngDialog', '
}]
);
module.controller('skipMessageAccumulateDialogController',['$scope', 'ngDialog', '$http','Notification', function ($scope, ngDialog, $http,Notification) {
module.controller('skipMessageAccumulateDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.skipAccumulate = function () {
console.log($scope.ngDialogData.selectedConsumerGroup);
$http({
@@ -415,19 +432,19 @@ module.controller('skipMessageAccumulateDialogController',['$scope', 'ngDialog',
data: {
resetTime: -1,
consumerGroupList: $scope.ngDialogData.selectedConsumerGroup,
topic:$scope.ngDialogData.topic,
force:true
topic: $scope.ngDialogData.topic,
force: true
}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
ngDialog.open({
template: 'resetOffsetResultDialog',
data:{
result:resp.data
data: {
result: resp.data
}
});
ngDialog.close(this);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
@@ -435,7 +452,7 @@ module.controller('skipMessageAccumulateDialogController',['$scope', 'ngDialog',
}]
);
module.controller('sendTopicMessageDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('sendTopicMessageDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.sendTopicMessage = {
topic: $scope.ngDialogData.topic,
key: "key",
@@ -449,37 +466,35 @@ module.controller('sendTopicMessageDialogController', ['$scope', 'ngDialog', '$h
url: "topic/sendTopicMessage.do",
data: $scope.sendTopicMessage
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
ngDialog.open({
template: 'sendResultDialog',
data:{
result:resp.data
data: {
result: resp.data
}
});
ngDialog.close(this);
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
}
}]
);
module.controller('routerViewDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
module.controller('routerViewDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
$scope.deleteTopicByBroker = function (broker) {
$http({
method: "POST",
url: "topic/deleteTopicByBroker.do",
params: {brokerName:broker.brokerName,topic:$scope.ngDialogData.topic}
params: {brokerName: broker.brokerName, topic: $scope.ngDialogData.topic}
}).success(function (resp) {
if(resp.status ==0){
if (resp.status == 0) {
Notification.info({message: "delete success", delay: 2000});
}else {
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
})
};
}]
);

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