Compare commits

...

52 Commits

Author SHA1 Message Date
zzl
ffdef24fc5 Merge d55a70fb16 into e57d423268 2024-11-14 11:15:16 +08:00
yuz10
e57d423268 remove rocketmq-namesrv dependency (#254) 2024-11-04 15:49:02 +08:00
dependabot[bot]
0d87486d7a Bump snakeyaml from 1.30 to 1.32 (#130)
---
updated-dependencies:
- dependency-name: org.yaml:snakeyaml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-04 11:36:30 +08:00
yuz10
94d7a4e418 remove rocketmq-broker dependency (#249) 2024-11-04 11:35:39 +08:00
xx
3fbaa3ab92 fix: Duplicate message in the topic tab message list in the message menu. (#202)
Co-authored-by: xx <xx@123.com>
2024-11-04 11:35:17 +08:00
RongtongJin
e6d454301f [maven-release-plugin] prepare for next development iteration 2024-09-18 09:57:37 +08:00
RongtongJin
f5c09ac287 [maven-release-plugin] prepare release rocketmq-dashboard-2.0.0 2024-09-18 09:57:04 +08:00
Guyu
e97072a3b1 style: Remove unused imports for the checkstyle. (#232)
* fix: 5.x query message detail throw: Failed to query message by Id: xxx

* style: Remove unused imports for the checkstyle.

---------

Co-authored-by: yangzengc <yangzengc@ewan.cn>
2024-09-12 16:51:34 +08:00
Evan
6d360509c0 fix: 5.x query message detail throw: Failed to query message by Id: xxx (#231)
Co-authored-by: yangzengc <yangzengc@ewan.cn>
2024-09-07 19:59:38 +08:00
Akai
464f57adf8 Support retryMaxTimes filed set for consumer group (#229)
* fix:Fixed the issue that normal messages in version v4 are not showed

* feat:support retryMaxTimes args set

---------

Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
Co-authored-by: yuanziwei.akai <yuanziwei.akai@bytedance.com>
2024-08-27 20:30:00 +08:00
Akai
5d08d3b122 Support Unspecified Topic Add & Update & Query (#223)
* fix:Fixed the issue that normal messages in version v4 are not showed

* feat:support unspecified topic

---------

Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
2024-07-24 10:57:04 +08:00
Akai
d9fc76d3a3 fix:Fixed the issue that normal messages in version v4 are not showed (#222)
Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
2024-07-24 10:56:47 +08:00
Akai
2bc59db340 Supplement UserGuide for RocketMQ 5.0 (#208)
* Support UserGuide for new feature

* update userGuide md

* update userGuide md

---------

Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
2024-06-13 15:22:38 +08:00
shizhili
d55a70fb16 [ISSUE #209] make CID_DefaultHeartbeatSyncerTopic to be system group 2024-06-13 10:44:09 +08:00
Akai
d58e13da95 Proxy Support And ConsumerGroup Enhancement (#207)
* Support dashboard v4-v5 switch And query for v5 topic

* Modify tag name

* Support proxy-module And Fix the problem of showing wrong consumerGroup-info

---------

Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
2024-06-12 09:12:19 +08:00
Akai
e7cb315050 Support FIFO-Type SubGroup Add、Update and Query For V5 (#204)
* Support dashboard v4-v5 switch And query for v5 topic

* Modify tag name

* Support subGroup FIFO Type Query and Update

---------

Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
2024-06-11 10:53:36 +08:00
Akai
21dc2acfdc Support dashboard v4-v5 switch And query for v5 topic (#198)
* Support dashboard v4-v5 switch And query for v5 topic

* Modify tag name

---------

Co-authored-by: yuanziwei <yuanziwei@xiaomi.com>
2024-06-05 15:17:23 +08:00
guangdashao
823bce2b8b feat: add topic message type
add message type
2024-06-04 11:40:46 +08:00
Javen
2fb0fce0b1 perf: The new metrics of getTransferredTps for rocketmq5.x and the old metrics of getTransferedTps for rocketmq4.x (#197)
Co-authored-by: jinwei2 <jinwei2@enmonster.com>
2024-03-26 17:02:16 +08:00
Abhijeet Mishra
6456630324 [#148] Throwables.propagate in deprecated for making runtime exception more verbose (#160) 2023-04-19 20:33:17 +08:00
Abhijeet Mishra
a25ccd6337 5.1.0 rocketmq version update (#155)
* update rocketmq version to 5.1.0
2023-04-07 08:30:18 +08:00
Abhijeet Mishra
538d1c1c45 [ISSUE apache#149] updated lombok version in pom.xml because of this compilation was failing (#151) 2023-03-20 15:33:45 +08: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
109 changed files with 4734 additions and 563 deletions

View File

@@ -9,3 +9,10 @@ github:
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

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
.project
.factorypath
.settings/
.vscode

View File

@@ -20,3 +20,5 @@ script:
# - 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'

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.

2
NOTICE
View File

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

View File

@@ -1,5 +1,7 @@
## [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)
## [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)

View File

@@ -63,6 +63,18 @@
* 根据消息主题和消息Id进行消息的查询
* 消息详情可以展示这条消息的详细信息,查看消息对应到具体消费组的消费情况(如果异常,可以查看具体的异常信息)。可以向指定的消费组重发消息。
## RocketMQ-V5.0 仪表盘
* 版本切换
* RocketMQ右上角可切换不同版本用户可以自主选择 RocketMQ-5.x 或 RocketMQ-4.x 版本
* 主题页面
* 支持延迟/顺序/事务消息的筛选
* 支持延迟/顺序/事物/普通等多种消息类型主题的新增与更新
* 消费页面
* 支持顺序消费类型订阅组的过滤
* 提供顺序消费类型订阅组的新增与更新如果需要开启顺序消费FIFO类型的订阅组一定需要打开consumeOrderlyEnable选项
* 代理页面RocketMQ 5.0新增)
* 支持代理节点的新增与查询
* 支持代理节点地址配置在application.yml中可对proxyAddr和proxyAddrs属性进行预配置
## HTTPS 方式访问Dashboard
* HTTPS功能实际上是使用SpringBoot提供的配置功能即可完成首先需要有一个SSL KeyStore来存放服务端证书可以使用本工程所提供的测试密钥库:

View File

@@ -64,6 +64,18 @@
* look over this message's detail info.you can see the message's consume state(each group has one line),show the exception message if has exception.
you can send this message to the group you selected
## RocketMQ-V5.0 dashboard
* Version switching
* RocketMQ can switch between different versions in the upper right corner, and users can freely choose between RocketMQ-5.X or RocketMQ-4.X versions
* Theme page
* Support filtering of delayed/sequential/transaction messages
* Support the addition and update of multiple message types such as delay, sequence, object, and ordinary themes
* Consumption page
* Support filtering of subscription groups for fifo consumption types
* Provide the addition and update of subscription groups for sequential consumption types. If fifo consumption needs to be enabled, FIFO type subscription groups must have the consumeOrderlyEnable option enabled
* Proxy page (Added in RocketMQ 5.0)
* Support for adding and querying proxy nodes
* Support proxy node address configuration: ProxyAddr and proxyAddrs properties can be pre configured in application.yml
## Access Dashboard with HTTPS
* SpringBoot itself has provided the SSL configuration. You can use the project test Keystore:resources/rmqcngkeystore.jks. The store is generated with the following unix keytool commands:

48
pom.xml
View File

@@ -28,7 +28,7 @@
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-dashboard</artifactId>
<packaging>jar</packaging>
<version>1.0.1-SNAPSHOT</version>
<version>2.0.1-SNAPSHOT</version>
<name>rocketmq-dashboard</name>
<scm>
@@ -90,18 +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>5.1.0</rocketmq.version>
<surefire.version>2.19.1</surefire.version>
<aspectj.version>1.9.6</aspectj.version>
<lombok.version>1.18.12</lombok.version>
<lombok.version>1.18.22</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.32</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>
@@ -125,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>
@@ -150,6 +167,7 @@
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-namesrv</artifactId>
<version>${rocketmq.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -169,6 +187,7 @@
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-broker</artifactId>
<version>${rocketmq.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -203,17 +222,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>
@@ -246,6 +265,17 @@
<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>
@@ -431,7 +461,7 @@
<configuration>
<target>
<copy todir="${project.build.directory}/classes/public">
<fileset dir="${project.basedir}/frontend/build"/>
<fileset dir="${project.basedir}/frontend/build" />
</copy>
</target>
</configuration>

View File

@@ -16,6 +16,7 @@
*/
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;
@@ -33,6 +34,8 @@ public class MQAdminFactory {
this.rmqConfigure = rmqConfigure;
}
private final AtomicLong adminIndex = new AtomicLong(0);
public MQAdminExt getInstance() throws Exception {
RPCHook rpcHook = null;
final String accessKey = rmqConfigure.getAccessKey();
@@ -47,6 +50,7 @@ public class MQAdminFactory {
} 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()));

View File

@@ -21,7 +21,7 @@ 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.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.tools.admin.MQAdminExt;
@Slf4j

View File

@@ -53,6 +53,7 @@ public class AuthWebMVCConfigurerAdapter extends WebMvcConfigurerAdapter {
"/cluster/**",
"/consumer/**",
"/dashboard/**",
"/dlqMessage/**",
"/message/**",
"/messageTrace/**",
"/monitor/**",
@@ -60,7 +61,8 @@ public class AuthWebMVCConfigurerAdapter extends WebMvcConfigurerAdapter {
"/ops/**",
"/producer/**",
"/test/**",
"/topic/**");
"/topic/**",
"/acl/**");
}
}

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,6 +16,8 @@
*/
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.slf4j.Logger;
@@ -29,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;
@@ -40,6 +43,8 @@ public class RMQConfigure {
//use rocketmq.namesrv.addr first,if it is empty,than use system proerty or system env
private volatile String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV));
private volatile String proxyAddr;
private volatile String isVIPChannel = System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "true");
@@ -57,6 +62,10 @@ public class RMQConfigure {
private Long timeoutMillis;
private List<String> namesrvAddrs = new ArrayList<>();
private List<String> proxyAddrs = new ArrayList<>();
public String getAccessKey() {
return accessKey;
}
@@ -77,6 +86,36 @@ public class RMQConfigure {
return namesrvAddr;
}
public List<String> getNamesrvAddrs() {
return namesrvAddrs;
}
public List<String> getProxyAddrs() {
return this.proxyAddrs;
}
public void setProxyAddrs(List<String> proxyAddrs) {
this.proxyAddrs = proxyAddrs;
if (CollectionUtils.isNotEmpty(proxyAddrs)) {
this.setProxyAddr(proxyAddrs.get(0));
}
}
public String getProxyAddr() {
return proxyAddr;
}
public void setProxyAddr(String proxyAddr) {
this.proxyAddr = proxyAddr;
}
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;

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

@@ -19,7 +19,7 @@ package org.apache.rocketmq.dashboard.controller;
import com.google.common.base.Preconditions;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.dashboard.model.ConnectionInfo;
import org.apache.rocketmq.dashboard.model.request.ConsumerConfigInfo;
import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
@@ -47,14 +47,14 @@ public class ConsumerController {
@RequestMapping(value = "/groupList.query")
@ResponseBody
public Object list(@RequestParam(value = "skipSysGroup", required = false) boolean skipSysGroup) {
return consumerService.queryGroupList(skipSysGroup);
public Object list(@RequestParam(value = "skipSysGroup", required = false) boolean skipSysGroup, String address) {
return consumerService.queryGroupList(skipSysGroup, address);
}
@RequestMapping(value = "/group.query")
@ResponseBody
public Object groupQuery(@RequestParam String consumerGroup) {
return consumerService.queryGroup(consumerGroup);
public Object groupQuery(@RequestParam String consumerGroup, String address) {
return consumerService.queryGroup(consumerGroup, address);
}
@RequestMapping(value = "/resetOffset.do", method = {RequestMethod.POST})
@@ -99,14 +99,14 @@ public class ConsumerController {
@RequestMapping(value = "/queryTopicByConsumer.query")
@ResponseBody
public Object queryConsumerByTopic(@RequestParam String consumerGroup) {
return consumerService.queryConsumeStatsListByGroupName(consumerGroup);
public Object queryConsumerByTopic(@RequestParam String consumerGroup, String address) {
return consumerService.queryConsumeStatsListByGroupName(consumerGroup, address);
}
@RequestMapping(value = "/consumerConnection.query")
@ResponseBody
public Object consumerConnection(@RequestParam(required = false) String consumerGroup) {
ConsumerConnection consumerConnection = consumerService.getConsumerConnection(consumerGroup);
public Object consumerConnection(@RequestParam(required = false) String consumerGroup, String address) {
ConsumerConnection consumerConnection = consumerService.getConsumerConnection(consumerGroup, address);
consumerConnection.setConnectionSet(ConnectionInfo.buildConnectionInfoHashSet(consumerConnection.getConnectionSet()));
return consumerConnection;
}

View File

@@ -51,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

@@ -17,12 +17,16 @@
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;
@@ -30,6 +34,7 @@ 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;
@@ -39,6 +44,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/dlqMessage")
@Permission
@Slf4j
public class DlqMessageController {
@Resource
@@ -70,4 +76,33 @@ public class DlqMessageController {
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

@@ -18,7 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
import com.google.common.collect.Maps;
import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.remoting.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;

View File

@@ -32,7 +32,7 @@ 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,9 @@
*/
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;
@@ -46,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) {
@@ -53,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

@@ -17,7 +17,7 @@
package org.apache.rocketmq.dashboard.controller;
import javax.annotation.Resource;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
import org.apache.rocketmq.remoting.protocol.body.ProducerConnection;
import org.apache.rocketmq.dashboard.model.ConnectionInfo;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.ProducerService;

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.
*/
package org.apache.rocketmq.dashboard.controller;
import org.apache.rocketmq.dashboard.permisssion.Permission;
import org.apache.rocketmq.dashboard.service.ProxyService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
@Controller
@RequestMapping("/proxy")
@Permission
public class ProxyController {
@Resource
private ProxyService proxyService;
@RequestMapping(value = "/homePage.query", method = RequestMethod.GET)
@ResponseBody
public Object homePage() {
return proxyService.getProxyHomePage();
}
@RequestMapping(value = "/addProxyAddr.do", method = RequestMethod.POST)
@ResponseBody
public Object addProxyAddr(@RequestParam String newProxyAddr) {
proxyService.addProxyAddrList(newProxyAddr);
return true;
}
@RequestMapping(value = "/updateProxyAddr.do", method = RequestMethod.POST)
@ResponseBody
public Object updateProxyAddr(@RequestParam String proxyAddr) {
proxyService.updateProxyAddrList(proxyAddr);
return true;
}
}

View File

@@ -56,6 +56,12 @@ public class TopicController {
return topicService.fetchAllTopicList(skipSysProcess, skipRetryAndDlq);
}
@RequestMapping(value = "/list.queryTopicType", method = RequestMethod.GET)
@ResponseBody
public Object listTopicType() {
return topicService.examineAllTopicType();
}
@RequestMapping(value = "/stats.query", method = RequestMethod.GET)
@ResponseBody
public Object stats(@RequestParam String topic) {

View File

@@ -20,7 +20,7 @@ import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.HashSet;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.protocol.body.Connection;
import org.apache.rocketmq.remoting.protocol.body.Connection;
public class ConnectionInfo extends Connection {
private String versionDesc;

View File

@@ -16,7 +16,7 @@
*/
package org.apache.rocketmq.dashboard.model;
import org.apache.rocketmq.common.admin.RollbackStats;
import org.apache.rocketmq.remoting.protocol.admin.RollbackStats;
import com.google.common.collect.Lists;
import java.util.List;

View File

@@ -25,9 +25,11 @@ 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)
@@ -66,6 +68,10 @@ public class DlqMessageExcelModel extends BaseRowModel implements Serializable {
@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();

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.remoting.protocol.body.CMResult;
import org.apache.rocketmq.remoting.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

@@ -16,8 +16,10 @@
*/
package org.apache.rocketmq.dashboard.model;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
import java.util.List;
public class GroupConsumeInfo implements Comparable<GroupConsumeInfo> {
private String group;
@@ -25,8 +27,11 @@ public class GroupConsumeInfo implements Comparable<GroupConsumeInfo> {
private int count;
private ConsumeType consumeType;
private MessageModel messageModel;
private List<String> address;
private int consumeTps;
private long diffTotal = -1;
private String subGroupType = "NORMAL";
public String getGroup() {
return group;
@@ -68,6 +73,22 @@ public class GroupConsumeInfo implements Comparable<GroupConsumeInfo> {
this.diffTotal = diffTotal;
}
public List<String> getAddress() {
return address;
}
public void setAddress(List<String> address) {
this.address = address;
}
public String getSubGroupType() {
return subGroupType;
}
public void setSubGroupType(String subGroupType) {
this.subGroupType = subGroupType;
}
@Override
public int compareTo(GroupConsumeInfo o) {
if (this.count != o.count) {

View File

@@ -16,7 +16,7 @@
*/
package org.apache.rocketmq.dashboard.model;
import org.apache.rocketmq.common.admin.OffsetWrapper;
import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper;
import org.apache.rocketmq.common.message.MessageQueue;
import org.springframework.beans.BeanUtils;

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

@@ -16,7 +16,7 @@
*/
package org.apache.rocketmq.dashboard.model.request;
import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
import java.util.List;

View File

@@ -15,7 +15,6 @@
* limitations under the License.
*/
package org.apache.rocketmq.dashboard.model.request;
import com.google.common.base.Objects;
import java.util.List;
@@ -32,6 +31,7 @@ public class TopicConfigInfo {
private int perm;
private boolean order;
private String messageType;
public List<String> getClusterNameList() {
return clusterNameList;
}
@@ -92,6 +92,18 @@ public class TopicConfigInfo {
this.order = order;
}
public String getMessageType() {
return messageType;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
@Override
public boolean equals(Object o) {
if (this == o)
@@ -103,12 +115,13 @@ public class TopicConfigInfo {
readQueueNums == that.readQueueNums &&
perm == that.perm &&
order == that.order &&
Objects.equal(topicName, that.topicName);
Objects.equal(topicName, that.topicName) &&
Objects.equal(messageType, that.messageType);
}
@Override
public int hashCode() {
return Objects.hashCode(topicName, writeQueueNums, readQueueNums, perm, order);
return Objects.hashCode(topicName, writeQueueNums, readQueueNums, perm, order,messageType);
}
}

View File

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

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.model.request;
public class TopicTypeMeta {
private String topicName;
private String messageType;
public String getTopicName() {
return topicName;
}
public void setTopicName(String topicName) {
this.topicName = topicName;
}
public String getMessageType() {
return messageType;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
}

View File

@@ -19,8 +19,8 @@ package org.apache.rocketmq.dashboard.service;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
@@ -28,7 +28,7 @@ import org.apache.commons.collections.CollectionUtils;
public abstract class AbstractCommonService {
@Resource
protected MQAdminExt mqAdminExt;
protected final Set<String> changeToBrokerNameSet(HashMap<String, Set<String>> clusterAddrTable,
protected final Set<String> changeToBrokerNameSet(Map<String, Set<String>> clusterAddrTable,
List<String> clusterNameList, List<String> brokerNameList) {
Set<String> finalBrokerNameList = Sets.newHashSet();
if (CollectionUtils.isNotEmpty(clusterNameList)) {
@@ -38,7 +38,8 @@ public abstract class AbstractCommonService {
}
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
if (CollectionUtils.isNotEmpty(brokerNameList)) {

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

@@ -17,8 +17,8 @@
package org.apache.rocketmq.dashboard.service;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.dashboard.model.ConsumerGroupRollBackStat;
import org.apache.rocketmq.dashboard.model.GroupConsumeInfo;
import org.apache.rocketmq.dashboard.model.TopicConsumerInfo;
@@ -31,12 +31,12 @@ import java.util.Map;
import java.util.Set;
public interface ConsumerService {
List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup);
List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup,String address);
GroupConsumeInfo queryGroup(String consumerGroup);
GroupConsumeInfo queryGroup(String consumerGroup, String address);
List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName);
List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName, String address);
List<TopicConsumerInfo> queryConsumeStatsList(String topic, String groupName);
@@ -52,7 +52,7 @@ public interface ConsumerService {
Set<String> fetchBrokerNameSetBySubscriptionGroup(String group);
ConsumerConnection getConsumerConnection(String consumerGroup);
ConsumerConnection getConsumerConnection(String consumerGroup, String address);
ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack);
}

View File

@@ -17,10 +17,15 @@
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

@@ -19,7 +19,7 @@ package org.apache.rocketmq.dashboard.service;
import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.dashboard.model.MessagePage;
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
import org.apache.rocketmq.tools.admin.api.MessageTrack;

View File

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

View File

@@ -17,7 +17,7 @@
package org.apache.rocketmq.dashboard.service;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
import org.apache.rocketmq.remoting.protocol.body.ProducerConnection;
public interface ProducerService {
ProducerConnection getProducerConnection(String producerGroup, String topic);

View File

@@ -0,0 +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.
*/
package org.apache.rocketmq.dashboard.service;
import java.util.Map;
public interface ProxyService {
void addProxyAddrList(String proxyAddr);
void updateProxyAddrList(String proxyAddr);
Map<String, Object> getProxyHomePage();
}

View File

@@ -19,10 +19,11 @@ package org.apache.rocketmq.dashboard.service;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.admin.TopicStatsTable;
import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.dashboard.model.request.TopicTypeList;
import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
import org.apache.rocketmq.dashboard.model.request.SendTopicMessageRequest;
import org.apache.rocketmq.dashboard.model.request.TopicConfigInfo;
@@ -31,6 +32,8 @@ import java.util.List;
public interface TopicService {
TopicList fetchAllTopicList(boolean skipSysProcess, boolean skipRetryAndDlq);
TopicTypeList examineAllTopicType();
TopicStatsTable stats(String topic);
TopicRouteData route(String topic);

View File

@@ -17,43 +17,16 @@
package org.apache.rocketmq.dashboard.service.client;
import com.google.common.base.Throwables;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.rocketmq.client.QueryResult;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.impl.MQAdminImpl;
import org.apache.rocketmq.common.AclConfig;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.admin.ConsumeStats;
import org.apache.rocketmq.common.admin.RollbackStats;
import org.apache.rocketmq.common.admin.TopicStatsTable;
import org.apache.rocketmq.common.message.MessageClientIDSetter;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.BrokerStatsData;
import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumeStatsList;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
import org.apache.rocketmq.common.protocol.body.QueryConsumeQueueResponseBody;
import org.apache.rocketmq.common.protocol.body.QueueTimeSpan;
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.common.message.MessageRequestMode;
import org.apache.rocketmq.dashboard.util.JsonUtil;
import org.apache.rocketmq.remoting.RemotingClient;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -62,13 +35,51 @@ import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
import org.apache.rocketmq.remoting.protocol.admin.RollbackStats;
import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo;
import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData;
import org.apache.rocketmq.remoting.protocol.body.ClusterAclVersionInfo;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.remoting.protocol.body.ConsumeStatsList;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.remoting.protocol.body.EpochEntryCache;
import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo;
import org.apache.rocketmq.remoting.protocol.body.KVTable;
import org.apache.rocketmq.remoting.protocol.body.ProducerConnection;
import org.apache.rocketmq.remoting.protocol.body.ProducerTableInfo;
import org.apache.rocketmq.remoting.protocol.body.QueryConsumeQueueResponseBody;
import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan;
import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader;
import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingDetail;
import org.apache.rocketmq.remoting.protocol.subscription.GroupForbidden;
import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.apache.rocketmq.tools.admin.api.BrokerOperatorResult;
import org.apache.rocketmq.tools.admin.api.MessageTrack;
import org.joor.Reflect;
import org.apache.rocketmq.tools.admin.common.AdminToolResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import static org.apache.rocketmq.remoting.protocol.RemotingSerializable.decode;
@Service
@@ -81,7 +92,7 @@ public class MQAdminExtImpl implements MQAdminExt {
@Override
public void updateBrokerConfig(String brokerAddr, Properties properties)
throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
UnsupportedEncodingException, InterruptedException, MQBrokerException {
UnsupportedEncodingException, InterruptedException, MQBrokerException, MQClientException {
MQAdminInstance.threadLocalMQAdminExt().updateBrokerConfig(brokerAddr, properties);
}
@@ -91,29 +102,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
@@ -123,7 +139,7 @@ public class MQAdminExtImpl implements MQAdminExt {
}
@Override
public SubscriptionGroupConfig examineSubscriptionGroupConfig(String addr, String group) {
public SubscriptionGroupConfig examineSubscriptionGroupConfig(String addr, String group) throws MQBrokerException {
RemotingClient remotingClient = MQAdminInstance.threadLocalRemotingClient();
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, null);
RemotingCommand response = null;
@@ -131,7 +147,8 @@ public class MQAdminExtImpl implements MQAdminExt {
response = remotingClient.invokeSync(addr, request, 3000);
}
catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
assert response != null;
switch (response.getCode()) {
@@ -140,12 +157,12 @@ public class MQAdminExtImpl implements MQAdminExt {
return subscriptionGroupWrapper.getSubscriptionGroupTable().get(group);
}
default:
throw Throwables.propagate(new MQBrokerException(response.getCode(), response.getRemark()));
throw new MQBrokerException(response.getCode(), response.getRemark());
}
}
@Override
public TopicConfig examineTopicConfig(String addr, String topic) {
public TopicConfig examineTopicConfig(String addr, String topic) throws MQBrokerException {
RemotingClient remotingClient = MQAdminInstance.threadLocalRemotingClient();
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, null);
RemotingCommand response = null;
@@ -153,7 +170,8 @@ public class MQAdminExtImpl implements MQAdminExt {
response = remotingClient.invokeSync(addr, request, 3000);
}
catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
@@ -161,7 +179,7 @@ public class MQAdminExtImpl implements MQAdminExt {
return topicConfigSerializeWrapper.getTopicConfigTable().get(topic);
}
default:
throw Throwables.propagate(new MQBrokerException(response.getCode(), response.getRemark()));
throw new MQBrokerException(response.getCode(), response.getRemark());
}
}
@@ -235,6 +253,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 +296,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
@@ -364,14 +389,14 @@ public class MQAdminExtImpl implements MQAdminExt {
}
@Override
public void createTopic(String key, String newTopic, int queueNum) throws MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createTopic(key, newTopic, queueNum);
public void createTopic(String key, String newTopic, int queueNum, Map<String, String> attributes) throws MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createTopic(key, newTopic, queueNum, attributes);
}
@Override
public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag)
public void createTopic(String key, String newTopic, int queueNum, int topicSysFlag, Map<String, String> attributes)
throws MQClientException {
MQAdminInstance.threadLocalMQAdminExt().createTopic(key, newTopic, queueNum, topicSysFlag);
MQAdminInstance.threadLocalMQAdminExt().createTopic(key, newTopic, queueNum, topicSysFlag, attributes);
}
@Override
@@ -439,16 +464,19 @@ public class MQAdminExtImpl implements MQAdminExt {
}
catch (Exception e) {
}
MQAdminImpl mqAdminImpl = MQAdminInstance.threadLocalMqClientInstance().getMQAdminImpl();
QueryResult qr = Reflect.on(mqAdminImpl).call("queryMessage", topic, msgId, 32,
MessageClientIDSetter.getNearlyTimeFromID(msgId).getTime() - 1000 * 60 * 60 * 13L, Long.MAX_VALUE, true).get();
if (qr != null && qr.getMessageList() != null && qr.getMessageList().size() > 0) {
return qr.getMessageList().get(0);
Set<String> clusterList = MQAdminInstance.threadLocalMQAdminExt().getTopicClusterList(topic);
if (clusterList == null || clusterList.isEmpty()) {
return MQAdminInstance.threadLocalMQAdminExt().queryMessage("", topic, msgId);
}
for (String name : clusterList) {
MessageExt messageExt = MQAdminInstance.threadLocalMQAdminExt().queryMessage(name, topic, msgId);
if (messageExt != null) {
return messageExt;
}
}
else {
return null;
}
}
@Override
public ConsumeMessageDirectlyResult consumeMessageDirectly(String consumerGroup, String clientId, String topic,
@@ -511,9 +539,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
@@ -548,4 +588,256 @@ public class MQAdminExtImpl implements MQAdminExt {
String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
return false;
}
@Override
public void addBrokerToContainer(String brokerContainerAddr, String brokerConfig) throws InterruptedException,
MQBrokerException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'addBrokerToContainer'");
}
@Override
public void removeBrokerFromContainer(String brokerContainerAddr, String clusterName, String brokerName,
long brokerId) throws InterruptedException, MQBrokerException, RemotingTimeoutException,
RemotingSendRequestException, RemotingConnectException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'removeBrokerFromContainer'");
}
@Override
public void updateGlobalWhiteAddrConfig(String addr, String globalWhiteAddrs, String aclFileFullPath)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'updateGlobalWhiteAddrConfig'");
}
@Override
public TopicStatsTable examineTopicStats(String brokerAddr, String topic)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'examineTopicStats'");
}
@Override
public AdminToolResult<TopicStatsTable> examineTopicStatsConcurrent(String topic) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'examineTopicStatsConcurrent'");
}
@Override
public ConsumeStats examineConsumeStats(String brokerAddr, String consumerGroup, String topicName,
long timeoutMillis) throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException,
RemotingConnectException, MQBrokerException {
// TODO Auto-generated method stub
return MQAdminInstance.threadLocalMQAdminExt().examineConsumeStats(brokerAddr, consumerGroup, topicName, timeoutMillis);
}
@Override
public AdminToolResult<ConsumeStats> examineConsumeStatsConcurrent(String consumerGroup, String topic) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'examineConsumeStatsConcurrent'");
}
@Override
public ConsumerConnection examineConsumerConnectionInfo(String consumerGroup, String brokerAddr)
throws InterruptedException, MQBrokerException, RemotingException, MQClientException {
return MQAdminInstance.threadLocalMQAdminExt().examineConsumerConnectionInfo(consumerGroup, brokerAddr);
}
@Override
public ProducerTableInfo getAllProducerInfo(String brokerAddr)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getAllProducerInfo'");
}
@Override
public void deleteTopic(String topicName, String clusterName)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'deleteTopic'");
}
@Override
public AdminToolResult<BrokerOperatorResult> deleteTopicInBrokerConcurrent(Set<String> addrs, String topic) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'deleteTopicInBrokerConcurrent'");
}
@Override
public void deleteTopicInNameServer(Set<String> addrs, String clusterName, String topic)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'deleteTopicInNameServer'");
}
@Override
public AdminToolResult<BrokerOperatorResult> resetOffsetNewConcurrent(String group, String topic, long timestamp) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'resetOffsetNewConcurrent'");
}
@Override
public TopicList queryTopicsByConsumer(String group)
throws InterruptedException, MQBrokerException, RemotingException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'queryTopicsByConsumer'");
}
@Override
public AdminToolResult<TopicList> queryTopicsByConsumerConcurrent(String group) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'queryTopicsByConsumerConcurrent'");
}
@Override
public SubscriptionData querySubscription(String group, String topic)
throws InterruptedException, MQBrokerException, RemotingException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'querySubscription'");
}
@Override
public AdminToolResult<List<QueueTimeSpan>> queryConsumeTimeSpanConcurrent(String topic, String group) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'queryConsumeTimeSpanConcurrent'");
}
@Override
public boolean deleteExpiredCommitLog(String cluster) throws RemotingConnectException, RemotingSendRequestException,
RemotingTimeoutException, MQClientException, InterruptedException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'deleteExpiredCommitLog'");
}
@Override
public boolean deleteExpiredCommitLogByAddr(String addr) throws RemotingConnectException,
RemotingSendRequestException, RemotingTimeoutException, MQClientException, InterruptedException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'deleteExpiredCommitLogByAddr'");
}
@Override
public ConsumerRunningInfo getConsumerRunningInfo(String consumerGroup, String clientId, boolean jstack,
boolean metrics) throws RemotingException, MQClientException, InterruptedException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getConsumerRunningInfo'");
}
@Override
public List<MessageTrack> messageTrackDetailConcurrent(MessageExt msg)
throws RemotingException, MQClientException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'messageTrackDetailConcurrent'");
}
@Override
public void setMessageRequestMode(String brokerAddr, String topic, String consumerGroup, MessageRequestMode mode,
int popWorkGroupSize, long timeoutMillis) throws InterruptedException, RemotingTimeoutException,
RemotingSendRequestException, RemotingConnectException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'setMessageRequestMode'");
}
@Override
public long searchOffset(String brokerAddr, String topicName, int queueId, long timestamp, long timeoutMillis)
throws RemotingException, MQBrokerException, InterruptedException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'searchOffset'");
}
@Override
public void resetOffsetByQueueId(String brokerAddr, String consumerGroup, String topicName, int queueId,
long resetOffset) throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'resetOffsetByQueueId'");
}
@Override
public void createStaticTopic(String addr, String defaultTopic, TopicConfig topicConfig,
TopicQueueMappingDetail mappingDetail, boolean force)
throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'createStaticTopic'");
}
@Override
public GroupForbidden updateAndGetGroupReadForbidden(String brokerAddr, String groupName, String topicName,
Boolean readable) throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'updateAndGetGroupReadForbidden'");
}
@Override
public MessageExt queryMessage(String clusterName, String topic, String msgId)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'queryMessage'");
}
@Override
public HARuntimeInfo getBrokerHAStatus(String brokerAddr) throws RemotingConnectException,
RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getBrokerHAStatus'");
}
@Override
public BrokerReplicasInfo getInSyncStateData(String controllerAddress, List<String> brokers)
throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getInSyncStateData'");
}
@Override
public EpochEntryCache getBrokerEpochCache(String brokerAddr)
throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getBrokerEpochCache'");
}
@Override
public GetMetaDataResponseHeader getControllerMetaData(String controllerAddr)
throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getControllerMetaData'");
}
@Override
public void resetMasterFlushOffset(String brokerAddr, long masterFlushOffset) throws InterruptedException,
MQBrokerException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'resetMasterFlushOffset'");
}
@Override
public Map<String, Properties> getControllerConfig(List<String> controllerServers)
throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException,
RemotingConnectException, MQClientException, UnsupportedEncodingException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getControllerConfig'");
}
@Override
public void updateControllerConfig(Properties properties, List<String> controllers)
throws InterruptedException, RemotingConnectException, UnsupportedEncodingException,
RemotingSendRequestException, RemotingTimeoutException, MQClientException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'updateControllerConfig'");
}
@Override
public ElectMasterResponseHeader electMaster(String controllerAddr, String clusterName, String brokerName,
String brokerAddr) throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'electMaster'");
}
@Override
public void cleanControllerBrokerData(String controllerAddr, String clusterName, String brokerName,
String brokerAddr, boolean isCleanLivingBroker)
throws RemotingException, InterruptedException, MQBrokerException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'cleanControllerBrokerData'");
}
}

View File

@@ -0,0 +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.
*/
package org.apache.rocketmq.dashboard.service.client;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.remoting.exception.RemotingConnectException;
import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
public interface ProxyAdmin {
ConsumerConnection examineConsumerConnectionInfo(String addr, String consumerGroup) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException;
}

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.service.client;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.remoting.RemotingClient;
import org.apache.rocketmq.remoting.exception.RemotingConnectException;
import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.header.GetConsumerConnectionListRequestHeader;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static org.apache.rocketmq.remoting.protocol.RequestCode.GET_CONSUMER_CONNECTION_LIST;
@Slf4j
@Service
public class ProxyAdminImpl implements ProxyAdmin {
@Autowired
private GenericObjectPool<MQAdminExt> mqAdminExtPool;
@Override
public ConsumerConnection examineConsumerConnectionInfo(String addr, String consumerGroup) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
try {
MQAdminInstance.createMQAdmin(mqAdminExtPool);
RemotingClient remotingClient = MQAdminInstance.threadLocalRemotingClient();
GetConsumerConnectionListRequestHeader requestHeader = new GetConsumerConnectionListRequestHeader();
requestHeader.setConsumerGroup(consumerGroup);
RemotingCommand request = RemotingCommand.createRequestCommand(GET_CONSUMER_CONNECTION_LIST, requestHeader);
RemotingCommand response = remotingClient.invokeSync(addr, request, 3000);
switch (response.getCode()) {
case 0:
return ConsumerConnection.decode(response.getBody(), ConsumerConnection.class);
default:
throw new MQBrokerException(response.getCode(), response.getRemark(), addr);
}
} finally {
MQAdminInstance.returnMQAdmin(mqAdminExtPool);
}
}
}

View File

@@ -0,0 +1,370 @@
/*
* 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.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.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);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@Override
public void syncData(PlainAccessConfig config) {
try {
for (String addr : getBrokerAddrs()) {
mqAdminExt.createAndUpdatePlainAccessConfig(addr, config);
}
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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

@@ -17,9 +17,10 @@
package org.apache.rocketmq.dashboard.service.impl;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.attribute.TopicMessageType;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.KVTable;
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.apache.rocketmq.dashboard.service.ClusterService;
import org.apache.rocketmq.dashboard.util.JsonUtil;
@@ -30,8 +31,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
@Service
public class ClusterServiceImpl implements ClusterService {
@@ -56,10 +59,14 @@ public class ClusterServiceImpl implements ClusterService {
}
resultMap.put("clusterInfo", clusterInfo);
resultMap.put("brokerServer", brokerServer);
// add messageType
resultMap.put("messageTypes", Arrays.stream(TopicMessageType.values()).sorted()
.collect(Collectors.toMap(TopicMessageType::getValue, messageType ->String.format("MESSAGE_TYPE_%s",messageType.getValue()))));
return resultMap;
}
catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
}
@@ -70,7 +77,8 @@ public class ClusterServiceImpl implements ClusterService {
return mqAdminExt.getBrokerConfig(brokerAddr);
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}

View File

@@ -23,29 +23,44 @@ 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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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.dashboard.service.client.ProxyAdmin;
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
import org.apache.rocketmq.remoting.protocol.admin.RollbackStats;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.Connection;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
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.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.Connection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
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;
@@ -57,16 +72,46 @@ 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
protected ProxyAdmin proxyAdmin;
@Resource
private RMQConfigure configure;
private static final Set<String> SYSTEM_GROUP_SET = new HashSet<>();
private ExecutorService executorService;
@Override
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);
@@ -76,25 +121,51 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_PERMISSION_GROUP);
SYSTEM_GROUP_SET.add(MixAll.CID_ONSAPI_OWNER_GROUP);
SYSTEM_GROUP_SET.add(MixAll.CID_SYS_RMQ_TRANS);
SYSTEM_GROUP_SET.add("CID_DefaultHeartBeatSyncerTopic");
}
@Override
public List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup) {
Set<String> consumerGroupSet = Sets.newHashSet();
public List<GroupConsumeInfo> queryGroupList(boolean skipSysGroup, String address) {
HashMap<String, List<String>> consumerGroupMap = Maps.newHashMap();
try {
ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
for (BrokerData brokerData : clusterInfo.getBrokerAddrTable().values()) {
SubscriptionGroupWrapper subscriptionGroupWrapper = mqAdminExt.getAllSubscriptionGroup(brokerData.selectBrokerAddr(), 3000L);
consumerGroupSet.addAll(subscriptionGroupWrapper.getSubscriptionGroupTable().keySet());
for (String groupName : subscriptionGroupWrapper.getSubscriptionGroupTable().keySet()) {
if (!consumerGroupMap.containsKey(groupName)) {
consumerGroupMap.putIfAbsent(groupName, new ArrayList<>());
}
List<String> addresses = consumerGroupMap.get(groupName);
addresses.add(brokerData.selectBrokerAddr());
consumerGroupMap.put(groupName, addresses);
}
}
catch (Exception err) {
throw Throwables.propagate(err);
} catch (Exception err) {
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
List<GroupConsumeInfo> groupConsumeInfoList = Lists.newArrayList();
for (String consumerGroup : consumerGroupSet) {
groupConsumeInfoList.add(queryGroup(consumerGroup));
List<GroupConsumeInfo> groupConsumeInfoList = Collections.synchronizedList(Lists.newArrayList());
CountDownLatch countDownLatch = new CountDownLatch(consumerGroupMap.size());
for (Map.Entry<String, List<String>> entry : consumerGroupMap.entrySet()) {
String consumerGroup = entry.getKey();
executorService.submit(() -> {
try {
GroupConsumeInfo consumeInfo = queryGroup(consumerGroup, address);
consumeInfo.setAddress(entry.getValue());
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())) {
@@ -108,7 +179,7 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
public GroupConsumeInfo queryGroup(String consumerGroup) {
public GroupConsumeInfo queryGroup(String consumerGroup, String address) {
GroupConsumeInfo groupConsumeInfo = new GroupConsumeInfo();
try {
ConsumeStats consumeStats = null;
@@ -120,14 +191,28 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
ConsumerConnection consumerConnection = null;
boolean isFifoType = examineSubscriptionGroupConfig(consumerGroup)
.stream().map(ConsumerConfigInfo::getSubscriptionGroupConfig)
.allMatch(SubscriptionGroupConfig::isConsumeMessageOrderly);
try {
if (StringUtils.isNotEmpty(address)) {
consumerConnection = proxyAdmin.examineConsumerConnectionInfo(address, consumerGroup);
} else {
consumerConnection = mqAdminExt.examineConsumerConnectionInfo(consumerGroup);
}
catch (Exception e) {
} catch (Exception e) {
logger.warn("examineConsumeStats exception to consumerGroup {}, response [{}]", consumerGroup, e.getMessage());
}
groupConsumeInfo.setGroup(consumerGroup);
if (SYSTEM_GROUP_SET.contains(consumerGroup)) {
groupConsumeInfo.setSubGroupType("SYSTEM");
} else if (isFifoType) {
groupConsumeInfo.setSubGroupType("FIFO");
} else {
groupConsumeInfo.setSubGroupType("NORMAL");
}
if (consumeStats != null) {
groupConsumeInfo.setConsumeTps((int)consumeStats.getConsumeTps());
@@ -149,8 +234,18 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
@Override
public List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName) {
return queryConsumeStatsList(null, groupName);
public List<TopicConsumerInfo> queryConsumeStatsListByGroupName(String groupName, String address) {
ConsumeStats consumeStats;
String topic = null;
try {
String[] addresses = address.split(",");
String addr = addresses[0];
consumeStats = mqAdminExt.examineConsumeStats(addr, groupName, null, 3000);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
return toTopicConsumerInfoList(topic, consumeStats, groupName);
}
@Override
@@ -160,8 +255,13 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
consumeStats = mqAdminExt.examineConsumeStats(groupName, topic);
}
catch (Exception e) {
throw propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
return toTopicConsumerInfoList(topic, consumeStats, groupName);
}
private List<TopicConsumerInfo> toTopicConsumerInfoList(String topic, ConsumeStats consumeStats, String groupName) {
List<MessageQueue> mqList = Lists.newArrayList(Iterables.filter(consumeStats.getOffsetTable().keySet(), new Predicate<MessageQueue>() {
@Override
public boolean apply(MessageQueue o) {
@@ -220,7 +320,8 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
return group2ConsumerInfoMap;
}
catch (Exception e) {
throw propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -283,26 +384,50 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
}
catch (Exception e) {
throw propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
return consumerConfigInfoList;
}
@Override
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) {
throw propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
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 {
@@ -313,7 +438,8 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
}
catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
return true;
}
@@ -328,19 +454,22 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
}
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
return brokerNameSet;
}
@Override
public ConsumerConnection getConsumerConnection(String consumerGroup) {
public ConsumerConnection getConsumerConnection(String consumerGroup, String address) {
try {
return mqAdminExt.examineConsumerConnectionInfo(consumerGroup);
}
catch (Exception e) {
throw Throwables.propagate(e);
String[] addresses = address.split(",");
String addr = addresses[0];
return mqAdminExt.examineConsumerConnectionInfo(consumerGroup, addr);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -350,7 +479,8 @@ public class ConsumerServiceImpl extends AbstractCommonService implements Consum
return mqAdminExt.getConsumerRunningInfo(consumerGroup, clientId, jstack);
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}

View File

@@ -107,7 +107,8 @@ public class DashboardCollectServiceImpl implements DashboardCollectService {
strings = Files.readLines(file, Charsets.UTF_8);
}
catch (IOException e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
StringBuffer sb = new StringBuffer();
for (String string : strings) {

View File

@@ -19,12 +19,16 @@ 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.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.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;
@@ -58,11 +62,26 @@ public class DlqMessageServiceImpl implements DlqMessageService {
&& e.getResponseCode() == ResponseCode.TOPIC_NOT_EXIST) {
return new MessagePage(new PageImpl<>(messageViews, page, 0), query.getTaskId());
} else {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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,14 +31,15 @@ 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;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.body.Connection;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.Connection;
import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.exception.ServiceException;
import org.apache.rocketmq.dashboard.model.QueueOffsetInfo;
@@ -111,7 +112,11 @@ public class MessageServiceImpl implements MessageService {
}
});
} catch (Exception err) {
throw Throwables.propagate(err);
if (err instanceof MQClientException) {
throw new ServiceException(-1, ((MQClientException) err).getErrorMessage());
}
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
}
@@ -181,7 +186,8 @@ public class MessageServiceImpl implements MessageService {
});
return messageViewList;
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
consumer.shutdown();
}
@@ -205,7 +211,8 @@ public class MessageServiceImpl implements MessageService {
try {
return mqAdminExt.consumeMessageDirectly(consumerGroup, clientId, topic, msgId);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -219,7 +226,8 @@ public class MessageServiceImpl implements MessageService {
return mqAdminExt.consumeMessageDirectly(consumerGroup, connection.getClientId(), topic, msgId);
}
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
throw new IllegalStateException("NO CONSUMER");
@@ -268,7 +276,7 @@ public class MessageServiceImpl implements MessageService {
int idx = 0;
for (MessageQueue messageQueue : messageQueues) {
Long minOffset = consumer.searchOffset(messageQueue, query.getBegin());
Long maxOffset = consumer.searchOffset(messageQueue, query.getEnd()) + 1;
Long maxOffset = consumer.searchOffset(messageQueue, query.getEnd());
queueOffsetInfos.add(new QueueOffsetInfo(idx++, minOffset, maxOffset, minOffset, minOffset, messageQueue));
}
@@ -325,7 +333,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;
@@ -384,7 +392,8 @@ public class MessageServiceImpl implements MessageService {
PageImpl<MessageView> page = new PageImpl<>(messageViews, query.page(), total);
return new MessagePageTask(page, queueOffsetInfos);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
consumer.shutdown();
}
@@ -451,7 +460,8 @@ public class MessageServiceImpl implements MessageService {
}
return new PageImpl<>(messageViews, query.page(), total);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
consumer.shutdown();
}

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;
@@ -35,6 +33,7 @@ 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;
@@ -82,7 +81,7 @@ 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));
}
}

View File

@@ -82,7 +82,8 @@ public class MonitorServiceImpl implements MonitorService {
MixAll.string2File(dataStr, path);
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}

View File

@@ -16,7 +16,6 @@
*/
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;
@@ -46,7 +45,8 @@ public class OpsServiceImpl extends AbstractCommonService implements OpsService
@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;
@@ -73,7 +73,8 @@ 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;
@@ -85,4 +86,13 @@ public class OpsServiceImpl extends AbstractCommonService implements OpsService
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

@@ -19,7 +19,7 @@ package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Throwables;
import javax.annotation.Resource;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
import org.apache.rocketmq.remoting.protocol.body.ProducerConnection;
import org.apache.rocketmq.dashboard.service.ProducerService;
import org.apache.rocketmq.tools.admin.MQAdminExt;
import org.springframework.stereotype.Service;
@@ -35,7 +35,8 @@ public class ProducerServiceImpl implements ProducerService {
return mqAdminExt.examineProducerConnectionInfo(producerGroup, topic);
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}

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.
*/
package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.service.ProxyService;
import org.apache.rocketmq.dashboard.service.client.ProxyAdmin;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class ProxyServiceImpl implements ProxyService {
@Resource
protected ProxyAdmin proxyAdmin;
@Resource
private RMQConfigure configure;
@Override
public void addProxyAddrList(String proxyAddr) {
List<String> proxyAddrs = configure.getProxyAddrs();
if (proxyAddrs != null && !proxyAddrs.contains(proxyAddr)) {
proxyAddrs.add(proxyAddr);
}
configure.setProxyAddrs(proxyAddrs);
}
@Override
public void updateProxyAddrList(String proxyAddr) {
configure.setProxyAddr(proxyAddr);
}
@Override
public Map<String, Object> getProxyHomePage() {
Map<String, Object> homePageInfoMap = Maps.newHashMap();
homePageInfoMap.put("currentProxyAddr", configure.getProxyAddr());
homePageInfoMap.put("proxyAddrList", configure.getProxyAddrs());
return homePageInfoMap;
}
}

View File

@@ -18,43 +18,57 @@
package org.apache.rocketmq.dashboard.service.impl;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
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;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.trace.TraceContext;
import org.apache.rocketmq.client.trace.TraceDispatcher;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.admin.TopicStatsTable;
import org.apache.rocketmq.common.attribute.TopicMessageType;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
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.message.MessageExt;
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;
import org.apache.rocketmq.dashboard.model.request.TopicTypeList;
import org.apache.rocketmq.dashboard.model.request.TopicTypeMeta;
import org.apache.rocketmq.dashboard.service.AbstractCommonService;
import org.apache.rocketmq.dashboard.service.TopicService;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
import org.apache.rocketmq.tools.command.CommandUtil;
import org.joor.Reflect;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.apache.rocketmq.common.TopicAttributes.TOPIC_MESSAGE_TYPE_ATTRIBUTE;
@Service
public class TopicServiceImpl extends AbstractCommonService implements TopicService {
@@ -84,7 +98,43 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
allTopics.getTopicList().addAll(topics);
return allTopics;
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@Override
public TopicTypeList examineAllTopicType() {
ArrayList<TopicTypeMeta> topicTypes = new ArrayList<>();
ArrayList<String> names = new ArrayList<>();
ArrayList<String> messageTypes = new ArrayList<>();
TopicList topicList = fetchAllTopicList(false, false);
checkTopicType(topicList, topicTypes);
topicTypes.sort((t1, t2) -> t1.getTopicName().compareTo(t2.getTopicName()));
for (TopicTypeMeta topicTypeMeta : topicTypes) {
names.add(topicTypeMeta.getTopicName());
messageTypes.add(topicTypeMeta.getMessageType());
}
return new TopicTypeList(names, messageTypes);
}
private void checkTopicType(TopicList topicList, ArrayList<TopicTypeMeta> topicTypes) {
for (String topicName : topicList.getTopicList()) {
TopicTypeMeta topicType = new TopicTypeMeta();
topicType.setTopicName(topicName);
if (topicName.startsWith("%R")) {
topicType.setMessageType("RETRY");
} else if (topicName.startsWith("%D")) {
topicType.setMessageType("DELAY");
} else if (topicName.startsWith("%S")) {
topicType.setMessageType("SYSTEM");
} else {
List<TopicConfigInfo> topicConfigInfos = examineTopicConfig(topicName);
if (!CollectionUtils.isEmpty(topicConfigInfos)) {
topicType.setMessageType(topicConfigInfos.get(0).getMessageType());
}
}
topicTypes.add(topicType);
}
}
@@ -93,7 +143,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
try {
return mqAdminExt.examineTopicStats(topic);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -102,7 +153,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
try {
return mqAdminExt.examineTopicRouteInfo(topic);
} catch (Exception ex) {
throw Throwables.propagate(ex);
Throwables.throwIfUnchecked(ex);
throw new RuntimeException(ex);
}
}
@@ -111,7 +163,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
try {
return mqAdminExt.queryTopicConsumeByWho(topic);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -119,6 +172,11 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
public void createOrUpdate(TopicConfigInfo topicCreateOrUpdateRequest) {
TopicConfig topicConfig = new TopicConfig();
BeanUtils.copyProperties(topicCreateOrUpdateRequest, topicConfig);
String messageType = topicCreateOrUpdateRequest.getMessageType();
if (StringUtils.isBlank(messageType)) {
messageType = TopicMessageType.NORMAL.name();
}
topicConfig.setAttributes(ImmutableMap.of("+".concat(TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName()), messageType));
try {
ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
for (String brokerName : changeToBrokerNameSet(clusterInfo.getClusterAddrTable(),
@@ -126,7 +184,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
mqAdminExt.createAndUpdateTopicConfig(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), topicConfig);
}
} catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
}
@@ -135,10 +194,11 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
ClusterInfo clusterInfo = null;
try {
clusterInfo = mqAdminExt.examineBrokerClusterInfo();
} catch (Exception e) {
throw Throwables.propagate(e);
}
return mqAdminExt.examineTopicConfig(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr(), topic);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@Override
@@ -150,6 +210,11 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
TopicConfig topicConfig = examineTopicConfig(topic, brokerData.getBrokerName());
BeanUtils.copyProperties(topicConfig, topicConfigInfo);
topicConfigInfo.setBrokerNameList(Lists.newArrayList(brokerData.getBrokerName()));
String messageType = topicConfig.getAttributes().get(TOPIC_MESSAGE_TYPE_ATTRIBUTE.getName());
if (StringUtils.isBlank(messageType)) {
messageType = TopicMessageType.UNSPECIFIED.name();
}
topicConfigInfo.setMessageType(messageType);
topicConfigInfoList.add(topicConfigInfo);
}
return topicConfigInfoList;
@@ -170,7 +235,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
}
mqAdminExt.deleteTopicInNameServer(nameServerSet, topic);
} catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
return true;
}
@@ -181,7 +247,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
try {
clusterInfo = mqAdminExt.examineBrokerClusterInfo();
} catch (Exception err) {
throw Throwables.propagate(err);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
for (String clusterName : clusterInfo.getClusterAddrTable().keySet()) {
deleteTopic(topic, clusterName);
@@ -197,11 +264,13 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
try {
clusterInfo = mqAdminExt.examineBrokerClusterInfo();
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
mqAdminExt.deleteTopicInBroker(Sets.newHashSet(clusterInfo.getBrokerAddrTable().get(brokerName).selectBrokerAddr()), topic);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
return true;
}
@@ -216,6 +285,12 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
return defaultMQProducer;
}
public TransactionMQProducer buildTransactionMQProducer(String producerGroup, RPCHook rpcHook, boolean traceEnabled) {
TransactionMQProducer defaultMQProducer = new TransactionMQProducer(null, producerGroup, rpcHook, traceEnabled, TopicValidator.RMQ_SYS_TRACE_TOPIC);
defaultMQProducer.setUseTLS(configure.isUseTLS());
return defaultMQProducer;
}
private TopicList getSystemTopicList() {
RPCHook rpcHook = null;
boolean isEnableAcl = !StringUtils.isEmpty(configure.getAccessKey()) && !StringUtils.isEmpty(configure.getSecretKey());
@@ -230,7 +305,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
producer.start();
return producer.getDefaultMQProducerImpl().getmQClientFactory().getMQClientAPIImpl().getSystemTopicList(20000L);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
producer.shutdown();
}
@@ -238,7 +314,8 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
@Override
public SendResult sendTopicMessageRequest(SendTopicMessageRequest sendTopicMessageRequest) {
DefaultMQProducer producer = null;
List<TopicConfigInfo> topicConfigInfos = examineTopicConfig(sendTopicMessageRequest.getTopic());
String messageType = topicConfigInfos.get(0).getMessageType();
AclClientRPCHook rpcHook = null;
if (configure.isACLEnabled()) {
rpcHook = new AclClientRPCHook(new SessionCredentials(
@@ -246,6 +323,32 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
configure.getSecretKey()
));
}
if (TopicMessageType.TRANSACTION.getValue().equals(messageType)) {
// transaction message
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = buildTransactionMQProducer(MixAll.SELF_TEST_PRODUCER_GROUP, rpcHook, sendTopicMessageRequest.isTraceEnabled());
producer.setInstanceName(String.valueOf(System.currentTimeMillis()));
producer.setNamesrvAddr(configure.getNamesrvAddr());
producer.setTransactionListener(transactionListener);
try {
producer.start();
Message msg = new Message(sendTopicMessageRequest.getTopic(),
sendTopicMessageRequest.getTag(),
sendTopicMessageRequest.getKey(),
sendTopicMessageRequest.getMessageBody().getBytes()
);
return producer.sendMessageInTransaction(msg, null);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
waitSendTraceFinish(producer, sendTopicMessageRequest.isTraceEnabled());
producer.shutdown();
}
} else {
// no transaction message
DefaultMQProducer producer = null;
producer = buildDefaultMQProducer(MixAll.SELF_TEST_PRODUCER_GROUP, rpcHook, sendTopicMessageRequest.isTraceEnabled());
producer.setInstanceName(String.valueOf(System.currentTimeMillis()));
producer.setNamesrvAddr(configure.getNamesrvAddr());
@@ -258,13 +361,16 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
);
return producer.send(msg);
} catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
waitSendTraceFinish(producer, sendTopicMessageRequest.isTraceEnabled());
producer.shutdown();
}
}
}
private void waitSendTraceFinish(DefaultMQProducer producer, boolean traceEnabled) {
if (!traceEnabled) {
return;
@@ -284,4 +390,20 @@ public class TopicServiceImpl extends AbstractCommonService implements TopicServ
} catch (Exception ignore) {
}
}
static class TransactionListenerImpl implements TransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
return LocalTransactionState.COMMIT_MESSAGE;
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.stats.Stats;
import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData;
import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.apache.rocketmq.remoting.protocol.route.BrokerData;
import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
import org.apache.rocketmq.dashboard.service.DashboardCollectService;
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, Stats.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, Stats.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) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(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,17 +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.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;
@@ -42,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.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.KVTable;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.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;
@@ -67,14 +61,14 @@ public class DashboardCollectTask {
private final static Logger log = LoggerFactory.getLogger(DashboardCollectTask.class);
@Resource
private ExecutorService collectExecutor;
@Scheduled(cron = "30 0/1 * * * ?")
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();
@@ -85,80 +79,13 @@ 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);
Throwables.throwIfUnchecked(err);
throw new RuntimeException(err);
}
}
@@ -202,7 +129,8 @@ public class DashboardCollectTask {
log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
}
catch (Exception e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -218,10 +146,12 @@ public class DashboardCollectTask {
Thread.sleep(1000);
}
catch (InterruptedException e1) {
throw Throwables.propagate(e1);
Throwables.throwIfUnchecked(e1);
throw new RuntimeException(e1);
}
fetchBrokerRuntimeStats(brokerAddr, retryTime - 1);
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -271,7 +201,8 @@ public class DashboardCollectTask {
}
catch (IOException e) {
throw Throwables.propagate(e);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
@@ -324,10 +255,11 @@ public class DashboardCollectTask {
private void addSystemTopic() throws Exception {
ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
HashMap<String, Set<String>> clusterTable = clusterInfo.getClusterAddrTable();
Map<String, Set<String>> clusterTable = clusterInfo.getClusterAddrTable();
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

@@ -40,7 +40,7 @@ public class MonitorTask {
// @Scheduled(cron = "* * * * * ?")
public void scanProblemConsumeGroup() {
for (Map.Entry<String, ConsumerMonitorConfig> configEntry : monitorService.queryConsumerMonitorConfig().entrySet()) {
GroupConsumeInfo consumeInfo = consumerService.queryGroup(configEntry.getKey());
GroupConsumeInfo consumeInfo = consumerService.queryGroup(configEntry.getKey(), null);
if (consumeInfo.getCount() < configEntry.getValue().getMinCount() || consumeInfo.getDiffTotal() > configEntry.getValue().getMaxDiffTotal()) {
logger.info("op=look consumeInfo {}", JsonUtil.obj2String(consumeInfo)); // notify the alert system
}

View File

@@ -1,54 +0,0 @@
#
# 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.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=
#timeout for mqadminExt, default 5000ms
rocketmq.config.timeoutMillis=
#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,74 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
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
# - 10.151.47.32:9876;10.151.47.33:9876;10.151.47.34:9876
# - 10.151.47.30: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
proxyAddr: 127.0.0.1:8080
proxyAddrs:
- 127.0.0.1:8080
# set the accessKey and secretKey if you used acl
# accessKey: rocketmq2
# secretKey: 12345678
threadpool:
config:
coreSize: 10
maxSize: 10
keepAliveTime: 3000
queueSize: 5000

View File

@@ -19,16 +19,16 @@
<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">
@@ -37,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

@@ -23,13 +23,18 @@
rolePerms:
ordinary:
- /rocketmq/nsaddr
- /ops/*
- /dashboard/**
- /rocketmq/*.query
- /ops/*.query
- /dashboard/*.query
- /topic/*.query
- /topic/sendTopicMessage.do
- /producer/*.query
- /message/*
- /messageTrace/*
- /monitor/*
- /message/*.query
- /messageTrace/*.query
- /monitor/*.query
- /consumer/*.query
- /cluster/*.query
- /dlqMessage/*.query
- /dlqMessage/exportDlqMessage.do
- /dlqMessage/batchResendDlqMessage.do
- /acl/*.query

View File

@@ -104,6 +104,7 @@
<script type="text/javascript" src="src/tools/tools.js?v=201703171710"></script>
<script type="text/javascript" src="src/cluster.js?timestamp=4"></script>
<script type="text/javascript" src="src/topic.js"></script>
<script type="text/javascript" src="src/proxy.js"></script>
<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>
@@ -113,5 +114,6 @@
<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){
@@ -204,6 +213,12 @@ app.config(['$routeProvider', '$httpProvider','$cookiesProvider','getDictNamePro
}).when('/ops', {
templateUrl: 'view/pages/ops.html',
controller:'opsController'
}).when('/proxy', {
templateUrl: 'view/pages/proxy.html',
controller:'proxyController'
}).when('/acl', {
templateUrl: 'view/pages/acl.html',
controller: 'aclController'
}).when('/404', {
templateUrl: 'view/pages/404.html'
}).otherwise('/404');

View File

@@ -45,6 +45,7 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
$scope.filterNormal = true;
$scope.filterSystem = false;
$scope.filterFIFO = false;
$scope.doSort = function () {// todo how to change this fe's code ? (it's dirty)
if ($scope.sortKey == 'diffTotal') {
@@ -75,7 +76,11 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
$http({
method: "GET",
url: "consumer/groupList.query"
url: "consumer/groupList.query",
params: {
skipSysGroup: false,
address: localStorage.getItem('isV5') ? localStorage.getItem('proxyAddr') : null
}
}).success(function (resp) {
if (resp.status == 0) {
$scope.allConsumerGrouopList = resp.data;
@@ -135,16 +140,28 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
$scope.filterList(1);
});
$scope.filterByType = function (str) {
$scope.$watch('filterFIFO', function () {
$scope.filterList(1);
});
$scope.filterByType = function (str, type,version) {
if ($scope.filterSystem) {
if (str.startsWith("%S")) {
if (type === "SYSTEM") {
return true
}
}
if ($scope.filterNormal) {
if (str.startsWith("%") == false) {
if (type === "NORMAL") {
return true
}
if(!version && type === "FIFO"){
return true;
}
}
if ($scope.filterFIFO) {
if (type === "FIFO") {
return true;
}
}
return false;
};
@@ -154,7 +171,7 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
var canShowList = [];
$scope.allConsumerGrouopList.forEach(function (element) {
console.log(element)
if ($scope.filterByType(element.group)) {
if ($scope.filterByType(element.group, element.subGroupType, $scope.rmqVersion)) {
if (element.group.toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push(element);
}
@@ -189,6 +206,7 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
subscriptionGroupConfig: {
groupName: "",
consumeEnable: true,
consumeMessageOrderly: false,
consumeFromMinEnable: true,
consumeBroadcastEnable: true,
retryQueueNums: 1,
@@ -211,7 +229,7 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
// Refresh topic list
$scope.refreshConsumerData();
},
template: 'consumerModifyDialog',
template: $scope.rmqVersion ? 'consumerModifyDialogForV5' : 'consumerModifyDialog',
controller: 'consumerModifyDialogController',
data: {
consumerRequestList: request,
@@ -226,11 +244,11 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
}
});
};
$scope.detail = function (consumerGroupName) {
$scope.detail = function (consumerGroupName, address) {
$http({
method: "GET",
url: "consumer/queryTopicByConsumer.query",
params: {consumerGroup: consumerGroupName}
params: {consumerGroup: consumerGroupName, address: address}
}).success(function (resp) {
if (resp.status == 0) {
console.log(resp);
@@ -245,11 +263,11 @@ module.controller('consumerController', ['$scope', 'ngDialog', '$http', 'Notific
});
};
$scope.client = function (consumerGroupName) {
$scope.client = function (consumerGroupName, address) {
$http({
method: "GET",
url: "consumer/consumerConnection.query",
params: {consumerGroup: consumerGroupName}
params: {consumerGroup: consumerGroupName, address: address}
}).success(function (resp) {
if (resp.status == 0) {
console.log(resp);

View File

@@ -15,10 +15,18 @@
* limitations under the License.
*/
app.controller('AppCtrl', ['$scope','$window','$translate','$http','Notification', function ($scope,$window,$translate, $http, Notification) {
$scope.rmqVersion = localStorage.getItem("isV5");
$scope.changeTranslate = function(langKey){
$translate.use(langKey);
}
$scope.changeRMQVersion = function (version) {
$scope.rmqVersion = version === 5;
var v = version === 5;
localStorage.setItem("isV5", v);
}
$scope.logout = function(){
$http({
method: "POST",

View File

@@ -82,6 +82,9 @@ module.controller('dlqMessageController', ['$scope', 'ngDialog', '$http', 'Notif
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;
@@ -180,5 +183,115 @@ module.controller('dlqMessageController', ['$scope', 'ngDialog', '$http', 'Notif
$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

@@ -21,8 +21,8 @@ var en = {
"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",
@@ -54,9 +54,13 @@ var en = {
"RESET_CUS_OFFSET": "Reset Consumer Offset",
"DELETE": "Delete",
"CHANGE_LANG": "ChangeLanguage",
"CHANGE_VERSION": "ChangeVersion",
"BROKER": "Broker",
"NORMAL": "NORMAL",
"RETRY": "RETRY",
"FIFO": "FIFO",
"TRANSACTION": "TRANSACTION",
"UNSPECIFIED": "UNSPECIFIED",
"DLQ": "DLQ",
"QUANTITY":"Quantity",
"TYPE":"Type",
@@ -97,6 +101,7 @@ var en = {
"RESET_OFFSET":"resetOffset",
"CLUSTER_NAME":"clusterName",
"OPS":"OPS",
"PROXY":"Proxy",
"AUTO_REFRESH":"AUTO_REFRESH",
"REFRESH":"REFRESH",
"LOGOUT":"Logout",
@@ -111,5 +116,23 @@ var en = {
"TRACE_TOPIC":"TraceTopic",
"SELECT_TRACE_TOPIC":"selectTraceTopic",
"EXPORT": "export",
"NO_MATCH_RESULT": "no match result"
"NO_MATCH_RESULT": "no match result",
"BATCH_RESEND": "batchReSend",
"BATCH_EXPORT": "batchExport",
"WHITE_LIST":"White List",
"ACCOUNT_INFO":"Account Info",
"IS_ADMIN":"Is Admin",
"DEFAULT_TOPIC_PERM":"Default Topic Permission",
"DEFAULT_GROUP_PERM":"Default Group Permission",
"TOPIC_PERM":"Topic Permission",
"GROUP_PERM":"Group Permission",
"SYNCHRONIZE":"Synchronize Data",
"SHOW":"Show",
"HIDE":"Hide",
"MESSAGE_TYPE":"messageType",
"MESSAGE_TYPE_UNSPECIFIED": "UNSPECIFIED, is NORMAL",
"MESSAGE_TYPE_NORMAL": "NORMAL",
"MESSAGE_TYPE_FIFO": "FIFO",
"MESSAGE_TYPE_DELAY": "DELAY",
"MESSAGE_TYPE_TRANSACTION": "TRANSACTION",
}

View File

@@ -55,9 +55,13 @@ var zh = {
"SKIP_MESSAGE_ACCUMULATE":"跳过堆积",
"DELETE": "删除",
"CHANGE_LANG": "更换语言",
"CHANGE_VERSION": "更换版本",
"BROKER": "Broker",
"NORMAL": "普通",
"RETRY": "重试",
"FIFO": "顺序",
"TRANSACTION": "事务",
"UNSPECIFIED": "未指定",
"DLQ": "死信",
"QUANTITY":"数量",
"TYPE":"类型",
@@ -98,6 +102,7 @@ var zh = {
"RESET_OFFSET":"重置位点",
"CLUSTER_NAME":"集群名",
"OPS":"运维",
"PROXY":"代理",
"AUTO_REFRESH":"自动刷新",
"REFRESH":"刷新",
"LOGOUT":"退出",
@@ -112,5 +117,23 @@ var zh = {
"TRACE_TOPIC":"消息轨迹主题",
"SELECT_TRACE_TOPIC":"选择消息轨迹主题",
"EXPORT": "导出",
"NO_MATCH_RESULT": "没有查到符合条件的结果"
"NO_MATCH_RESULT": "没有查到符合条件的结果",
"BATCH_RESEND": "批量重发",
"BATCH_EXPORT": "批量导出",
"WHITE_LIST":"白名单",
"ACCOUNT_INFO":"账户信息",
"IS_ADMIN":"是否管理员",
"DEFAULT_TOPIC_PERM":"topic默认权限",
"DEFAULT_GROUP_PERM":"消费组默认权限",
"TOPIC_PERM":"topic权限",
"GROUP_PERM":"消费组权限",
"SYNCHRONIZE":"同步",
"SHOW":"显示",
"HIDE":"隐藏",
"MESSAGE_TYPE":"消息类型",
"MESSAGE_TYPE_UNSPECIFIED": "未指定,为普通消息",
"MESSAGE_TYPE_NORMAL": "普通消息",
"MESSAGE_TYPE_FIFO": "顺序消息",
"MESSAGE_TYPE_DELAY": "定时/延时消息",
"MESSAGE_TYPE_TRANSACTION": "事务消息",
}

View File

@@ -58,6 +58,7 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http', 'Notifica
};
$scope.queryMessagePageByTopic = function () {
$("#noMsgTip").css("display", "none");
if ($scope.timepickerEnd < $scope.timepickerBegin) {
Notification.error({message: "endTime is later than beginTime!", delay: 2000});
return
@@ -80,6 +81,9 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http', 'Notifica
if (resp.status === 0) {
console.log(resp);
$scope.messageShowList = resp.data.page.content;
if ($scope.messageShowList.length == 0){
$("#noMsgTip").removeAttr("style");
}
if (resp.data.page.first) {
$scope.paginationConf.currentPage = 1;
}
@@ -206,7 +210,8 @@ module.controller('messageController', ['$scope', 'ngDialog', '$http', 'Notifica
}]);
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;
@@ -258,5 +263,18 @@ module.controller('messageDetailViewDialogController', ['$scope', 'ngDialog', '$
}
});
};
$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

@@ -22,6 +22,7 @@ app.controller('opsController', ['$scope', '$location', '$http', 'Notification',
$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"
@@ -30,6 +31,7 @@ app.controller('opsController', ['$scope', '$location', '$http', 'Notification',
$scope.namesvrAddrList = resp.data.namesvrAddrList;
$scope.useVIPChannel = resp.data.useVIPChannel;
$scope.useTLS = resp.data.useTLS;
$scope.selectedNamesrv = resp.data.currentNamesrv;
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
@@ -43,7 +45,7 @@ app.controller('opsController', ['$scope', '$location', '$http', 'Notification',
$http({
method: "POST",
url: "ops/updateNameSvrAddr.do",
params: {nameSvrAddrList: $scope.namesvrAddrList.join(";")}
params: {nameSvrAddrList: $scope.selectedNamesrv}
}).success(function (resp) {
if (resp.status == 0) {
Notification.info({message: "SUCCESS", delay: 2000});
@@ -52,6 +54,26 @@ app.controller('opsController', ['$scope', '$location', '$http', 'Notification',
}
});
};
$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",

View File

@@ -0,0 +1,97 @@
/*
* 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('proxyController', ['$scope', '$location', '$http', 'Notification', 'remoteApi', 'tools', '$window',
function ($scope, $location, $http, Notification, remoteApi, tools, $window) {
$scope.proxyAddrList = [];
$scope.userRole = $window.sessionStorage.getItem("userrole");
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
$scope.inputReadonly = !$scope.writeOperationEnabled;
$scope.newProxyAddr = "";
$scope.allProxyConfig = {};
$http({
method: "GET",
url: "proxy/homePage.query"
}).success(function (resp) {
if (resp.status == 0) {
$scope.proxyAddrList = resp.data.proxyAddrList;
$scope.selectedProxy = resp.data.currentProxyAddr;
$scope.showProxyDetailConfig($scope.selectedProxy);
localStorage.setItem('proxyAddr',$scope.selectedProxy);
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
$scope.eleChange = function (data) {
$scope.proxyAddrList = data;
}
$scope.showDetailConf = function () {
$(".proxyModal").modal();
}
$scope.showProxyDetailConfig = function (proxyAddr) {
$http({
method: "GET",
url: "proxy/proxyDetailConfig.query",
params: {proxyAddress: proxyAddr}
}).success(function (resp) {
if (resp.status == 0) {
$scope.allProxyConfig = resp.data;
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
$scope.updateProxyAddr = function () {
$http({
method: "POST",
url: "proxy/updateProxyAddr.do",
params: {proxyAddr: $scope.selectedProxy}
}).success(function (resp) {
if (resp.status == 0) {
localStorage.setItem('proxyAddr', $scope.selectedProxy);
Notification.info({message: "SUCCESS", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
$scope.showProxyDetailConfig($scope.selectedProxy);
};
$scope.addProxyAddr = function () {
$http({
method: "POST",
url: "proxy/addProxyAddr.do",
params: {newProxyAddr: $scope.newProxyAddr}
}).success(function (resp) {
if (resp.status == 0) {
if ($scope.proxyAddrList.indexOf($scope.newProxyAddr) == -1) {
$scope.proxyAddrList.push($scope.newProxyAddr);
}
$("#proxyAddr").val("");
$scope.newProxyAddr = "";
Notification.info({message: "SUCCESS", delay: 2000});
} else {
Notification.error({message: resp.errMsg, delay: 2000});
}
});
};
}])

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

@@ -45,10 +45,16 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
}
};
$scope.filterNormal = true
$scope.filterDelay = false
$scope.filterFifo = false
$scope.filterTransaction = false
$scope.filterUnspecified = false
$scope.filterRetry = false
$scope.filterDLQ = false
$scope.filterSystem = false
$scope.allTopicList = [];
$scope.allTopicNameList = [];
$scope.allMessageTypeList = [];
$scope.topicShowList = [];
$scope.userRole = $window.sessionStorage.getItem("userrole");
$scope.writeOperationEnabled = $scope.userRole == null ? true : ($scope.userRole == 1 ? true : false);
@@ -56,13 +62,14 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
$scope.refreshTopicList = function () {
$http({
method: "GET",
url: "topic/list.query"
url: "topic/list.queryTopicType"
}).success(function (resp) {
if (resp.status == 0) {
$scope.allTopicList = resp.data.topicList.sort();
console.log($scope.allTopicList);
$scope.allTopicNameList = resp.data.topicNameList;
$scope.allMessageTypeList = resp.data.messageTypeList;
console.log($scope.allTopicNameList);
console.log(JSON.stringify(resp));
$scope.showTopicList(1, $scope.allTopicList.length);
$scope.showTopicList(1, $scope.allTopicNameList.length);
} else {
Notification.error({message: resp.errMsg, delay: 5000});
@@ -79,6 +86,18 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
$scope.$watch('filterNormal', function () {
$scope.filterList(1);
});
$scope.$watch('filterFifo', function () {
$scope.filterList(1);
});
$scope.$watch('filterTransaction', function () {
$scope.filterList(1);
});
$scope.$watch('filterUnspecified', function () {
$scope.filterList(1);
});
$scope.$watch('filterDelay', function () {
$scope.filterList(1);
});
$scope.$watch('filterRetry', function () {
$scope.filterList(1);
});
@@ -92,13 +111,13 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
var lowExceptStr = $scope.filterStr.toLowerCase();
var canShowList = [];
$scope.allTopicList.forEach(function (element) {
if ($scope.filterByType(element)) {
if (element.toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push(element);
for (let i = 0; i < $scope.allTopicNameList.length; ++i) {
if ($scope.filterByType($scope.allTopicNameList[i], $scope.allMessageTypeList[i])) {
if ($scope.allTopicNameList[i].toLowerCase().indexOf(lowExceptStr) != -1) {
canShowList.push($scope.allTopicNameList[i]);
}
}
}
});
$scope.paginationConf.totalItems = canShowList.length;
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
@@ -106,7 +125,7 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
$scope.topicShowList = canShowList.slice(from, to);
};
$scope.filterByType = function (str) {
$scope.filterByType = function (str, type) {
if ($scope.filterRetry) {
if (str.startsWith("%R")) {
return true
@@ -122,8 +141,31 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
return true
}
}
if (localStorage.getItem('isV5') && $scope.filterUnspecified) {
if (type.includes("UNSPECIFIED")) {
return true
}
}
if ($scope.filterNormal) {
if (str.startsWith("%") == false) {
if (type.includes("NORMAL")) {
return true
}
if (!localStorage.getItem('isV5') && type.includes("UNSPECIFIED")) {
return true
}
}
if (localStorage.getItem('isV5') && $scope.filterDelay) {
if (type.includes("DELAY")) {
return true
}
}
if (localStorage.getItem('isV5') && $scope.filterFifo) {
if (type.includes("FIFO")) {
return true
}
}
if (localStorage.getItem('isV5') && $scope.filterTransaction) {
if (type.includes("TRANSACTION")) {
return true
}
}
@@ -138,10 +180,10 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
var perPage = $scope.paginationConf.itemsPerPage;
var from = (currentPage - 1) * perPage;
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
console.log($scope.allTopicList);
console.log($scope.allTopicNameList);
console.log(from)
console.log(to)
$scope.topicShowList = $scope.allTopicList.slice(from, to);
$scope.topicShowList = $scope.allTopicNameList.slice(from, to);
$scope.paginationConf.totalItems = totalItem;
console.log($scope.topicShowList)
console.log($scope.paginationConf.totalItems)
@@ -328,8 +370,8 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
var bIsUpdate = true;
if (request == null) {
request = [{
writeQueueNums: 16,
readQueueNums: 16,
writeQueueNums: 8,
readQueueNums: 8,
perm: 6,
order: false,
topicName: "",
@@ -355,6 +397,7 @@ module.controller('topicController', ['$scope', 'ngDialog', '$http', 'Notificati
topicRequestList: request,
allClusterNameList: Object.keys(resp.data.clusterInfo.clusterAddrTable),
allBrokerNameList: Object.keys(resp.data.brokerServer),
allMessageTypeList: resp.data.messageTypes,
bIsUpdate: bIsUpdate,
writeOperationEnabled: $scope.writeOperationEnabled
}

View File

@@ -289,3 +289,15 @@
.table.text-middle>tbody>tr>td,.table.text-middle>tbody>tr>th{
vertical-align: middle;
}
.perm-table{width: 100%;}
.perm-table .perm-tg{width: 70%;}
.perm-table .center{border-left: 1px solid #e4dddd; border-right: 1px solid #e4dddd;}
.input-none {
border: 0;
outline: none;
background-color: rgba(0, 0, 0, 0);
cursor: text !important;
width: 60%;
}

View File

@@ -28,6 +28,7 @@
<div class="navbar-collapse collapse navbar-warning-collapse">
<ul class="nav navbar-nav">
<li ng-class="path =='ops' ? 'active':''"><a ng-href="#/ops">{{'OPS' | translate}}</a></li>
<li ng-show="rmqVersion" ng-class="path =='proxy' ? 'active':''"><a ng-href="#/proxy">{{'PROXY' | translate}}</a></li>
<li ng-class="path =='dashboard' || path ==''? 'active':''"><a ng-href="#/">{{'DASHBOARD' | translate}}</a></li>
<li ng-class="path =='cluster' ? 'active':''"><a ng-href="#/cluster">{{'CLUSTER' | translate}}</a></li>
<li ng-class="path =='topic' ? 'active':''"><a ng-href="#/topic">{{'TOPIC' | translate}}</a></li>
@@ -36,6 +37,7 @@
<li ng-class="path =='message' ? 'active':''"><a ng-href="#/message">{{'MESSAGE' | translate}}</a></li>
<li ng-class="path =='dlqMessage' ? 'active':''"><a ng-href="#/dlqMessage">{{'DLQ_MESSAGE' | translate}}</a></li>
<li ng-class="path =='messageTrace' ? 'active':''"><a ng-href="#/messageTrace">{{'MESSAGETRACE' | translate}}</a></li>
<li ng-show="{{ show }}" ng-class="path =='acl' ? 'active':''"><a ng-href="#/acl">Acl</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
@@ -46,6 +48,14 @@
<li><a href="javascript:void(0)" ng-click="changeTranslate('zh')">Simplified Chinese</a></li>
</ul>
</li>
<li class="dropdown">
<a href="bootstrap-elements.html" data-target="#" class="dropdown-toggle" data-toggle="dropdown">{{'CHANGE_VERSION' | translate}}
<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="javascript:void(0)" ng-click="changeRMQVersion(5)">RocketMQ 5.x</a></li>
<li><a href="javascript:void(0)" ng-click="changeRMQVersion(4)">RocketMQ 4.x</a></li>
</ul>
</li>
<li class="dropdown" ng-show="username != ''">
<a href="bootstrap-elements.html" data-target="#" class="dropdown-toggle" data-toggle="dropdown">{{username}}
<b class="caret"></b></a>

View File

@@ -0,0 +1,483 @@
<!--
~ 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.
-->
<div class="container-fluid" id="deployHistoryList">
<div class="modal-body">
<div ng-cloak="" class="tabsdemoDynamicHeight">
<md-content>
<md-tabs md-dynamic-height="" md-border-bottom="">
<md-tab label="Account Info">
<md-content class="md-padding" style="min-height:600px">
<form class="form-inline pull-left col-sm-12">
<div class="form-group">
<label>Access Key:</label>
<input class="form-control" style="width: 450px" type="text" ng-model="filterStr"/>
</div>
<button ng-show="writeOperationEnabled" class="btn btn-raised btn-sm btn-primary"
type="button"
ng-click="openAddDialog()">{{'ADD' |
translate}}
</button>
</form>
<table class="table table-bordered">
<tr>
<th class="text-center">Access Key</th>
<th ng-show="writeOperationEnabled" class="text-center">Secret Key</th>
<th class="text-center">{{'IS_ADMIN' | translate}}</th>
<th class="text-center">{{'DEFAULT_TOPIC_PERM' | translate}}</th>
<th class="text-center">{{'DEFAULT_GROUP_PERM' | translate}}</th>
<th class="text-center">{{'TOPIC_PERM' | translate}}</th>
<th class="text-center">{{'GROUP_PERM' | translate}}</th>
<th ng-show="writeOperationEnabled" class="text-center">
{{'OPERATION' | translate}}
</th>
</tr>
<tr ng-repeat="item in plainAccessConfigs">
<td class="text-center">{{item.accessKey}}</td>
<td ng-show="writeOperationEnabled" class="text-center">
<input type="{{showSecretKeyType[item.accessKey].type}}"
value="{{item.secretKey}}" class="input-none" ng-disabled="true"/>
<a href="javascript:;"
ng-click="switchSecretKeyType(item.accessKey)">{{showSecretKeyType[item.accessKey].action | translate}}</a>
</td>
<td class="text-center">{{item.admin}}</td>
<td class="text-center">{{item.defaultTopicPerm}}</td>
<td class="text-center">{{item.defaultGroupPerm}}</td>
<td class="text-center">
<table ng-repeat="topic in item.topicPerms" class="perm-table">
<tr>
<td class="perm-tg">{{topic}}</td>
<td ng-show="writeOperationEnabled" class="center"><a
href="javascript:;"
ng-click="openUpdateTopicDialog(item, topic)">
{{'UPDATE' | translate}}</a></td>
<td ng-show="writeOperationEnabled"><a href="javascript:;"
ng-confirm-click="Are you sure to delete {{topic}}?"
confirmed-click="deletePermConfig(item, topic, 'topic')">{{'DELETE' | translate}}</a>
</td>
</tr>
</table>
</td>
<td class="text-center">
<table ng-repeat="group in item.groupPerms" class="perm-table">
<tr>
<td class="perm-tg">{{group}}</td>
<td ng-show="writeOperationEnabled" class="center"><a
href="javascript:;"
ng-click="openUpdateGroupDialog(item, group)">
{{'UPDATE' | translate}}</a></td>
<td ng-show="writeOperationEnabled"><a href="javascript:;"
ng-confirm-click="Are you sure to delete {{group}}?"
confirmed-click="deletePermConfig(item, group, 'group')">{{'DELETE' | translate}}</a>
</td>
</tr>
</table>
</td>
<td ng-show="writeOperationEnabled" class="text-center">
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-click="openAddTopicDialog(item)">
{{'ADD' | translate}}topic
</button>
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-click="openAddGroupDialog(item)">
{{'ADD' | translate}}group
</button>
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-click="openUpdateDialog(item)">
{{'UPDATE' | translate}}
</button>
<button class="btn btn-raised btn-sm btn-danger" type="button"
ng-confirm-click="Are you sure to delete {{item.accessKey}}?"
confirmed-click="deleteAclConfig(item.accessKey)">{{'DELETE' | translate}}
</button>
<button class="btn btn-raised btn-sm btn-danger" type="button"
ng-click="synchronizeData(item)">{{'SYNCHRONIZE' | translate}}
</button>
</td>
</tr>
</table>
<tm-pagination conf="paginationConf"></tm-pagination>
</md-content>
</md-tab>
<md-tab label="Global White List">
<md-content class="md-padding" style="min-height:600px">
<form class="form-inline pull-left col-sm-12">
<button ng-show="writeOperationEnabled" class="btn btn-raised btn-sm btn-primary"
type="button"
ng-click="openAddAddrDialog()">{{'ADD' |
translate}}
</button>
<button ng-show="writeOperationEnabled" class="btn btn-raised btn-sm btn-danger"
type="button"
ng-confirm-click="Are you sure to synchronize white list to all broker int the cluster?"
confirmed-click="synchronizeWhiteList(allGlobalWhiteAddrs)">
{{'SYNCHRONIZE' | translate}}
</button>
</form>
<table class="table table-bordered">
<tr>
<th class="text-center">{{'WHITE_LIST' | translate}}</th>
<th ng-show="writeOperationEnabled" class="text-center">
{{'OPERATION' | translate}}
</th>
</tr>
<tr ng-repeat="item in allGlobalWhiteAddrs">
<td class="text-center">{{item}}
</td>
<td ng-show="writeOperationEnabled" class="text-center">
<button class="btn btn-raised btn-sm btn-danger" type="button"
ng-confirm-click="Are you sure to delete {{item}}?"
confirmed-click="deleteGlobalWhiteAddr(item)">{{'DELETE' | translate}}
</button>
</td>
</tr>
</table>
</md-content>
</md-tab>
</md-tabs>
</md-content>
</div>
</div>
</div>
<script type="text/ng-template" id="addAclAccountDialog">
<div class="modal-header">
<h4 class="modal-title">{{'ADD' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="addAppForm" name="addAppForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">Access Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.accessKey" type="text" required/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Secret Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.secretKey" type="text" required/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'IS_ADMIN' | translate}}:</label>
<div class="col-sm-8">
<md-switch class="md-primary" md-no-ink aria-label="Switch No Ink"
ng-model="ngDialogData.admin">
</md-switch>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'DEFAULT_TOPIC_PERM' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.defaultTopicPerm" type="text" readonly/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'DEFAULT_GROUP_PERM' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.defaultGroupPerm" type="text" readonly/>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="addRequest({'accessKey':ngDialogData.accessKey, 'secretKey': ngDialogData.secretKey, 'admin': ngDialogData.admin, 'defaultTopicPerm': ngDialogData.defaultTopicPerm, 'defaultGroupPerm': ngDialogData.defaultGroupPerm})">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="updateAclAccountDialog">
<div class="modal-header">
<h4 class="modal-title">{{'UPDATE' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="updateAccountForm" name="updateAccountForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">Access Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.accessKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Secret Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.secretKey" type="text" required/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'IS_ADMIN' | translate}}:</label>
<div class="col-sm-8">
<md-switch class="md-primary" md-no-ink aria-label="Switch No Ink"
ng-model="ngDialogData.admin">
</md-switch>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'DEFAULT_TOPIC_PERM' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.defaultTopicPerm" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'DEFAULT_GROUP_PERM' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.defaultGroupPerm" type="text" disabled/>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="updateAclAccountRequest({'accessKey':ngDialogData.accessKey, 'secretKey': ngDialogData.secretKey, 'admin': ngDialogData.admin, 'defaultTopicPerm': ngDialogData.defaultTopicPerm, 'defaultGroupPerm': ngDialogData.defaultGroupPerm})">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="addAclTopicDialog">
<div class="modal-header">
<h4 class="modal-title">{{'ADD' | translate }}{{'TOPIC_PERM' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="addAclTopicForm" name="addAclTopicForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">Access Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.accessKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Secret Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.secretKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'TOPIC' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="topic" type="text" required/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'TOPIC_PERM' | translate}}:</label>
<div class="col-sm-8">
<md-checkbox class="md-primary" ng-model="ngDialogData.pub">PUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.sub">SUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.deny">DENY</md-checkbox>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="updateAclAccountRequest({'originalData':ngDialogData,'topic': topic , 'pub': ngDialogData.pub, 'sub': ngDialogData.sub, 'deny': ngDialogData.deny})">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="updateAclTopicDialog">
<div class="modal-header">
<h4 class="modal-title">{{'UPDATE' | translate }}{{'TOPIC_PERM' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="updateAclTopicForm" name="updateAclTopicForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">Access Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.accessKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Secret Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.secretKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'TOPIC' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.topic" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'TOPIC_PERM' | translate}}:</label>
<div class="col-sm-8">
<md-checkbox class="md-primary" ng-model="ngDialogData.pub">PUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.sub">SUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.deny">DENY</md-checkbox>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="updateAclAccountRequest({'originalData':ngDialogData,'topic': ngDialogData.topic , 'pub': ngDialogData.pub, 'sub': ngDialogData.sub, 'deny': ngDialogData.deny})">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="addAclGroupDialog">
<div class="modal-header">
<h4 class="modal-title">{{'ADD' | translate }}{{'GROUP_PERM' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="addAclGroupForm" name="addAclGroupForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">Access Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.accessKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Secret Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.secretKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'CONSUMER' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="group" type="text" required/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'GROUP_PERM' | translate}}:</label>
<div class="col-sm-8">
<md-checkbox class="md-primary" ng-model="ngDialogData.pub">PUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.sub">SUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.deny">DENY</md-checkbox>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="updateAclAccountRequest({'originalData':ngDialogData,'group': group , 'pub': ngDialogData.pub, 'sub': ngDialogData.sub, 'deny': ngDialogData.deny})">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="updateAclGroupDialog">
<div class="modal-header">
<h4 class="modal-title">{{'UPDATE' | translate }}{{'GROUP_PERM' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="updateAclGroupForm" name="updateAclGroupForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">Access Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.accessKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Secret Key:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.secretKey" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'CONSUMER' | translate}}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ngDialogData.group" type="text" disabled/>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'GROUP_PERM' | translate}}:</label>
<div class="col-sm-8">
<md-checkbox class="md-primary" ng-model="ngDialogData.pub">PUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.sub">SUB</md-checkbox>
<md-checkbox class="md-primary" ng-model="ngDialogData.deny">DENY</md-checkbox>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="updateAclAccountRequest({'originalData':ngDialogData,'group': ngDialogData.group , 'pub': ngDialogData.pub, 'sub': ngDialogData.sub, 'deny': ngDialogData.deny})">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="addWhiteListDialog">
<div class="modal-header">
<h4 class="modal-title">{{'ADD' | translate }}{{'WHITE_LIST' | translate }}</h4>
</div>
<div class="modal-body ">
<form id="addWhiteListForm" name="addWhiteListForm" class="form-horizontal" novalidate>
<div class="form-group">
<label class="control-label col-sm-2">{{'WHITE_LIST' | translate }}:</label>
<div class="col-sm-10">
<input class="form-control" ng-model="ip" type="text"/>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-click="addWhiteListRequest(ip)">
{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</script>

View File

@@ -50,7 +50,14 @@
<td class="text-center">{{instance.address}}</td>
<td class="text-center">{{instance.brokerVersionDesc}}</td>
<td class="text-center">{{instance.putTps.split(' ')[0]| number:2}}</td>
<td class="text-center">{{instance.getTransferedTps.split(' ')[0]| number:2}}</td>
<td class="text-center">
<span ng-if="!instance.getTransferedTps || !instance.getTransferedTps.trim()">
{{instance.getTransferredTps.split(' ')[0] | number:2}}
</span>
<span ng-if="instance.getTransferedTps && instance.getTransferedTps.trim()">
{{instance.getTransferedTps.split(' ')[0] | number:2}}
</span>
</td>
<td class="text-center">{{instance.msgPutTotalTodayMorning -
instance.msgPutTotalYesterdayMorning}}
</td>

View File

@@ -26,6 +26,8 @@
</div>
<md-checkbox aria-label="Checkbox" ng-model="filterNormal" class="md-primary">{{'NORMAL' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-show="rmqVersion" ng-model="filterFIFO" class="md-primary">{{'FIFO' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterSystem" class="md-primary">{{'SYSTEM' | translate}}
</md-checkbox>
<button class="btn btn-raised btn-sm btn-primary" type="button" ng-show="{{writeOperationEnabled}}"
@@ -64,11 +66,11 @@
<td class="text-center">{{consumerGroup.consumeTps}}</td>
<td class="text-center">{{consumerGroup.diffTotal}}</td>
<td class="text-left">
<button name="client" ng-click="client(consumerGroup.group)"
<button name="client" ng-click="client(consumerGroup.group, consumerGroup.address)"
class="btn btn-raised btn-sm btn-primary"
type="button">{{'CLIENT' | translate}}
</button>
<button name="client" ng-click="detail(consumerGroup.group)"
<button name="client" ng-click="detail(consumerGroup.group, consumerGroup.address)"
class="btn btn-raised btn-sm btn-primary"
type="button">{{'CONSUME_DETAIL' | translate}}
</button>
@@ -274,15 +276,6 @@
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<!--<div class="form-group">-->
<!--<label class="control-label col-sm-4">retryMaxTimes:</label>-->
<!--<div class="col-sm-8">-->
<!--<input class="form-control" ng-model="item.subscriptionGroupConfig.retryMaxTimes"-->
<!--type="text"-->
<!--required/>-->
<!--<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>-->
<!--</div>-->
<!--</div>-->
<div class="form-group">
<label class="control-label col-sm-3">brokerId:</label>
<div class="col-sm-9">
@@ -291,6 +284,132 @@
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">retryMaxTimes:</label>
<div class="col-sm-9">
<input class="form-control" ng-model="item.subscriptionGroupConfig.retryMaxTimes" type="text"
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">whichBrokerWhenConsumeSlowly:</label>
<div class="col-sm-9">
<input class="form-control"
ng-model="item.subscriptionGroupConfig.whichBrokerWhenConsumeSlowly" type="text"
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
</form>
<div class="modal-footer">
<div class="ngdialog-buttons">
<button type="button" class="ngdialog-button ngdialog-button-primary"
ng-disabled="addAppForm.$invalid"
ng-show="{{ngDialogData.writeOperationEnabled}}"
ng-click="postConsumerRequest(item)">{{ 'COMMIT' | translate }}
</button>
<button type="button" class="ngdialog-button ngdialog-button-secondary"
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="consumerModifyDialogForV5">
<div>
<div>
<div class="modal-header">
<h4 class="modal-title">{{'SUBSCRIPTION_CHANGE'|translate}}</h4>
</div>
<div class="modal-body " ng-repeat="item in ngDialogData.consumerRequestList">
<form id="addAppForm1" name="addAppForm" class="form-horizontal" novalidate>
<div class="form-group" ng-hide="ngDialogData.bIsUpdate">
<label class="control-label col-sm-3">clusterName:</label>
<div class="col-sm-9">
<select name="mySelectClusterNameList" multiple chosen
ng-model="item.clusterNameList"
ng-options="clusterNameItem for clusterNameItem in ngDialogData.allClusterNameList">
<option value=""></option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">brokerName:</label>
<div class="col-sm-9">
<select name="mySelectBrokerNameList" multiple chosen
ng-disabled="ngDialogData.bIsUpdate"
ng-model="item.brokerNameList"
ng-options="brokerNameItem for brokerNameItem in ngDialogData.allBrokerNameList">
<option value=""></option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">groupName:</label>
<div class="col-sm-9">
<input class="form-control" ng-model="item.subscriptionGroupConfig.groupName" type="text"
ng-disabled="ngDialogData.bIsUpdate" required/>
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">consumeEnable:</label>
<div class="col-sm-9">
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
aria-label="Switch No Ink" ng-model="item.subscriptionGroupConfig.consumeEnable">
</md-switch>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">consumeOrderlyEnable:</label>
<div class="col-sm-9">
<md-switch class="md-primary custom-md-switch" eng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
aria-label="Switch No Ink" ng-model="item.subscriptionGroupConfig.consumeMessageOrderly">
</md-switch>
<span style="font-size: 12px;">[Pay Attention: FIFO ConsumerGroup Need Open 'consumeOrderlyEnable' Option]</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">consumeBroadcastEnable:</label>
<div class="col-sm-9">
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
aria-label="Switch No Ink"
ng-model="item.subscriptionGroupConfig.consumeBroadcastEnable">
</md-switch>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">retryQueueNums:</label>
<div class="col-sm-9">
<input class="form-control" ng-model="item.subscriptionGroupConfig.retryQueueNums"
type="text" ng-disabled="{{!ngDialogData.writeOperationEnabled}}"
required/>
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">brokerId:</label>
<div class="col-sm-9">
<input class="form-control" ng-model="item.subscriptionGroupConfig.brokerId" type="text"
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">retryMaxTimes:</label>
<div class="col-sm-9">
<input class="form-control" ng-model="item.subscriptionGroupConfig.retryMaxTimes" type="text"
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">whichBrokerWhenConsumeSlowly:</label>
<div class="col-sm-9">

View File

@@ -64,9 +64,22 @@
ng-click="queryDlqMessageByConsumerGroup()">
<span class="glyphicon glyphicon-search"></span> {{ 'SEARCH' | translate}}
</button>
<button type="button" id="batchResendBtn"
class="btn btn-raised btn-sm btn-primary disabled"
data-toggle="modal"
ng-click="batchResendDlqMessage(selectedConsumerGroup)">
<span class="glyphicon glyphicon-send"></span> {{ 'BATCH_RESEND' | translate}}
</button>
<button type="button" id="batchExportBtn"
class="btn btn-raised btn-sm btn-primary disabled"
data-toggle="modal"
ng-click="batchExportDlqMessage(selectedConsumerGroup)">
<span class="glyphicon glyphicon-export"></span> {{ 'BATCH_EXPORT' | translate}}
</button>
</form>
<table class="table table-bordered text-middle">
<tr>
<th class="text-center"><input type="checkbox" ng-model='checkedAll' ng-change="selectAll()"/></th>
<th class="text-center">Message ID</th>
<th class="text-center">Tag</th>
<th class="text-center">Key</th>
@@ -77,6 +90,9 @@
<td colspan="6" style="text-align: center">{{'NO_MATCH_RESULT' | translate}}</td>
</tr>
<tr ng-repeat="item in messageShowList">
<td class="text-center">
<input type="checkbox" ng-model='item.checked' ng-change="selectItem()"/>
</td>
<td class="text-center">{{item.msgId}}</td>
<td class="text-center">{{item.properties.TAGS}}</td>
<td class="text-center">{{item.properties.KEYS}}</td>
@@ -192,7 +208,7 @@
<label class="control-label col-sm-2">Properties:</label>
<div class="col-sm-10">
<textarea class="form-control"
style="min-height:60px; resize: none"
style="min-height:100px; resize: none"
readonly>{{ngDialogData.messageView.properties}}</textarea>
</div>
</div>

View File

@@ -73,6 +73,9 @@
<th class="text-center">StoreTime</th>
<th class="text-center">Operation</th>
</tr>
<tr style="display: none" id="noMsgTip">
<td colspan="5" style="text-align: center">{{'NO_MATCH_RESULT' | translate}}</td>
</tr>
<tr ng-repeat="item in messageShowList">
<td class="text-center">{{item.msgId}}</td>
<td class="text-center">{{item.properties.TAGS}}</td>
@@ -227,7 +230,13 @@
</div>
</form>
</div>
<p>messageTrackList:</p>
<div style="font-weight:700; color:#000">messageTrackList:</div>
<form class="form-inline">
<div class="form-group">
<label>consumerGroup:</label>
<input type="text" class="form-control" ng-model="filterConsumerGroup">
</div>
</form>
<table class="table-bordered table text-middle">
<tr>
<th class="text-center">consumerGroup</th>
@@ -235,7 +244,7 @@
<!--<th class="text-center">exceptionDesc</th>-->
<th class="text-center">Operation</th>
</tr>
<tr ng-repeat="item in ngDialogData.messageTrackList">
<tr ng-repeat="item in messageTrackShowList">
<td class="text-center">{{item.consumerGroup}}</td>
<td class="text-center">{{item.trackType}}</td>
<td class="text-center">

View File

@@ -17,15 +17,26 @@
<div class="container-fluid" id="deployHistoryList">
<div class="page-content">
<h2 class="md-title">NameServerAddressList</h2>
<div class="pull-left">
<md-chips ng-model="namesvrAddrList" md-on-add="eleChange(namesvrAddrList)"
md-on-remove="eleChange(namesvrAddrList)" readonly="inputReadonly" md-removable="ctrl.removable"></md-chips>
<div class="pull-left" style="min-width: 400px; max-width: 500px; padding: 10px 10px 10px 0">
<select ng-model="selectedNamesrv" chosen
ng-options="x for x in namesvrAddrList"
ng-change="updateNameSvrAddr()"
required></select>
</div>
<div class="pull-left">
<button class="btn btn-raised btn-sm btn-primary" type="button" ng-show="{{writeOperationEnabled}}"
ng-click="updateNameSvrAddr()">{{'UPDATE' | translate}}
</button>
</div>
<form class="form-inline pull-left" style="margin-left: 20px" ng-show="{{writeOperationEnabled}}">
<div class="form-group" style="margin: 0">
<label for="namesrvAddr">NamesrvAddr:</label>
<input id="namesrvAddr" class="form-control" style="width: 300px; margin: 0 10px 0 10px" type="text" ng-model="newNamesrvAddr" required/>
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-click="addNameSvrAddr()"> {{ 'ADD' | translate}}
</button>
</div>
</form>
<br/>
<br/>
<h2 class="md-title">IsUseVIPChannel</h2>

View File

@@ -0,0 +1,67 @@
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div class="container-fluid" id="deployHistoryList">
<div class="page-content">
<h2 class="md-title">ProxyServerAddressList</h2>
<div class="pull-left" style="min-width: 400px; max-width: 500px; padding: 10px 10px 10px 0">
<select ng-model="selectedProxy" chosen
ng-options="x for x in proxyAddrList"
ng-change="updateProxyAddr()"
required></select>
</div>
<div class="pull-left">
<button class="btn btn-raised btn-sm btn-primary" type="button" ng-show="{{writeOperationEnabled}}"
ng-click="updateProxyAddr()">{{'UPDATE' | translate}}
</button>
</div>
<form class="form-inline pull-left" style="margin-left: 20px" ng-show="{{writeOperationEnabled}}">
<div class="form-group" style="margin: 0">
<label for="proxyAddr">ProxyAddr:</label>
<input id="proxyAddr" class="form-control" style="width: 300px; margin: 0 10px 0 10px" type="text" ng-model="newProxyAddr" required/>
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-click="addProxyAddr()"> {{ 'ADD' | translate}}
</button>
</div>
</form>
</div>
</div>
<div class="modal proxyModal fade" role="dialog" tabindex="-1" aria-hidden="true" aria-labelledby="config-modal-label">
<div class="modal-dialog modal-lg">
<div class="modal-content" >
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal">&times;</button>
<h4 id="config-modal-label" class="modal-title">
[{{selectedProxy}}]
</h4>
</div>
<div class="modal-body limit_height">
<table class="table table-bordered">
<tr ng-repeat="(key, value) in allProxyConfig">
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
</table>
</div>
<div class="modal-footer">
<div class="col-md-12 text-center">
<button type="button" class="btn btn-raised" data-dismiss="modal">{{ 'CLOSE' | translate }}</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -24,6 +24,14 @@
</div>
<md-checkbox aria-label="Checkbox" ng-model="filterNormal" class="md-primary">{{'NORMAL' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterDelay" class="md-primary" ng-show="rmqVersion">{{'DELAY' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterFifo" class="md-primary" ng-show="rmqVersion">{{'FIFO' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterTransaction" class="md-primary" ng-show="rmqVersion">{{'TRANSACTION' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterUnspecified" class="md-primary" ng-show="rmqVersion">{{'UNSPECIFIED' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterRetry" class="md-primary">{{'RETRY' | translate}}
</md-checkbox>
<md-checkbox aria-label="Checkbox" ng-model="filterDLQ" class="md-primary">{{'DLQ' | translate}}
@@ -63,6 +71,7 @@
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-click="openUpdateDialog(topic, sysFlag)">topic {{'CONFIG' |translate}}
</button>
<!-- todo 发送消息,根据消息类型判断-->
<button class="btn btn-raised btn-sm btn-primary" type="button"
ng-show="{{!sysFlag}}"
ng-click="openSendTopicMessageDialog(topic)">{{'SEND_MSG' | translate}}
@@ -189,6 +198,18 @@
<span class="text-danger" ng-show="addAppForm.topicName.$error.required">{{'TOPIC_NAME'|translate}}不能为空.</span>
</div>
</div>
<!-- 设置topic 类型 -->
<div class="form-group">
<label class="control-label col-sm-2">{{'MESSAGE_TYPE'|translate}}:</label>
<div class="col-sm-10">
<select name="mySelectMessageType" chosen ng-disabled="ngDialogData.bIsUpdate"
ng-model="item.messageType"
ng-options="messageType as value | translate disable when messageType=='UNSPECIFIED' for (messageType , value) in ngDialogData.allMessageTypeList"
>
<option value=""></option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">{{'WRITE_QUEUE_NUMS'|translate}}:</label>
<div class="col-sm-10">

View File

@@ -34,27 +34,27 @@ import org.apache.rocketmq.client.impl.MQClientAPIImpl;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.admin.ConsumeStats;
import org.apache.rocketmq.common.admin.RollbackStats;
import org.apache.rocketmq.common.admin.TopicStatsTable;
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
import org.apache.rocketmq.remoting.protocol.admin.RollbackStats;
import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.BrokerStatsData;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumeStatsList;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.body.GroupList;
import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
import org.apache.rocketmq.common.protocol.body.QueueTimeSpan;
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.remoting.protocol.body.ConsumeStatsList;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.remoting.protocol.body.GroupList;
import org.apache.rocketmq.remoting.protocol.body.KVTable;
import org.apache.rocketmq.remoting.protocol.body.ProducerConnection;
import org.apache.rocketmq.remoting.protocol.body.QueueTimeSpan;
import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.dashboard.service.client.MQAdminExtImpl;
import org.apache.rocketmq.dashboard.service.client.MQAdminInstance;
import org.apache.rocketmq.dashboard.util.MockObjectUtil;
@@ -82,6 +82,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
@@ -418,8 +419,10 @@ public class MQAdminExtImplTest {
assertNotNull(mqAdminExtImpl);
{
doNothing().when(defaultMQAdminExt).deleteSubscriptionGroup(anyString(), anyString());
doNothing().when(defaultMQAdminExt).deleteSubscriptionGroup(anyString(), anyString(), anyBoolean());
}
mqAdminExtImpl.deleteSubscriptionGroup(brokerAddr, "group_test");
mqAdminExtImpl.deleteSubscriptionGroup(brokerAddr, "group_test", true);
}
@Test
@@ -568,11 +571,13 @@ public class MQAdminExtImplTest {
public void testCreateTopic() throws Exception {
assertNotNull(mqAdminExtImpl);
{
doNothing().when(defaultMQAdminExt).createTopic(anyString(), anyString(), anyInt());
doNothing().when(defaultMQAdminExt).createTopic(anyString(), anyString(), anyInt(), anyInt());
doNothing().when(defaultMQAdminExt).createTopic(anyString(), anyString(), anyInt(), anyMap());
doNothing().when(defaultMQAdminExt).createTopic(anyString(), anyString(), anyInt(), anyInt(), anyMap());
}
mqAdminExtImpl.createTopic("key", "topic_test", 8);
mqAdminExtImpl.createTopic("key", "topic_test", 8, 1);
Map<String, String> map = new HashMap<>();
map.put("message.type", "FIFO");
mqAdminExtImpl.createTopic("key", "topic_test", 8, map);
mqAdminExtImpl.createTopic("key", "topic_test", 8, 1, map);
}
@Test
@@ -758,16 +763,6 @@ public class MQAdminExtImplTest {
Assert.assertNotNull(wrapper);
}
@Test
public void testGetAllTopicGroup() throws Exception {
assertNotNull(mqAdminExtImpl);
{
when(defaultMQAdminExt.getAllTopicGroup(anyString(), anyLong())).thenReturn(new TopicConfigSerializeWrapper());
}
TopicConfigSerializeWrapper wrapper = mqAdminExtImpl.getAllTopicGroup(brokerAddr, 5000L);
Assert.assertNotNull(wrapper);
}
@Test
public void testUpdateConsumeOffset() throws Exception {
assertNotNull(mqAdminExtImpl);
@@ -801,4 +796,42 @@ public class MQAdminExtImplTest {
Assert.assertFalse(mqAdminExtImpl.resumeCheckHalfMessage("topic_test", "7F000001ACC018B4AAC2116AF6500000"));
}
@Test
public void testAddWritePermOfBroker() throws Exception {
assertNotNull(mqAdminExtImpl);
{
when(defaultMQAdminExt.addWritePermOfBroker(anyString(), anyString())).thenReturn(6);
}
Assert.assertEquals(mqAdminExtImpl.addWritePermOfBroker("127.0.0.1:9876", "broker-a"), 6);
}
@Test
public void testGetUserSubscriptionGroup() throws Exception {
assertNotNull(mqAdminExtImpl);
SubscriptionGroupWrapper wrapper = new SubscriptionGroupWrapper();
{
when(defaultMQAdminExt.getUserSubscriptionGroup(anyString(), anyLong())).thenReturn(wrapper);
}
Assert.assertEquals(mqAdminExtImpl.getUserSubscriptionGroup("127.0.0.1:10911", 3000), wrapper);
}
@Test
public void testGetAllTopicConfig() throws Exception {
assertNotNull(mqAdminExtImpl);
TopicConfigSerializeWrapper wrapper = new TopicConfigSerializeWrapper();
{
when(defaultMQAdminExt.getAllTopicConfig(anyString(), anyLong())).thenReturn(wrapper);
}
Assert.assertEquals(mqAdminExtImpl.getAllTopicConfig("127.0.0.1:10911", 3000), wrapper);
}
@Test
public void testGetUserTopicConfig() throws Exception {
assertNotNull(mqAdminExtImpl);
TopicConfigSerializeWrapper wrapper = new TopicConfigSerializeWrapper();
{
when(defaultMQAdminExt.getUserTopicConfig(anyString(), anyBoolean(), anyLong())).thenReturn(wrapper);
}
Assert.assertEquals(mqAdminExtImpl.getUserTopicConfig("127.0.0.1:10911", true, 3000), wrapper);
}
}

View File

@@ -18,7 +18,7 @@ package org.apache.rocketmq.dashboard.admin;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.util.MockObjectUtil;
import org.apache.rocketmq.tools.admin.MQAdminExt;

View File

@@ -0,0 +1,50 @@
/*
* 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.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.junit.Test;
public class CollectExecutorConfigTest {
private final static int COUNT = 100;
@Test
public void testCollectExecutor() throws Exception {
AtomicInteger num = new AtomicInteger(0);
CollectExecutorConfig config = new CollectExecutorConfig();
config.setCoreSize(10);
config.setMaxSize(10);
config.setQueueSize(500);
config.setKeepAliveTime(3000);
ExecutorService collectExecutor = config.collectExecutor(config);
Assert.assertNotNull(collectExecutor);
CountDownLatch countDownLatch = new CountDownLatch(COUNT);
for (int i = 0; i < COUNT; i++) {
collectExecutor.submit(() -> {
num.getAndIncrement();
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println(collectExecutor.isTerminated());
Assert.assertEquals(COUNT, num.get());
}
}

View File

@@ -17,6 +17,7 @@
package org.apache.rocketmq.dashboard.config;
import com.google.common.collect.Lists;
import java.io.File;
import org.junit.Assert;
import org.junit.Test;
@@ -39,6 +40,7 @@ public class RMQConfigureTest {
rmqConfigure.setLoginRequired(true);
rmqConfigure.setNamesrvAddr("127.0.0.1:9876");
rmqConfigure.setTimeoutMillis(3000L);
rmqConfigure.setNamesrvAddrs(Lists.asList("127.0.0.1:9876", new String[] {"127.0.0.2:9876"}));
}
@Test
@@ -54,6 +56,7 @@ public class RMQConfigureTest {
Assert.assertTrue(rmqConfigure.isEnableDashBoardCollect());
Assert.assertTrue(rmqConfigure.isLoginRequired());
Assert.assertEquals(rmqConfigure.getNamesrvAddr(), "127.0.0.1:9876");
Assert.assertEquals(rmqConfigure.getNamesrvAddrs().size(), 2);
Assert.assertEquals(rmqConfigure.getTimeoutMillis().longValue(), 3000L);
ErrorPageRegistrar registrar = rmqConfigure.errorPageRegistrar();
registrar.registerErrorPages(new ErrorPageRegistry() {

View File

@@ -0,0 +1,368 @@
/*
* 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.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import java.util.List;
import org.apache.rocketmq.common.AclConfig;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.dashboard.model.request.AclRequest;
import org.apache.rocketmq.dashboard.service.impl.AclServiceImpl;
import org.apache.rocketmq.dashboard.util.MockObjectUtil;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class AclControllerTest extends BaseControllerTest {
@InjectMocks
private AclController aclController;
@Spy
private AclServiceImpl aclService;
@Before
public void init() throws Exception {
AclConfig aclConfig = MockObjectUtil.createAclConfig();
when(mqAdminExt.examineBrokerClusterAclConfig(anyString())).thenReturn(aclConfig);
ClusterInfo clusterInfo = MockObjectUtil.createClusterInfo();
when(mqAdminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
doNothing().when(mqAdminExt).createAndUpdatePlainAccessConfig(anyString(), any(PlainAccessConfig.class));
doNothing().when(mqAdminExt).deletePlainAccessConfig(anyString(), anyString());
doNothing().when(mqAdminExt).updateGlobalWhiteAddrConfig(anyString(), anyString());
}
@Test
public void testIsEnableAcl() throws Exception {
final String url = "/acl/enable.query";
// 1. disable acl.
requestBuilder = MockMvcRequestBuilders.get(url);
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data").value(false));
// 2.enable acl.
super.mockRmqConfigure();
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data").value(true));
}
@Test
public void testGetAclConfig() throws Exception {
final String url = "/acl/config.query";
// 1. broker addr table is not empty.
ClusterInfo clusterInfo = MockObjectUtil.createClusterInfo();
when(mqAdminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
requestBuilder = MockMvcRequestBuilders.get(url);
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isMap())
.andExpect(jsonPath("$.data.globalWhiteAddrs").isNotEmpty())
.andExpect(jsonPath("$.data.plainAccessConfigs").isNotEmpty())
.andExpect(jsonPath("$.data.plainAccessConfigs[0].secretKey").isNotEmpty());
// 2. broker addr table is empty.
clusterInfo.getBrokerAddrTable().clear();
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isMap())
.andExpect(jsonPath("$.data.globalWhiteAddrs").isEmpty())
.andExpect(jsonPath("$.data.plainAccessConfigs").isEmpty());
// 3. login required and user info is null.
when(configure.isLoginRequired()).thenReturn(true);
when(mqAdminExt.examineBrokerClusterInfo()).thenReturn(MockObjectUtil.createClusterInfo());
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isMap())
.andExpect(jsonPath("$.data.globalWhiteAddrs").isNotEmpty())
.andExpect(jsonPath("$.data.plainAccessConfigs").isNotEmpty())
.andExpect(jsonPath("$.data.plainAccessConfigs[0].secretKey").isEmpty());
// 4. login required, but user is not admin. emmmm, Mockito may can not mock static method.
}
@Test
public void testAddAclConfig() throws Exception {
final String url = "/acl/add.do";
PlainAccessConfig accessConfig = new PlainAccessConfig();
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
// 1. access key is null.
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
// 2. secret key is null.
accessConfig.setAccessKey("test-access-key");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
ClusterInfo clusterInfo = MockObjectUtil.createClusterInfo();
when(mqAdminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
// 3. add if the access key not exist.
accessConfig.setSecretKey("12345678");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// 4. add failed if the access key is existed.
accessConfig.setAccessKey("rocketmq2");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
// 5. add failed if there is no alive broker.
clusterInfo.getBrokerAddrTable().clear();
accessConfig.setAccessKey("test-access-key");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
}
@Test
public void testDeleteAclConfig() throws Exception {
final String url = "/acl/delete.do";
PlainAccessConfig accessConfig = new PlainAccessConfig();
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
// 1. access key is null.
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
// 2. access key is not null.
accessConfig.setAccessKey("rocketmq");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testUpdateAclConfig() throws Exception {
final String url = "/acl/update.do";
PlainAccessConfig accessConfig = new PlainAccessConfig();
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
// 1. secret key is null.
accessConfig.setAccessKey("rocketmq");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
// 2. update.
accessConfig.setSecretKey("abcdefghjkl");
requestBuilder.content(JSON.toJSONString(accessConfig));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testAddAclTopicConfig() throws Exception {
final String url = "/acl/topic/add.do";
AclRequest request = new AclRequest();
request.setConfig(createDefaultPlainAccessConfig());
// 1. if not exist.
request.setTopicPerm("test_topic=PUB");
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// 2. if exist.
request.setTopicPerm("topicA=PUB");
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// 3. if access key not exist.
request.getConfig().setAccessKey("test_access_key123");
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testAddAclGroupConfig() throws Exception {
final String url = "/acl/group/add.do";
AclRequest request = new AclRequest();
request.setConfig(createDefaultPlainAccessConfig());
// 1. if not exist.
request.setGroupPerm("test_consumer=PUB|SUB");
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// 2. if exist.
request.setGroupPerm("groupA=PUB|SUB");
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// 3. if access key not exist.
request.getConfig().setAccessKey("test_access_key123");
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testDeletePermConfig() throws Exception {
final String url = "/acl/perm/delete.do";
AclRequest request = new AclRequest();
request.setConfig(createDefaultPlainAccessConfig());
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// if access key not exist.
request.getConfig().setAccessKey("test_access_key123");
requestBuilder.content(JSON.toJSONString(request));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testSyncConfig() throws Exception {
final String url = "/acl/sync.do";
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(createDefaultPlainAccessConfig()));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testAddWhiteList() throws Exception {
final String url = "/acl/white/list/add.do";
List<String> whiteList = Lists.newArrayList("192.168.0.1");
// 1. if global white list is not null.
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(whiteList));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
// 2. if global white list is null.
AclConfig aclConfig = MockObjectUtil.createAclConfig();
aclConfig.setGlobalWhiteAddrs(null);
when(mqAdminExt.examineBrokerClusterAclConfig(anyString())).thenReturn(aclConfig);
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testDeleteWhiteAddr() throws Exception {
final String url = "/acl/white/list/delete.do";
requestBuilder = MockMvcRequestBuilders.delete(url);
requestBuilder.param("request", "localhost");
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Test
public void testSynchronizeWhiteList() throws Exception {
final String url = "/acl/white/list/sync.do";
List<String> whiteList = Lists.newArrayList();
// 1. if white list for syncing is empty.
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(whiteList));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(-1))
.andExpect(jsonPath("$.errMsg").exists());
// 2. if white list for syncing is not empty.
whiteList.add("localhost");
requestBuilder.content(JSON.toJSONString(whiteList));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(0));
}
@Override protected Object getTestController() {
return aclController;
}
private PlainAccessConfig createDefaultPlainAccessConfig() {
PlainAccessConfig config = new PlainAccessConfig();
config.setAdmin(false);
config.setAccessKey("rocketmq");
config.setSecretKey("123456789");
config.setDefaultGroupPerm("SUB");
config.setDefaultTopicPerm("DENY");
config.setTopicPerms(Lists.newArrayList("topicA=DENY", "topicB=PUB|SUB"));
config.setGroupPerms(Lists.newArrayList("groupA=DENY", "groupB=PUB|SUB"));
return config;
}
}

View File

@@ -17,6 +17,7 @@
package org.apache.rocketmq.dashboard.controller;
import com.google.common.collect.Lists;
import org.apache.rocketmq.dashboard.BaseTest;
import org.apache.rocketmq.dashboard.config.RMQConfigure;
import org.apache.rocketmq.dashboard.support.GlobalExceptionHandler;
@@ -62,6 +63,7 @@ public abstract class BaseControllerTest extends BaseTest {
when(configure.getAccessKey()).thenReturn("12345678");
when(configure.getSecretKey()).thenReturn("rocketmq");
when(configure.getNamesrvAddr()).thenReturn("127.0.0.1:9876");
when(configure.getNamesrvAddrs()).thenReturn(Lists.newArrayList("127.0.0.1:9876", "127.0.0.2:9876"));
when(configure.isACLEnabled()).thenReturn(true);
when(configure.isUseTLS()).thenReturn(false);
}

View File

@@ -18,8 +18,8 @@ package org.apache.rocketmq.dashboard.controller;
import java.util.HashMap;
import java.util.Properties;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.KVTable;
import org.apache.rocketmq.dashboard.service.impl.ClusterServiceImpl;
import org.apache.rocketmq.dashboard.util.MockObjectUtil;
import org.junit.Test;

View File

@@ -23,17 +23,17 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.admin.ConsumeStats;
import org.apache.rocketmq.common.admin.RollbackStats;
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
import org.apache.rocketmq.remoting.protocol.admin.RollbackStats;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.ClusterInfo;
import org.apache.rocketmq.common.protocol.body.ConsumerConnection;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
import org.apache.rocketmq.remoting.protocol.body.ConsumerConnection;
import org.apache.rocketmq.remoting.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.dashboard.model.request.ConsumerConfigInfo;
import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest;
@@ -67,6 +67,7 @@ public class ConsumerControllerTest extends BaseControllerTest {
@Before
public void init() throws Exception {
consumerService.afterPropertiesSet();
super.mockRmqConfigure();
ClusterInfo clusterInfo = MockObjectUtil.createClusterInfo();
when(mqAdminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo);
@@ -93,9 +94,10 @@ public class ConsumerControllerTest extends BaseControllerTest {
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data", hasSize(2)))
.andExpect(jsonPath("$.data[0].group").value("group_test"))
.andExpect(jsonPath("$.data[0].consumeType").value(ConsumeType.CONSUME_ACTIVELY.name()))
.andExpect(jsonPath("$.data[0].messageModel").value(MessageModel.CLUSTERING.name()));
// executorService shutdown
consumerService.destroy();
}
@Test
@@ -182,6 +184,8 @@ public class ConsumerControllerTest extends BaseControllerTest {
final String url = "/consumer/deleteSubGroup.do";
{
doNothing().when(mqAdminExt).deleteSubscriptionGroup(any(), anyString());
doNothing().when(mqAdminExt).deleteTopicInBroker(any(), anyString());
doNothing().when(mqAdminExt).deleteTopicInNameServer(any(), anyString());
}
DeleteSubGroupRequest request = new DeleteSubGroupRequest();
request.setBrokerNameList(Lists.newArrayList("broker-a"));

View File

@@ -152,7 +152,7 @@ public class DashboardControllerTest extends BaseControllerTest {
@Test
public void testTopicCurrent() throws Exception {
final String url = "/dashboard/topicCurrent";
final String url = "/dashboard/topicCurrent.query";
requestBuilder = MockMvcRequestBuilders.get(url);
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())

View File

@@ -18,10 +18,14 @@ package org.apache.rocketmq.dashboard.controller;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import java.util.List;
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.route.TopicRouteData;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.body.CMResult;
import org.apache.rocketmq.remoting.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
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;
@@ -128,10 +132,46 @@ public class DlqMessageControllerTest extends BaseControllerTest {
// 2、export dlqMessage success
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().is(200))
.andExpect(content().contentType("application/vnd.ms-excel"));
.andExpect(content().contentType("application/vnd.ms-excel;charset=utf-8"));
}
@Test
public void testBatchResendDlqMessage() throws Exception {
final String url = "/dlqMessage/batchResendDlqMessage.do";
List<DlqMessageRequest> dlqMessages = MockObjectUtil.createDlqMessageRequest();
{
ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult();
result.setConsumeResult(CMResult.CR_SUCCESS);
when(messageService.consumeMessageDirectly(any(), any(), any(), any())).thenReturn(result);
}
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(dlqMessages));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().isOk())
.andExpect(jsonPath("$.data", hasSize(2)))
.andExpect(jsonPath("$.data[0].consumeResult").value("CR_SUCCESS"));
}
@Test
public void testBatchExportDlqMessage() throws Exception {
final String url = "/dlqMessage/batchExportDlqMessage.do";
{
when(mqAdminExt.viewMessage("%DLQ%group_test", "0A9A003F00002A9F0000000000000310"))
.thenThrow(new RuntimeException());
when(mqAdminExt.viewMessage("%DLQ%group_test", "0A9A003F00002A9F0000000000000311"))
.thenReturn(MockObjectUtil.createMessageExt());
}
List<DlqMessageRequest> dlqMessages = MockObjectUtil.createDlqMessageRequest();
requestBuilder = MockMvcRequestBuilders.post(url);
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
requestBuilder.content(JSON.toJSONString(dlqMessages));
perform = mockMvc.perform(requestBuilder);
perform.andExpect(status().is(200))
.andExpect(content().contentType("application/vnd.ms-excel;charset=utf-8"));
}
@Override protected Object getTestController() {
return dlqMessageController;
}

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