You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by yu...@apache.org on 2017/06/15 02:53:17 UTC
[47/51] [partial] incubator-rocketmq-externals git commit: Release
rocketmq-console 1.0.0 version
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalExceptionHandler.java
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalExceptionHandler.java b/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalExceptionHandler.java
new file mode 100644
index 0000000..37a8d64
--- /dev/null
+++ b/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalExceptionHandler.java
@@ -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.console.support;
+
+import javax.servlet.http.HttpServletRequest;
+import org.apache.rocketmq.console.exception.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@ControllerAdvice(basePackages = "org.apache.rocketmq.console")
+public class GlobalExceptionHandler {
+ private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+ @ExceptionHandler(value = Exception.class)
+ @ResponseBody
+ public JsonResult<Object> jsonErrorHandler(HttpServletRequest req, Exception ex) throws Exception {
+ JsonResult<Object> value = null;
+ if (ex != null) {
+ logger.error("op=global_exception_handler_print_error", ex);
+ if (ex instanceof ServiceException) {
+ value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage());
+ }
+ else {
+ value = new JsonResult<Object>(-1, ex.getMessage());
+ }
+ }
+ return value;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalRestfulResponseBodyAdvice.java
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalRestfulResponseBodyAdvice.java b/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalRestfulResponseBodyAdvice.java
new file mode 100644
index 0000000..e67fa33
--- /dev/null
+++ b/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/GlobalRestfulResponseBodyAdvice.java
@@ -0,0 +1,62 @@
+/*
+ * 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.console.support;
+
+import java.lang.annotation.Annotation;
+import org.apache.rocketmq.console.aspect.admin.annotation.OriginalControllerReturnValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+@ControllerAdvice(basePackages = "org.apache.rocketmq.console")
+public class GlobalRestfulResponseBodyAdvice implements ResponseBodyAdvice<Object> {
+
+ private Logger logger = LoggerFactory.getLogger(GlobalRestfulResponseBodyAdvice.class);
+
+ @Override
+ public Object beforeBodyWrite(
+ Object obj, MethodParameter methodParameter, MediaType mediaType,
+ Class<? extends HttpMessageConverter<?>> converterType,
+ ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
+ Annotation originalControllerReturnValue = methodParameter.getMethodAnnotation(OriginalControllerReturnValue.class);
+ if (originalControllerReturnValue != null) {
+ return obj;
+ }
+ JsonResult value;
+ if (obj instanceof JsonResult) {
+ value = (JsonResult)obj;
+ }
+ else {
+ value = new JsonResult(obj);
+ }
+ return value;
+ }
+
+ @Override
+ public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
+
+ return true;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/JsonResult.java
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/JsonResult.java b/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/JsonResult.java
new file mode 100644
index 0000000..f5e20dd
--- /dev/null
+++ b/rocketmq-console/src/main/java/org/apache/rocketmq/console/support/JsonResult.java
@@ -0,0 +1,63 @@
+/*
+ * 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.console.support;
+
+public class JsonResult<T> {
+ private int status = 0;
+ private T data;
+ private String errMsg;
+
+ public JsonResult(T data) {
+ this.data = data;
+ }
+
+ public JsonResult(int status, String errMsg) {
+ this.status = status;
+ this.errMsg = errMsg;
+ }
+
+ public JsonResult(int status, T data, String errMsg) {
+ this.status = status;
+ this.data = data;
+ this.errMsg = errMsg;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+
+ public String getErrMsg() {
+ return errMsg;
+ }
+
+ public void setErrMsg(String errMsg) {
+ this.errMsg = errMsg;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/DashboardCollectTask.java
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/DashboardCollectTask.java b/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/DashboardCollectTask.java
new file mode 100644
index 0000000..db1fbc4
--- /dev/null
+++ b/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/DashboardCollectTask.java
@@ -0,0 +1,320 @@
+/*
+ * 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.console.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.console.aspect.admin.annotation.MultiMQAdminCmdMethod;
+import org.apache.rocketmq.store.stats.BrokerStatsManager;
+import org.apache.rocketmq.tools.admin.MQAdminExt;
+import org.apache.rocketmq.tools.command.stats.StatsAllSubCommand;
+import com.google.common.base.Throwables;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Resource;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.protocol.body.BrokerStatsData;
+import org.apache.rocketmq.console.config.RMQConfigure;
+import org.apache.rocketmq.console.service.DashboardCollectService;
+import org.apache.rocketmq.console.util.JsonUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DashboardCollectTask {
+ private Date currentDate = new Date();
+ @Resource
+ private MQAdminExt mqAdminExt;
+ @Resource
+ private RMQConfigure rmqConfigure;
+
+ @Resource
+ private DashboardCollectService dashboardCollectService;
+
+ private final static Logger log = LoggerFactory.getLogger(DashboardCollectTask.class);
+
+ @Scheduled(cron = "30 0/1 * * * ?")
+ @MultiMQAdminCmdMethod(timeoutMillis = 5000)
+ public void collectTopic() {
+ if (!rmqConfigure.isEnableDashBoardCollect()) {
+ return;
+ }
+ Date date = new Date();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ try {
+ TopicList topicList = mqAdminExt.fetchAllTopicList();
+ Set<String> topicSet = topicList.getTopicList();
+ for (String topic : topicSet) {
+ if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) || topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)) {
+ 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());
+ stopwatch.reset();
+ inTPS += bsd.getStatsMinute().getTps();
+ inMsgCntToday += StatsAllSubCommand.compute24HourSum(bsd);
+ }
+ catch (Exception e) {
+// throw Throwables.propagate(e);
+ }
+ }
+ }
+
+ 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) {
+// throw Throwables.propagate(e);
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ @Scheduled(cron = "0 0/1 * * * ?")
+ public void collectBroker() {
+ if (!rmqConfigure.isEnableDashBoardCollect()) {
+ return;
+ }
+ try {
+ Date date = new Date();
+ ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo();
+ Set<Map.Entry<String, BrokerData>> clusterEntries = clusterInfo.getBrokerAddrTable().entrySet();
+
+ Map<String, String> addresses = Maps.newHashMap();
+ for (Map.Entry<String, BrokerData> clusterEntry : clusterEntries) {
+ HashMap<Long, String> addrs = clusterEntry.getValue().getBrokerAddrs();
+ Set<Map.Entry<Long, String>> addrsEntries = addrs.entrySet();
+ for (Map.Entry<Long, String> addrEntry : addrsEntries) {
+ addresses.put(addrEntry.getValue(), clusterEntry.getKey() + ":" + addrEntry.getKey());
+ }
+ }
+ Set<Map.Entry<String, String>> entries = addresses.entrySet();
+ for (Map.Entry<String, String> entry : entries) {
+ List<String> list = dashboardCollectService.getBrokerMap().get(entry.getValue());
+ if (null == list) {
+ list = Lists.newArrayList();
+ }
+ KVTable kvTable = fetchBrokerRuntimeStats(entry.getKey(), 3);
+ if (kvTable == null) {
+ continue;
+ }
+ String[] tpsArray = kvTable.getTable().get("getTotalTps").split(" ");
+ BigDecimal totalTps = new BigDecimal(0);
+ for (String tps : tpsArray) {
+ totalTps = totalTps.add(new BigDecimal(tps));
+ }
+ BigDecimal averageTps = totalTps.divide(new BigDecimal(tpsArray.length), 5, BigDecimal.ROUND_HALF_UP);
+ list.add(date.getTime() + "," + averageTps.toString());
+ dashboardCollectService.getBrokerMap().put(entry.getValue(), list);
+ }
+ log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
+ }
+ catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private KVTable fetchBrokerRuntimeStats(String brokerAddr, Integer retryTime) {
+ if (retryTime == 0) {
+ return null;
+ }
+ try {
+ return mqAdminExt.fetchBrokerRuntimeStats(brokerAddr);
+ }
+ catch (Exception e) {
+ try {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e1) {
+ throw Throwables.propagate(e1);
+ }
+ fetchBrokerRuntimeStats(brokerAddr, retryTime - 1);
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Scheduled(cron = "0/5 * * * * ?")
+ public void saveData() {
+ if (!rmqConfigure.isEnableDashBoardCollect()) {
+ return;
+ }
+ //one day refresh cache one time
+ String dataLocationPath = rmqConfigure.getConsoleCollectData();
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ String nowDateStr = format.format(new Date());
+ String currentDateStr = format.format(currentDate);
+ if (!currentDateStr.equals(nowDateStr)) {
+ dashboardCollectService.getBrokerMap().invalidateAll();
+ dashboardCollectService.getTopicMap().invalidateAll();
+ currentDate = new Date();
+ }
+ File brokerFile = new File(dataLocationPath + nowDateStr + ".json");
+ File topicFile = new File(dataLocationPath + nowDateStr + "_topic" + ".json");
+ try {
+ Map<String, List<String>> brokerFileMap;
+ Map<String, List<String>> topicFileMap;
+ if (brokerFile.exists()) {
+ brokerFileMap = dashboardCollectService.jsonDataFile2map(brokerFile);
+ }
+ else {
+ brokerFileMap = Maps.newHashMap();
+ Files.createParentDirs(brokerFile);
+ }
+
+ if (topicFile.exists()) {
+ topicFileMap = dashboardCollectService.jsonDataFile2map(topicFile);
+ }
+ else {
+ topicFileMap = Maps.newHashMap();
+ Files.createParentDirs(topicFile);
+ }
+
+ brokerFile.createNewFile();
+ topicFile.createNewFile();
+
+ writeFile(dashboardCollectService.getBrokerMap(), brokerFileMap, brokerFile);
+ writeFile(dashboardCollectService.getTopicMap(), topicFileMap, topicFile);
+ log.debug("Broker Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getBrokerMap().asMap()));
+ log.debug("Topic Collected Data in memory = {}" + JsonUtil.obj2String(dashboardCollectService.getTopicMap().asMap()));
+
+ }
+ catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private void writeFile(LoadingCache<String, List<String>> map, Map<String, List<String>> fileMap,
+ File file) throws IOException {
+ Map<String, List<String>> newMap = map.asMap();
+ Map<String, List<String>> resultMap = Maps.newHashMap();
+ if (fileMap.size() == 0) {
+ resultMap = newMap;
+ }
+ else {
+ for (Map.Entry<String, List<String>> entry : fileMap.entrySet()) {
+ List<String> oldList = entry.getValue();
+ List<String> newList = newMap.get(entry.getKey());
+ resultMap.put(entry.getKey(), appendData(newList, oldList));
+ if (newList == null || newList.size() == 0) {
+ map.put(entry.getKey(), appendData(newList, oldList));
+ }
+ }
+
+ for (Map.Entry<String, List<String>> entry : newMap.entrySet()) {
+ List<String> oldList = fileMap.get(entry.getKey());
+ if (oldList == null || oldList.size() == 0) {
+ resultMap.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ Files.write(JsonUtil.obj2String(resultMap).getBytes(), file);
+ }
+
+ private List<String> appendData(List<String> newTpsList, List<String> oldTpsList) {
+ List<String> result = Lists.newArrayList();
+ if (newTpsList == null || newTpsList.size() == 0) {
+ return oldTpsList;
+ }
+ if (oldTpsList == null || oldTpsList.size() == 0) {
+ return newTpsList;
+ }
+ String oldLastTps = oldTpsList.get(oldTpsList.size() - 1);
+ Long oldLastTimestamp = Long.parseLong(oldLastTps.split(",")[0]);
+ String newFirstTps = newTpsList.get(0);
+ Long newFirstTimestamp = Long.parseLong(newFirstTps.split(",")[0]);
+ if (oldLastTimestamp.longValue() < newFirstTimestamp.longValue()) {
+ result.addAll(oldTpsList);
+ result.addAll(newTpsList);
+ return result;
+ }
+ return newTpsList;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/MonitorTask.java
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/MonitorTask.java b/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/MonitorTask.java
new file mode 100644
index 0000000..0db07be
--- /dev/null
+++ b/rocketmq-console/src/main/java/org/apache/rocketmq/console/task/MonitorTask.java
@@ -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.console.task;
+
+import java.util.Map;
+import javax.annotation.Resource;
+import org.apache.rocketmq.console.model.ConsumerMonitorConfig;
+import org.apache.rocketmq.console.model.GroupConsumeInfo;
+import org.apache.rocketmq.console.service.ConsumerService;
+import org.apache.rocketmq.console.service.MonitorService;
+import org.apache.rocketmq.console.util.JsonUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MonitorTask {
+ private Logger logger = LoggerFactory.getLogger(MonitorTask.class);
+
+ @Resource
+ private MonitorService monitorService;
+
+ @Resource
+ private ConsumerService consumerService;
+
+// @Scheduled(cron = "* * * * * ?")
+ public void scanProblemConsumeGroup() {
+ for (Map.Entry<String, ConsumerMonitorConfig> configEntry : monitorService.queryConsumerMonitorConfig().entrySet()) {
+ GroupConsumeInfo consumeInfo = consumerService.queryGroup(configEntry.getKey());
+ if (consumeInfo.getCount() < configEntry.getValue().getMinCount() || consumeInfo.getDiffTotal() > configEntry.getValue().getMaxDiffTotal()) {
+ logger.info("op=look consumeInfo {}", JsonUtil.obj2String(consumeInfo)); // notify the alert system
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/java/org/apache/rocketmq/console/util/JsonUtil.java
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/java/org/apache/rocketmq/console/util/JsonUtil.java b/rocketmq-console/src/main/java/org/apache/rocketmq/console/util/JsonUtil.java
new file mode 100644
index 0000000..857a7fa
--- /dev/null
+++ b/rocketmq-console/src/main/java/org/apache/rocketmq/console/util/JsonUtil.java
@@ -0,0 +1,156 @@
+/*
+ * 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.console.util;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
+import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("unchecked")
+public class JsonUtil {
+
+ private static Logger logger = LoggerFactory.getLogger(JsonUtil.class);
+ private static ObjectMapper objectMapper = new ObjectMapper();
+
+ private JsonUtil() {
+ }
+
+ static {
+ objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
+ objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ objectMapper.setFilters(new SimpleFilterProvider().setFailOnUnknownId(false));
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+ objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+ }
+
+ public static void writeValue(Writer writer, Object obj) {
+ try {
+ objectMapper.writeValue(writer, obj);
+ }
+ catch (IOException e) {
+ Throwables.propagateIfPossible(e);
+ }
+ }
+
+ public static <T> String obj2String(T src) {
+ if (src == null) {
+ return null;
+ }
+
+ try {
+ return src instanceof String ? (String)src : objectMapper.writeValueAsString(src);
+ }
+ catch (Exception e) {
+ logger.error("Parse Object to String error src=" + src, e);
+ return null;
+ }
+ }
+
+ public static <T> byte[] obj2Byte(T src) {
+ if (src == null) {
+ return null;
+ }
+
+ try {
+ return src instanceof byte[] ? (byte[])src : objectMapper.writeValueAsBytes(src);
+ }
+ catch (Exception e) {
+ logger.error("Parse Object to byte[] error", e);
+ return null;
+ }
+ }
+
+ public static <T> T string2Obj(String str, Class<T> clazz) {
+ if (Strings.isNullOrEmpty(str) || clazz == null) {
+ return null;
+ }
+ str = escapesSpecialChar(str);
+ try {
+ return clazz.equals(String.class) ? (T)str : objectMapper.readValue(str, clazz);
+ }
+ catch (Exception e) {
+ logger.error("Parse String to Object error\nString: {}\nClass<T>: {}\nError: {}", str, clazz.getName(), e);
+ return null;
+ }
+ }
+
+ public static <T> T byte2Obj(byte[] bytes, Class<T> clazz) {
+ if (bytes == null || clazz == null) {
+ return null;
+ }
+ try {
+ return clazz.equals(byte[].class) ? (T)bytes : objectMapper.readValue(bytes, clazz);
+ }
+ catch (Exception e) {
+ logger.error("Parse byte[] to Object error\nbyte[]: {}\nClass<T>: {}\nError: {}", bytes, clazz.getName(), e);
+ return null;
+ }
+ }
+
+ public static <T> T string2Obj(String str, TypeReference<T> typeReference) {
+ if (Strings.isNullOrEmpty(str) || typeReference == null) {
+ return null;
+ }
+ str = escapesSpecialChar(str);
+ try {
+ return (T)(typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
+ }
+ catch (Exception e) {
+ logger.error("Parse String to Object error\nString: {}\nTypeReference<T>: {}\nError: {}", str,
+ typeReference.getType(), e);
+ return null;
+ }
+ }
+
+ public static <T> T byte2Obj(byte[] bytes, TypeReference<T> typeReference) {
+ if (bytes == null || typeReference == null) {
+ return null;
+ }
+ try {
+ return (T)(typeReference.getType().equals(byte[].class) ? bytes : objectMapper.readValue(bytes,
+ typeReference));
+ }
+ catch (Exception e) {
+ logger.error("Parse byte[] to Object error\nbyte[]: {}\nTypeReference<T>: {}\nError: {}", bytes,
+ typeReference.getType(), e);
+ return null;
+ }
+ }
+
+ public static <T> T map2Obj(Map<String, String> map, Class<T> clazz) {
+ String str = obj2String(map);
+ return string2Obj(str, clazz);
+ }
+
+ private static String escapesSpecialChar(String str) {
+ return str.replace("\n", "\\n").replace("\r", "\\r");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/application.properties
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/application.properties b/rocketmq-console/src/main/resources/application.properties
new file mode 100644
index 0000000..de8eb1b
--- /dev/null
+++ b/rocketmq-console/src/main/resources/application.properties
@@ -0,0 +1,16 @@
+server.contextPath=
+server.port=8080
+#spring.application.index=true
+spring.application.name=rocketmq-console
+spring.http.encoding.charset=UTF-8
+spring.http.encoding.enabled=true
+spring.http.encoding.force=true
+logging.config=classpath:logback.xml
+#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
+rocketmq.config.namesrvAddr=
+#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
+rocketmq.config.isVIPChannel=
+#rocketmq-console's data path:dashboard/monitor
+rocketmq.config.dataPath=/tmp/rocketmq-console/data
+#set it false if you don't want use dashboard.default true
+rocketmq.config.enableDashBoardCollect=true
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/logback.xml
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/logback.xml b/rocketmq-console/src/main/resources/logback.xml
new file mode 100644
index 0000000..c1fc79a
--- /dev/null
+++ b/rocketmq-console/src/main/resources/logback.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+ </encoder>
+ </appender>
+
+ <appender name="FILE"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${user.home}/logs/consolelogs/rocketmq-console.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>
+ <timeBasedFileNamingAndTriggeringPolicy
+ class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+ <maxFileSize>104857600</maxFileSize>
+ </timeBasedFileNamingAndTriggeringPolicy>
+ <MaxHistory>10</MaxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %5p %m%n</pattern>
+ <charset class="java.nio.charset.Charset">UTF-8</charset>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="STDOUT" />
+ <appender-ref ref="FILE" />
+ </root>
+
+</configuration>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/static/index.html
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/static/index.html b/rocketmq-console/src/main/resources/static/index.html
new file mode 100644
index 0000000..6e3e6aa
--- /dev/null
+++ b/rocketmq-console/src/main/resources/static/index.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+ ~ 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.
+ -->
+<html lang="en" ng-app="app">
+<head>
+ <meta charset="UTF-8">
+ <title>RocketMq-console-ng</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name='description' content=''>
+ <meta name='keywords' content=''>
+ <!--iOS -->
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+
+ <!-- preLoading -->
+ <link rel="stylesheet" href="style/preLoading/normalize.css">
+ <link rel="stylesheet" href="style/preLoading/main.css">
+ <script src="vendor/preLoading/modernizr-2.6.2.min.js"></script>
+ <!-- preLoading -->
+
+ <link rel="stylesheet" href="vendor/bootstrap/css/bootstrap.min.css">
+ <link rel="stylesheet" href="vendor/bootstrap-material-design/css/bootstrap-material-design.css">
+ <link rel="stylesheet" href="vendor/bootstrap-material-design/css/ripples.css">
+ <link rel="stylesheet" href="vendor/angular/notification/angular-ui-notification.css">
+ <link rel="stylesheet" href="vendor/ng-dialog/ngDialog.min.css">
+ <link rel="stylesheet" href="vendor/ng-dialog/ngDialog-theme-default.css">
+ <link rel="stylesheet" href="vendor/dropdown/jquery.dropdown.css">
+ <link rel="stylesheet" href="vendor/datatimepicker/bootstrap-datetimepicker.min.css">
+ <link rel="stylesheet" href="vendor/font-awesome-4.7.0/css/font-awesome.min.css">
+ <link rel="stylesheet" href="vendor/font-awesome-4.7.0/fonts/fontawesome-webfont.svg">
+ <link rel="stylesheet" type="text/css" href="vendor/chosen/chosen.css"/>
+ <link rel="stylesheet" type="text/css" href="vendor/chosen/chosen-spinner.css"/>
+ <link rel="stylesheet" type="text/css" href="vendor/angular-material/angular-material.min.css"/>
+ <link rel="stylesheet" type="text/css" href="vendor/md-tab/docs.css"/>
+ <link rel="stylesheet" href="style/animate.css">
+ <link rel="stylesheet" href="style/theme.css">
+ <link rel="stylesheet" href="style/app.css">
+
+</head>
+<body ng-controller="AppCtrl">
+<!--[if lte IE 9]>
+<script type="text/javascript">
+ location.href = 'view/pages/un_support_browser.html';
+</script>
+<![endif]-->
+
+<div id="loader-wrapper">
+ <div id="loader"></div>
+
+ <div class="loader-section section-left"></div>
+ <div class="loader-section section-right"></div>
+
+</div>
+<div ng-include="'view/layout/_header.html'"></div>
+<section ng-view></section>
+<div ng-include="'view/layout/_footer.html'"></div>
+<script type="text/javascript" src="vendor/jquery/jquery1.11.3.min.js"></script>
+<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
+<script type="text/javascript" src="vendor/bootstrap-material-design/js/material.min.js"></script>
+<script type="text/javascript" src="vendor/bootstrap-material-design/js/ripples.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-translate.min.js"></script>
+<script type="text/javascript"
+ src="vendor/angular/angular-translate-storage-cookie/angular-translate-storage-cookie.min.js"></script>
+<script type="text/javascript" src="vendor/angular/i18n/angular-locale_zh-cn.js"></script>
+<script type="text/javascript" src="src/i18n/en.js"></script>
+<script type="text/javascript" src="src/i18n/zh.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-cookies.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-animate.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-route.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-ui-router.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-sanitize.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-aria.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-messages.min.js"></script>
+<script type="text/javascript" src="vendor/angular/angular-sanitize.min.js"></script>
+<script type="text/javascript" src="vendor/angular/notification/angular-ui-notification.js"></script>
+<script type="text/javascript" src="vendor/pagination/tm.pagination.js"></script>
+<script type="text/javascript" src="vendor/ng-dialog/ngDialog.min.js"></script>
+<script type="text/javascript" src="vendor/json-bigint/json-bigint.js"></script>
+<script type="text/javascript" src="vendor/dropdown/jquery.dropdown.js"></script>
+<script type="text/javascript" src="vendor/datatimepicker/moment.min.js"></script>
+<script type="text/javascript" src="vendor/datatimepicker/bootstrap-datetimepicker.min.js"></script>
+<script type="text/javascript" src="vendor/datatimepicker/angular-eonasdan-datetimepicker.min.js"></script>
+<script type="text/javascript" src="vendor/chosen/angular-chosen.js"></script>
+<script type="text/javascript" src="vendor/chosen/chosen.jquery.min.js"></script>
+<script type="text/javascript" src='vendor/md-tab/svg-assets-cache.js'></script>
+<script type="text/javascript" src='vendor/angular-material/angular-material.min.js'></script>
+<script type="text/javascript" src="vendor/echarts/echarts.min.js"></script>
+<script type="text/javascript" src="src/app.js"></script>
+<script type="text/javascript" src="src/controller.js?v=201702250025"></script>
+<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/consumer.js?timestamp=6"></script>
+<script type="text/javascript" src="src/producer.js"></script>
+<script type="text/javascript" src="src/message.js"></script>
+<script type="text/javascript" src="src/ops.js?timestamp=7"></script>
+<script type="text/javascript" src="src/remoteApi/remoteApi.js"></script>
+<script type="text/javascript" src="vendor/preLoading/main.js"></script>
+
+</body>
+</html>
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/static/src/app.js
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/static/src/app.js b/rocketmq-console/src/main/resources/static/src/app.js
new file mode 100644
index 0000000..b241010
--- /dev/null
+++ b/rocketmq-console/src/main/resources/static/src/app.js
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+'use strict';
+var app = angular.module('app', [
+ 'ngAnimate',
+ 'ngCookies',
+ 'ngRoute',
+ 'ngDialog',
+ 'ngMaterial',
+ 'ngSanitize',
+ 'material.svgAssetsCache',
+ 'ui-notification',
+ 'tm.pagination',
+ 'ae-datetimepicker',
+ 'localytics.directives',
+ 'pascalprecht.translate'
+]).run(
+ ['$rootScope','$location','$cookies',
+ function ($rootScope,$location,$cookies) {
+ // var filter = function(url){
+ // var outFilterArrs = []
+ // outFilterArrs.push("/login");
+ // outFilterArrs.push("/reg");
+ // outFilterArrs.push("/logout");
+ // outFilterArrs.push("/404");
+ // var flag = false;
+ // $.each(outFilterArrs,function(i,value){
+ // if(url.indexOf(value) > -1){
+ // flag = true;
+ // return false;
+ // }
+ // });
+ // return flag;
+ // }
+
+ // if(angular.isDefined($cookies.get("isLogin")) && $cookies.get("isLogin") == 'true'){
+ // chatApi.login();
+ // }
+
+
+ $rootScope.$on('$routeChangeSuccess', function() {
+ var pathArray = $location.url().split("/");
+ var index = pathArray.indexOf("");
+ if(index >= 0){
+ pathArray.remove(index);
+ }
+ $rootScope.path = pathArray[0];
+
+ //初始化material UI控件
+ $.material.init();
+ });
+
+ $rootScope.$on('$routeChangeStart',function (evt, next,current) {
+ window.clearInterval($rootScope._thread);
+ })
+ }
+ ]
+ ).animation('.view', function () {
+ return {
+ animate: function (element, className, from, to, done) {
+ //styles
+ }
+ }
+ });
+
+app.provider('getDictName', function () {
+
+ var dictList = [];
+
+ this.init = function () {
+ var url = "src/data/dict.json";//无法使用common服务类,地址只能写死
+ var params = {};
+ $.get(url, params, function (ret) {
+ dictList = ret;
+ })
+ }
+
+ this.$get = function () {
+ return function (dictType, value) {
+ for (var i = 0; i < dictList.length; i++) {
+ var dict = dictList[i];
+ if (dict.TYPE == dictType && dict.DICT_VALUE == value) {
+ return dict.DICT_NAME;
+ }
+ }
+ }
+ }
+})
+
+app.config(['$routeProvider', '$httpProvider','$cookiesProvider','getDictNameProvider','$sceProvider','$translateProvider','$mdThemingProvider',
+ function ($routeProvider, $httpProvider ,$cookiesProvider,getDictNameProvider,$sceProvider,$translateProvider,$mdThemingProvider) {
+ //关闭html校验,存在安全隐患,但目前没问题,使用ng-bind-html需要注意,防止跨站攻击
+ $sceProvider.enabled(false);
+ //前端字典项目初始化
+ getDictNameProvider.init();
+
+ //init angular
+ $mdThemingProvider.theme('default')
+ .primaryPalette('pink')
+ .accentPalette('light-blue');
+
+
+ //设置ajax默认配置
+ $.ajaxSetup({
+ type: "POST",
+ contentType: 'application/json',
+ cache:false,
+ timeout : 5000, //超时时间设置,单位毫秒
+ converters:{
+ "text json": JSONbig.parse
+ }
+ });
+
+ $httpProvider.defaults.cache = false;
+
+ $routeProvider.when('/', {
+ templateUrl: 'view/pages/index.html',
+ controller:'dashboardCtrl'
+ }).when('/cluster', {
+ templateUrl: 'view/pages/cluster.html',
+ controller:'clusterController'
+ }).when('/topic', {
+ templateUrl: 'view/pages/topic.html',
+ controller:'topicController'
+ }).when('/consumer', {
+ templateUrl: 'view/pages/consumer.html',
+ controller:'consumerController'
+ }).when('/producer', {
+ templateUrl: 'view/pages/producer.html',
+ controller:'producerController'
+ }).when('/message', {
+ templateUrl: 'view/pages/message.html',
+ controller:'messageController'
+ }).when('/ops', {
+ templateUrl: 'view/pages/ops.html',
+ controller:'opsController'
+ }).when('/404', {
+ templateUrl: '404'
+ }).otherwise('404');
+
+ $translateProvider.translations('en',en);
+ $translateProvider.translations('zh',zh);
+ $translateProvider.preferredLanguage('en');
+ $translateProvider.useCookieStorage();
+// $translateProvider.useSanitizeValueStrategy('sanitize');
+
+ }]);
+
+app.filter('range', function() {
+ return function(input, range) {
+ var total = parseInt(range.totalPage) + 1;
+ var count = 5;
+ for (var i = range.start; i<total; i++) {
+ if(count > 0){
+ input.push(i);
+ count -- ;
+ }else {
+ break;
+ }
+ }
+ return input;
+ };
+});
+
+
+app.filter('dict',['getDictName',function(getDictName){
+ return function(value,type){
+ return getDictName(type,value);
+ }
+}])
+
+/**
+ * 数组扩展方法,移除数组中某一元素或某一段元素
+ * @param from 需要移除元素的索引开始值(只传一个参数表示单独移除该元素)
+ * @param to 需要移除元素的索引结束值
+ * @returns {*}
+ */
+Array.prototype.remove = function(from, to) {
+ var rest = this.slice((to || from) + 1 || this.length);
+ this.length = from < 0 ? this.length + from : from;
+ return this.push.apply(this, rest);
+};
+
+/**
+ * 根据元素值查询数组中元素的索引
+ * @param val
+ * @returns {number}
+ */
+Array.prototype.indexOf = function(val) {
+ for (var i = 0; i < this.length; i++) {
+ if (this[i] == val) return i;
+ }
+ return -1;
+};
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/static/src/cluster.js
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/static/src/cluster.js b/rocketmq-console/src/main/resources/static/src/cluster.js
new file mode 100644
index 0000000..6f1baee
--- /dev/null
+++ b/rocketmq-console/src/main/resources/static/src/cluster.js
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+app.controller('clusterController', ['$scope','$location','$http','Notification','remoteApi','tools', function ($scope,$location,$http,Notification,remoteApi,tools) {
+ $scope.clusterMap = {};//cluster:brokerNameList
+ $scope.brokerMap = {};//brokerName:{id:addr}
+ $scope.brokerDetail = {};//{brokerName,id:detail}
+ $scope.clusterNames = [];
+ $scope.selectedCluster = "";
+ var callback = function (resp) {
+ if (resp.status == 0) {
+ $scope.clusterMap = resp.data.clusterInfo.clusterAddrTable;
+ $scope.brokerMap = resp.data.clusterInfo.brokerAddrTable;
+ $scope.brokerDetail = resp.data.brokerServer;
+ $.each($scope.clusterMap,function(clusterName,clusterBrokersNames){
+ $scope.clusterNames.push(clusterName);
+ });
+ if ($scope.clusterNames.length > 0) {
+ $scope.selectedCluster = $scope.clusterNames[0];
+ }
+ $scope.brokers = tools.generateBrokerMap($scope.brokerDetail,$scope.clusterMap,$scope.brokerMap);
+ $scope.switchCluster();
+ }else{
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ }
+
+ remoteApi.queryClusterList(callback);
+
+ $scope.switchCluster = function(){
+ $scope.instances = $scope.brokers[$scope.selectedCluster];
+ }
+
+ $scope.showDetail = function (brokerName,index) {
+ $scope.detail = $scope.brokerDetail[brokerName][index];
+ $scope.brokerName = brokerName;
+ $scope.index = index;
+ $(".brokerModal").modal();
+ }
+
+ $scope.showConfig = function (brokerAddr,brokerName,index) {
+ $scope.brokerAddr = brokerAddr;
+ $scope.brokerName = brokerName;
+ $scope.index = index;
+ $http({
+ method: "GET",
+ url: "cluster/brokerConfig.query",
+ params:{brokerAddr:brokerAddr}
+ }).success(function (resp) {
+ if (resp.status == 0) {
+ $scope.brokerConfig = resp.data;
+ $(".configModal").modal();
+ }else{
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ })
+ }
+}])
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/static/src/consumer.js
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/static/src/consumer.js b/rocketmq-console/src/main/resources/static/src/consumer.js
new file mode 100644
index 0000000..cd093d4
--- /dev/null
+++ b/rocketmq-console/src/main/resources/static/src/consumer.js
@@ -0,0 +1,350 @@
+/*
+ * 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('consumerController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
+ $scope.paginationConf = {
+ currentPage: 1,
+ totalItems: 0,
+ itemsPerPage: 10,
+ pagesLength: 15,
+ perPageOptions: [10],
+ rememberPerPage: 'perPageItems',
+ onChange: function () {
+ $scope.showConsumerGroupList(this.currentPage,this.totalItems);
+ }
+ };
+ $scope.sortKey = null;
+ $scope.sortOrder=1;
+ $scope.intervalProcessSwitch = false;
+ $scope.intervalProcess = null;
+ $scope.allConsumerGrouopList = [];
+ $scope.consumerGroupShowList = [];
+ $scope.sortByKey = function (key) {
+ $scope.paginationConf.currentPage=1;
+ $scope.sortOrder = -$scope.sortOrder;
+ $scope.sortKey = key;
+ $scope.doSort();
+ };
+
+ $scope.doSort = function (){// todo how to change this fe's code ? (it's dirty)
+ if($scope.sortKey == 'diffTotal'){
+ $scope.allConsumerGrouopList.sort(function(a,b) {return (a.diffTotal > b.diffTotal) ? $scope.sortOrder : ((b.diffTotal > a.diffTotal) ? -$scope.sortOrder : 0);} );
+ }
+ if($scope.sortKey == 'group'){
+ $scope.allConsumerGrouopList.sort(function(a,b) {return (a.group > b.group) ? $scope.sortOrder : ((b.group > a.group) ? -$scope.sortOrder : 0);} );
+ }
+ if($scope.sortKey == 'count'){
+ $scope.allConsumerGrouopList.sort(function(a,b) {return (a.count > b.count) ? $scope.sortOrder : ((b.count > a.count) ? -$scope.sortOrder : 0);} );
+ }
+ if($scope.sortKey == 'consumeTps'){
+ $scope.allConsumerGrouopList.sort(function(a,b) {return (a.consumeTps > b.consumeTps) ? $scope.sortOrder : ((b.consumeTps > a.consumeTps) ? -$scope.sortOrder : 0);} );
+ }
+ $scope.filterList($scope.paginationConf.currentPage)
+ };
+ $scope.refreshConsumerData = function () {
+ $http({
+ method: "GET",
+ url: "consumer/groupList.query"
+ }).success(function (resp) {
+ if(resp.status ==0){
+ $scope.allConsumerGrouopList = resp.data;
+ console.log($scope.allConsumerGrouopList);
+ console.log(JSON.stringify(resp));
+ $scope.showConsumerGroupList($scope.paginationConf.currentPage,$scope.allConsumerGrouopList.length);
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ };
+ $scope.monitor = function(consumerGroupName){
+ $http({
+ method: "GET",
+ url: "monitor/consumerMonitorConfigByGroupName.query",
+ params:{consumeGroupName:consumerGroupName}
+ }).success(function (resp) {
+ // if(resp.status ==0){
+ ngDialog.open({
+ template: 'consumerMonitorDialog',
+ controller: 'consumerMonitorDialogController',
+ data:{consumerGroupName:consumerGroupName,data:resp.data}
+ });
+ // }else {
+ // Notification.error({message: resp.errMsg, delay: 2000});
+ // }
+ });
+ };
+
+
+ $scope.$watch('intervalProcessSwitch', function () {
+ if ($scope.intervalProcess != null) {
+ clearInterval($scope.intervalProcess);
+ $scope.intervalProcess = null;
+ }
+ if ($scope.intervalProcessSwitch) {
+ $scope.intervalProcess = setInterval($scope.refreshConsumerData, 10000);
+ }
+ });
+
+
+ $scope.refreshConsumerData();
+ $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.allConsumerGrouopList.forEach(function(element) {
+ console.log(element)
+ if (element.group.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.consumerGroupShowList = canShowList.slice(from, to);
+ };
+
+
+ $scope.showConsumerGroupList = function (currentPage,totalItem) {
+ var perPage = $scope.paginationConf.itemsPerPage;
+ var from = (currentPage - 1) * perPage;
+ var to = (from + perPage)>totalItem?totalItem:from + perPage;
+ $scope.consumerGroupShowList = $scope.allConsumerGrouopList.slice(from, to);
+ $scope.paginationConf.totalItems = totalItem ;
+ console.log($scope.consumerGroupShowList)
+ console.log($scope.paginationConf.totalItems)
+ $scope.doSort()
+ };
+ $scope.openAddDialog = function () {
+ $scope.openCreateOrUpdateDialog(null);
+ };
+ $scope.openCreateOrUpdateDialog = function(request){
+ var bIsUpdate = true;
+ if(request == null){
+ request = [{
+ brokerNameList: [],
+ subscriptionGroupConfig: {
+ groupName: "",
+ consumeEnable: true,
+ consumeFromMinEnable: true,
+ consumeBroadcastEnable: true,
+ retryQueueNums: 1,
+ retryMaxTimes: 16,
+ brokerId: 0,
+ whichBrokerWhenConsumeSlowly: 1
+ }
+ }];
+ bIsUpdate = false;
+ }
+ console.log(request);
+ $http({
+ method: "GET",
+ url: "cluster/list.query"
+ }).success(function (resp) {
+ if(resp.status ==0){
+ console.log(resp);
+ ngDialog.open({
+ template: 'consumerModifyDialog',
+ controller: 'consumerModifyDialogController',
+ data:{
+ consumerRequestList:request,
+ allClusterNameList:Object.keys(resp.data.clusterInfo.clusterAddrTable),
+ allBrokerNameList:Object.keys(resp.data.brokerServer),
+ bIsUpdate:bIsUpdate
+ }
+ });
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ };
+ $scope.detail = function(consumerGroupName){
+ $http({
+ method: "GET",
+ url: "consumer/queryTopicByConsumer.query",
+ params:{consumerGroup:consumerGroupName}
+ }).success(function (resp) {
+ if(resp.status ==0){
+ console.log(resp);
+ ngDialog.open({
+ template: 'consumerTopicViewDialog',
+ controller: 'consumerTopicViewDialogController',
+ data:{consumerGroupName:consumerGroupName,data:resp.data}
+ });
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ };
+
+ $scope.client = function(consumerGroupName){
+ $http({
+ method: "GET",
+ url: "consumer/consumerConnection.query",
+ params:{consumerGroup:consumerGroupName}
+ }).success(function (resp) {
+ if(resp.status ==0){
+ console.log(resp);
+ ngDialog.open({
+ template: 'clientInfoDialog',
+ // controller: 'addTopicDialogController',
+ data:{data:resp.data,consumerGroupName:consumerGroupName}
+ });
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ };
+ $scope.updateConfigDialog = function(consumerGroupName){
+ $http({
+ method: "GET",
+ url: "consumer/examineSubscriptionGroupConfig.query",
+ params:{consumerGroup:consumerGroupName}
+ }).success(function (resp) {
+ if(resp.status ==0){
+ console.log(resp);
+ $scope.openCreateOrUpdateDialog(resp.data);
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+
+
+ };
+ $scope.delete = function(consumerGroupName){
+ $http({
+ method: "GET",
+ url: "consumer/fetchBrokerNameList.query",
+ params:{
+ consumerGroup:consumerGroupName
+ }
+ }).success(function (resp) {
+ if(resp.status ==0){
+ console.log(resp);
+
+ ngDialog.open({
+ template: 'deleteConsumerDialog',
+ controller: 'deleteConsumerDialogController',
+ data:{
+ // allClusterList:Object.keys(resp.data.clusterInfo.clusterAddrTable),
+ allBrokerNameList:resp.data,
+ consumerGroupName:consumerGroupName
+ }
+ });
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ }
+
+}])
+module.controller('consumerMonitorDialogController', function ($scope, ngDialog, $http,Notification) {
+ $scope.createOrUpdateConsumerMonitor = function () {
+ $http({
+ method: "POST",
+ url: "monitor/createOrUpdateConsumerMonitor.do",
+ params:{consumeGroupName:$scope.ngDialogData.consumerGroupName,
+ minCount:$scope.ngDialogData.data.minCount,
+ maxDiffTotal:$scope.ngDialogData.data.maxDiffTotal}
+ }).success(function (resp) {
+ if(resp.status ==0){
+ Notification.info({message: "delete success!", delay: 2000});
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ }
+ }
+);
+
+
+module.controller('deleteConsumerDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
+ $scope.selectedClusterList = [];
+ $scope.selectedBrokerNameList = [];
+ $scope.delete = function () {
+ console.log($scope.selectedClusterList);
+ console.log($scope.selectedBrokerNameList);
+ console.log($scope.ngDialogData.consumerGroupName);
+ $http({
+ method: "POST",
+ url: "consumer/deleteSubGroup.do",
+ data:{groupName:$scope.ngDialogData.consumerGroupName,
+ brokerNameList:$scope.selectedBrokerNameList}
+ }).success(function (resp) {
+ if(resp.status ==0){
+ Notification.info({message: "delete success!", delay: 2000});
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ }
+ }]
+);
+
+module.controller('consumerModifyDialogController', ['$scope', 'ngDialog', '$http','Notification',function ($scope, ngDialog, $http,Notification) {
+ $scope.postConsumerRequest = function (consumerRequest) {
+ var request = JSON.parse(JSON.stringify(consumerRequest));
+ console.log(request);
+ $http({
+ method: "POST",
+ url: "consumer/createOrUpdate.do",
+ data:request
+ }).success(function (resp) {
+ if(resp.status ==0){
+ Notification.info({message: "update success!", delay: 2000});
+ }else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ }
+ }]
+);
+
+module.controller('consumerTopicViewDialogController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
+ $scope.consumerRunningInfo = function (consumerGroup, clientId, jstack) {
+ $http({
+ method: "GET",
+ url: "consumer/consumerRunningInfo.query",
+ params: {
+ consumerGroup: consumerGroup,
+ clientId: clientId,
+ jstack: jstack
+ }
+ }).success(function (resp) {
+ if (resp.status == 0) {
+ ngDialog.open({
+ template: 'consumerClientDialog',
+ data:{consumerClientInfo:resp.data,
+ clientId:clientId}
+ });
+ } else {
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ });
+ };
+ }]
+);
+
+
+
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/static/src/controller.js
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/static/src/controller.js b/rocketmq-console/src/main/resources/static/src/controller.js
new file mode 100644
index 0000000..8d5d5d2
--- /dev/null
+++ b/rocketmq-console/src/main/resources/static/src/controller.js
@@ -0,0 +1,557 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+app.controller('AppCtrl', ['$scope','$rootScope','$cookies','$location','$translate', function ($scope,$rootScope,$cookies,$location,$translate) {
+ $scope.changeTranslate = function(langKey){
+ $translate.use(langKey);
+ }
+}]);
+
+app.controller('dashboardCtrl', ['$scope','$rootScope','$translate','$filter','Notification','remoteApi','tools', function ($scope,$rootScope,$translate,$filter,Notification,remoteApi,tools) {
+
+ $scope.barChart = echarts.init(document.getElementById('main'));
+ $scope.lineChart = echarts.init(document.getElementById('line'));
+ $scope.topicBarChart = echarts.init(document.getElementById('topicBar'));
+ $scope.topicLineChart = echarts.init(document.getElementById('topicLine'));
+ $scope.timepickerOptions ={format: 'YYYY-MM-DD', showClear: true};
+ $scope.topicNames = [];
+
+ $translate('BROKER').then(function (broker) {
+ $scope.BROKER_TITLE = broker;
+ initBrokerBarChart();
+ initBrokerLineChart();
+ }, function (translationId) {
+ $scope.BROKER_TITLE = translationId;
+ });
+
+ $translate('TOPIC').then(function (topic) {
+ $scope.TOPIC_TITLE = topic;
+ initTopicBarChart();
+ initTopicLineChart();
+ }, function (translationId) {
+ $scope.TOPIC_TITLE = translationId;
+ });
+
+ var initBrokerBarChart = function(){
+ $scope.barChart.setOption({
+ title: {
+ text:$scope.BROKER_TITLE + ' TOP 10'
+ },
+ tooltip: {},
+ legend: {
+ data:['TotalMsg']
+ },
+ axisPointer : {
+ type : 'shadow'
+ },
+ xAxis: {
+ data: [],
+ axisLabel: {
+ inside: false,
+ textStyle: {
+ color: '#000000'
+ },
+ rotate: 0,
+ interval:0
+ },
+ axisTick: {
+ show: true
+ },
+ axisLine: {
+ show: true
+ },
+ z: 10
+ },
+ yAxis: {
+ type: 'value',
+ boundaryGap: [0, '100%'],
+ axisLabel: {
+ formatter: function(value){
+ return value.toFixed(2);
+ }
+ },
+ splitLine: {
+ show: true
+ }
+ },
+ series: [{
+ name: 'TotalMsg',
+ type: 'bar',
+ data: []
+ }]
+ })
+ }
+
+ var initBrokerLineChart = function(){
+ $scope.lineChart.setOption({
+ title: {
+ text: $scope.BROKER_TITLE + ' 5min trend'
+ },
+ toolbox: {
+ feature: {
+ dataZoom: {
+ yAxisIndex: 'none'
+ },
+ restore: {},
+ saveAsImage: {}
+ }
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ animation: false
+ }
+ },
+ yAxis: {
+ type: 'value',
+ boundaryGap: [0, '80%'],
+ axisLabel: {
+ formatter: function(value){
+ return value.toFixed(2);
+ }
+ },
+ splitLine: {
+ show: true
+ }
+ },
+ dataZoom: [{
+ type: 'inside',
+ start: 90,
+ end: 100
+ }, {
+ start: 0,
+ end: 10,
+ handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
+ handleSize: '80%',
+ handleStyle: {
+ color: '#fff',
+ shadowBlur: 3,
+ shadowColor: 'rgba(0, 0, 0, 0.6)',
+ shadowOffsetX: 2,
+ shadowOffsetY: 2
+ }
+ }],
+ legend: {
+ data: [],
+ top:30
+ },
+ xAxis: {
+ type: 'time',
+ boundaryGap: false,
+ data: []
+ },
+ series: []
+ })
+
+ }
+
+ var initTopicBarChart = function(){
+ $scope.topicBarChart.setOption({
+ title: {
+ text:$scope.TOPIC_TITLE + ' TOP 10'
+ },
+ tooltip: {},
+ legend: {
+ data:['TotalMsg']
+ },
+ axisPointer : {
+ type : 'shadow'
+ },
+ xAxis: {
+ data: [],
+ axisLabel: {
+ inside: false,
+ textStyle: {
+ color: '#000000'
+ },
+ rotate: 0,
+ interval:0
+ },
+ axisTick: {
+ show: true
+ },
+ axisLine: {
+ show: true
+ },
+ z: 10
+ },
+ yAxis: {
+ type: 'value',
+ boundaryGap: [0, '100%'],
+ axisLabel: {
+ formatter: function(value){
+ return value.toFixed(2);
+ }
+ },
+ splitLine: {
+ show: true
+ }
+ },
+ series: [{
+ name: 'TotalMsg',
+ type: 'bar',
+ data: []
+ }]
+ })
+ }
+
+ var initTopicLineChart = function(){
+ var _option = {
+ baseOption:{
+ title: {
+ text: $scope.TOPIC_TITLE + ' 5min trend'
+ },
+ toolbox: {
+ feature: {
+ dataZoom: {
+ yAxisIndex: 'none'
+ },
+ restore: {},
+ saveAsImage: {}
+ }
+ },
+ grid:{
+ top:100
+ },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ animation: false
+ }
+ },
+ yAxis: {
+ type: 'value',
+ boundaryGap: [0, '80%'],
+ axisLabel: {
+ formatter: function(value){
+ return value.toFixed(2);
+ }
+ },
+ splitLine: {
+ show: true
+ }
+ },
+ dataZoom: [{
+ type: 'inside',
+ start: 90,
+ end: 100
+ }, {
+ start: 0,
+ end: 10,
+ handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
+ handleSize: '80%',
+ handleStyle: {
+ color: '#fff',
+ shadowBlur: 3,
+ shadowColor: 'rgba(0, 0, 0, 0.6)',
+ shadowOffsetX: 2,
+ shadowOffsetY: 2
+ }
+ }],
+ legend:{
+ data:[],
+ top:30
+ },
+ xAxis: {
+ type: 'time',
+ boundaryGap: false,
+ data: []
+ },
+ series: []
+ }
+ }
+ $scope.topicLineChart.setOption(_option)
+
+ }
+
+ var getBrokerBarChartOp = function(xAxisData,data){
+ // 指定图表的配置项和数据
+ var option = {
+ xAxis: {
+ data: xAxisData,
+ axisLabel: {
+ inside: false,
+ textStyle: {
+ color: '#000000'
+ },
+ rotate: 0,
+ interval:0
+ },
+ axisTick: {
+ show: true
+ },
+ axisLine: {
+ show: true
+ },
+ z: 10
+ },
+ series: [{
+ name: 'TotalMsg',
+ type: 'bar',
+ data: data
+ }]
+ };
+
+ $scope.barChart.setOption(option);
+ }
+
+ var callback = function (resp) {
+ $scope.barChart.hideLoading();
+ if (resp.status == 0) {
+ var clusterAddrTable = resp.data.clusterInfo.clusterAddrTable;
+ var brokerMap = resp.data.clusterInfo.brokerAddrTable;
+ var brokerDetail = resp.data.brokerServer;
+ var clusterMap = tools.generateBrokerMap(brokerDetail,clusterAddrTable,brokerMap);
+ $scope.brokerArray = [];
+ $.each(clusterMap,function(clusterName,brokers){
+ $.each(brokers,function(i,broker){
+ $scope.brokerArray.push(broker)
+ })
+ });
+
+ //sort the brokerArray
+ $scope.brokerArray.sort(function(firstBroker,lastBroker){
+ var firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow);
+ var lastTotalMsg = parseFloat(lastBroker.msgGetTotalTodayNow);
+ return lastTotalMsg-firstTotalMsg;
+ });
+
+ var xAxisData = [],
+ data = [];
+
+ $.each($scope.brokerArray,function(i,broker){
+ if(i > 9){
+ return false;
+ }
+ xAxisData.push(broker.brokerName + ":" + broker.index);
+ data.push(broker.msgGetTotalTodayNow);
+ })
+ getBrokerBarChartOp(xAxisData,data);
+ }else{
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ }
+
+ $scope.barChart.showLoading();
+ remoteApi.queryClusterList(callback);
+
+ $scope.topicBarChart.showLoading();
+ remoteApi.queryTopicCurrentData(function(resp){
+ $scope.topicBarChart.hideLoading();
+ if (resp.status == 0) {
+ var topicList = resp.data;
+ topicList.sort(function(first,last){
+ var firstTotalMsg = parseFloat(first.split(",")[1]);
+ var lastTotalMsg = parseFloat(last.split(",")[1]);
+ return lastTotalMsg-firstTotalMsg;
+ })
+
+ var xAxisData = [];
+ var data = [];
+ $.each(topicList,function (i,currentData) {
+ var currentArray = currentData.split(",");
+ $scope.topicNames.push(currentArray[0]);
+ if(!angular.isDefined($scope.selectedTopic)){
+ $scope.selectedTopic = currentArray[0];
+ }
+ })
+ $.each(topicList,function (i, currentData) {
+ if(i > 9){
+ return false;
+ }
+ var currentArray = currentData.split(",");
+ xAxisData.push(currentArray[0]);
+ data.push(currentArray[1]);
+ })
+ // 指定图表的配置项和数据
+ var option = {
+ xAxis: {
+ data: xAxisData,
+ axisLabel: {
+ inside: false,
+ textStyle: {
+ color: '#000000'
+ },
+ rotate: 60,
+ interval:0
+ },
+ axisTick: {
+ show: true
+ },
+ axisLine: {
+ show: true
+ },
+ z: 10
+ },
+ series: [{
+ name: 'TotalMsg',
+ type: 'bar',
+ data: data
+ }]
+ };
+ $scope.topicBarChart.setOption(option);
+ queryLineData();
+ }else{
+ Notification.error({message: resp.errMsg, delay: 2000});
+ }
+ })
+
+
+ var getBrokerLineChart = function(legend,data){
+ var series = [];
+ var xAxisData = [];
+ var flag = true;
+ var i = 0;
+ $.each(data,function(key,value){
+ // if(i > 9 ){
+ // return false;
+ // }
+ var _tps = [];
+ $.each(value,function(i,tpsValue){
+ var tpsArray = tpsValue.split(",");
+ if(flag){
+ xAxisData.push($filter('date')(tpsArray[0], "HH:mm:ss"));
+ }
+ _tps.push(tpsArray[1]);
+ })
+ flag = false;
+ var _series = {
+ name:key,
+ type:'line',
+ smooth:true,
+ symbol: 'none',
+ sampling: 'average',
+ data: _tps
+ }
+ series.push(_series);
+ i++
+ })
+
+ var option = {
+ legend: {
+ data: legend
+ },
+ color: ["#FF0000", "#00BFFF", "#FF00FF", "#1ce322", "#000000", '#EE7942'],
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: xAxisData
+ },
+ series: series
+ };
+ return option;
+ }
+
+ var getTopicLineChart = function(legend,data){
+ var series = [];
+ var xAxisData = [];
+ var flag = true;
+ var i = 0;
+ $.each(data,function(key,value){
+ var _tps = [];
+ $.each(value,function(i,tpsValue){
+ var tpsArray = tpsValue.split(",");
+ if(flag){
+ xAxisData.push($filter('date')(tpsArray[0], "HH:mm:ss"));
+ }
+ _tps.push(tpsArray[3]);
+ })
+ flag = false;
+ var _series = {
+ name:key,
+ type:'line',
+ smooth:true,
+ symbol: 'none',
+ sampling: 'average',
+ data: _tps
+ }
+ series.push(_series);
+ i++
+ })
+
+ var option = {
+ baseOption:{
+ legend: {
+ data: legend
+ },
+ // color: ["#FF0000", "#00BFFF", "#FF00FF", "#1ce322", "#000000", '#EE7942'],
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: xAxisData
+ },
+ series: series
+ },
+ media:[
+ {
+ query:{},
+ option:{
+
+ }
+ }
+ ]
+
+ };
+ return option;
+ }
+
+
+ var queryLineData = function () {
+ var _date;
+ if($scope.date != null){
+ _date = $filter('date')($scope.date.valueOf(), "yyyy-MM-dd");
+ }else{
+ _date = $filter('date')(new Date(), "yyyy-MM-dd");
+ }
+ // $scope.lineChart.showLoading();
+ remoteApi.queryBrokerHisData(_date,function(resp){
+ // $scope.lineChart.hideLoading();
+ if (resp.status == 0) {
+ var _data = {}
+ var _xAxisData = [];
+ $.each(resp.data,function(address,values){
+ _data[address] = values;
+ _xAxisData.push(address);
+ })
+ $scope.lineChart.setOption(getBrokerLineChart(_xAxisData,_data));
+ }else{
+ Notification.error({message: "" + resp.errMsg, delay: 2000});
+ }
+ })
+
+ $scope.topicLineChart.showLoading();
+ remoteApi.queryTopicHisData(_date,$scope.selectedTopic,function (resp) {
+ $scope.topicLineChart.hideLoading();
+ if (resp.status == 0) {
+ var _data = {};
+ _data[$scope.selectedTopic] = resp.data;
+ var _xAxisData = $scope.selectedTopic;
+ $scope.topicLineChart.setOption(getTopicLineChart(_xAxisData,_data));
+ }else{
+ Notification.error({message: "" + resp.errMsg, delay: 2000});
+ }
+
+ })
+
+ }
+
+ //router after will clear this thread
+ $rootScope._thread = setInterval( queryLineData, tools.dashboardRefreshTime);
+
+
+}]);
+
+
http://git-wip-us.apache.org/repos/asf/incubator-rocketmq-externals/blob/e218eef9/rocketmq-console/src/main/resources/static/src/data/dict.json
----------------------------------------------------------------------
diff --git a/rocketmq-console/src/main/resources/static/src/data/dict.json b/rocketmq-console/src/main/resources/static/src/data/dict.json
new file mode 100644
index 0000000..defdab3
--- /dev/null
+++ b/rocketmq-console/src/main/resources/static/src/data/dict.json
@@ -0,0 +1,4 @@
+[
+ {"TYPE":"DEMO_TYPE","DICT_VALUE":"0","DICT_NAME":"test1"},
+ {"TYPE":"DEMO_TYPE","DICT_VALUE":"1","DICT_NAME":"test2"}
+]
\ No newline at end of file