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
