Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508402d8ec | ||
|
|
eb3e573b22 | ||
|
|
5e7fbcf078 | ||
|
|
3fb35d1fcc | ||
|
|
538d54cae0 | ||
|
|
78b02f80ba | ||
|
|
f9ec890e1d | ||
|
|
af1bb2ccbd | ||
|
|
714e9a56a3 | ||
|
|
88d0a60182 | ||
|
|
05c52cd672 | ||
|
|
586b37caa0 | ||
|
|
d8aa3d64df | ||
|
|
13d8fd55c8 | ||
|
|
4133981048 | ||
|
|
2f0b18b005 | ||
|
|
44134ce0d6 | ||
|
|
5f21e5a728 | ||
|
|
d5079a1b75 | ||
|
|
656dfc2285 | ||
|
|
99be2d704f | ||
|
|
d071e31106 | ||
|
|
55b34d08dd | ||
|
|
7a29e58453 | ||
|
|
8892b5250e | ||
|
|
75e53a9617 | ||
|
|
7294aba59f | ||
|
|
a8c779675a | ||
|
|
facae65f61 | ||
|
|
0c6475b063 | ||
|
|
92d6214f4f | ||
|
|
6ad29b9565 | ||
|
|
f3b64ca463 | ||
|
|
9340e07662 | ||
|
|
50482c40d5 | ||
|
|
12ebc32cec | ||
|
|
215602bb84 | ||
|
|
5355c5c1f3 | ||
|
|
e13d77c81d | ||
|
|
103db39460 | ||
|
|
750da7c9d7 | ||
|
|
0fea002142 | ||
|
|
7163c74cba | ||
|
|
2fb3aa1c14 | ||
|
|
dc8604ad81 | ||
|
|
9c67afd170 | ||
|
|
bd48bc6a3d | ||
|
|
b75e630bac | ||
|
|
ebd4e4735d | ||
|
|
b3ad6a71ca | ||
|
|
91e2189864 | ||
|
|
ddd5d1b892 | ||
|
|
8aa877071c | ||
|
|
efa253fac8 | ||
|
|
3744c0e97d | ||
|
|
d510640e43 | ||
|
|
d7986ad8dd | ||
|
|
fbc4d4a540 | ||
|
|
bc32c71048 | ||
|
|
c4910964db | ||
|
|
1bc725bd62 | ||
|
|
34b7c6746b | ||
|
|
20d5b27bb6 | ||
|
|
a4abb4069d | ||
|
|
c73cfce780 | ||
|
|
dfb9b6136b | ||
|
|
341bd58d51 | ||
|
|
4386181304 | ||
|
|
fb21d8135c | ||
|
|
b4580277a9 | ||
|
|
045f65204b |
@@ -1,39 +1,4 @@
|
||||
|
||||
## v3.1.0
|
||||
|
||||
**Bug修复**
|
||||
- 修复重置 Group Offset 的提示信息中,缺少Dead状态也可进行重置的描述;
|
||||
- 修复新建 Topic 后,立即查看 Topic Messages 信息时,会提示 Topic 不存在的问题;
|
||||
- 修复副本变更时,优先副本选举未被正常处罚执行的问题;
|
||||
- 修复 git 目录不存在时,打包不能正常进行的问题;
|
||||
- 修复 KRaft 模式的 Kafka 集群,JMX PORT 显示 -1 的问题;
|
||||
|
||||
|
||||
**体验优化**
|
||||
- 优化Cluster、Broker、Topic、Group的健康分为健康状态;
|
||||
- 去除健康巡检配置中的权重信息;
|
||||
- 错误提示页面展示优化;
|
||||
- 前端打包编译依赖默认使用 taobao 镜像;
|
||||
- 重新设计优化导航栏的 icon ;
|
||||
|
||||
|
||||
**新增**
|
||||
- 个人头像下拉信息中,新增产品版本信息;
|
||||
- 多集群列表页面,新增集群健康状态分布信息;
|
||||
|
||||
|
||||
**Kafka ZK 部分 (v3.1.0版本正式发布)**
|
||||
- 新增 ZK 集群的指标大盘信息;
|
||||
- 新增 ZK 集群的服务状态概览信息;
|
||||
- 新增 ZK 集群的服务节点列表信息;
|
||||
- 新增 Kafka 在 ZK 的存储数据查看功能;
|
||||
- 新增 ZK 的健康巡检及健康状态计算;
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## v3.0.1
|
||||
|
||||
**Bug修复**
|
||||
|
||||
@@ -8,19 +8,7 @@
|
||||
|
||||
暂无
|
||||
|
||||
### 6.2.1、升级至 `v3.1.0` 版本
|
||||
|
||||
```sql
|
||||
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_BRAIN_SPLIT', '{ \"value\": 1} ', 'ZK 脑裂', 'admin');
|
||||
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_OUTSTANDING_REQUESTS', '{ \"amount\": 100, \"ratio\":0.8} ', 'ZK Outstanding 请求堆积数', 'admin');
|
||||
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_WATCH_COUNT', '{ \"amount\": 100000, \"ratio\": 0.8 } ', 'ZK WatchCount 数', 'admin');
|
||||
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_ALIVE_CONNECTIONS', '{ \"amount\": 10000, \"ratio\": 0.8 } ', 'ZK 连接数', 'admin');
|
||||
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_APPROXIMATE_DATA_SIZE', '{ \"amount\": 524288000, \"ratio\": 0.8 } ', 'ZK 数据大小(Byte)', 'admin');
|
||||
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_SENT_RATE', '{ \"amount\": 500000, \"ratio\": 0.8 } ', 'ZK 发包数', 'admin');
|
||||
|
||||
```
|
||||
|
||||
### 6.2.2、升级至 `v3.0.1` 版本
|
||||
### 6.2.1、升级至 `v3.0.1` 版本
|
||||
|
||||
**ES 索引模版**
|
||||
```bash
|
||||
@@ -154,8 +142,10 @@ CREATE TABLE `ks_km_group` (
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.2.3、升级至 `v3.0.0` 版本
|
||||
|
||||
### 6.2.2、升级至 `v3.0.0` 版本
|
||||
|
||||
**SQL 变更**
|
||||
|
||||
@@ -167,7 +157,7 @@ ADD COLUMN `zk_properties` TEXT NULL COMMENT 'ZK配置' AFTER `jmx_properties`;
|
||||
---
|
||||
|
||||
|
||||
### 6.2.4、升级至 `v3.0.0-beta.2`版本
|
||||
### 6.2.3、升级至 `v3.0.0-beta.2`版本
|
||||
|
||||
**配置变更**
|
||||
|
||||
@@ -238,7 +228,7 @@ ALTER TABLE `logi_security_oplog`
|
||||
|
||||
---
|
||||
|
||||
### 6.2.5、升级至 `v3.0.0-beta.1`版本
|
||||
### 6.2.4、升级至 `v3.0.0-beta.1`版本
|
||||
|
||||
**SQL 变更**
|
||||
|
||||
@@ -257,7 +247,7 @@ ALTER COLUMN `operation_methods` set default '';
|
||||
|
||||
---
|
||||
|
||||
### 6.2.6、`2.x`版本 升级至 `v3.0.0-beta.0`版本
|
||||
### 6.2.5、`2.x`版本 升级至 `v3.0.0-beta.0`版本
|
||||
|
||||
**升级步骤:**
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.xiaojukeji.know.streaming.km.biz.cluster;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysHealthState;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysState;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.MultiClusterDashboardDTO;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult;
|
||||
@@ -16,8 +15,6 @@ public interface MultiClusterPhyManager {
|
||||
*/
|
||||
ClusterPhysState getClusterPhysState();
|
||||
|
||||
ClusterPhysHealthState getClusterPhysHealthState();
|
||||
|
||||
/**
|
||||
* 查询多集群大盘
|
||||
* @param dto 分页信息
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.didiglobal.logi.log.LogFactory;
|
||||
import com.xiaojukeji.know.streaming.km.biz.cluster.ClusterZookeepersManager;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterZookeepersOverviewDTO;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ZookeeperMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.ZookeeperMetricParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus;
|
||||
@@ -18,6 +20,7 @@ import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.zookeeper.ZKRoleEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZnodeService;
|
||||
@@ -27,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Service
|
||||
@@ -52,6 +56,11 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager {
|
||||
return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, MsgConstant.getClusterPhyNotExist(clusterPhyId));
|
||||
}
|
||||
|
||||
// // TODO
|
||||
// private Integer healthState;
|
||||
// private Integer healthCheckPassed;
|
||||
// private Integer healthCheckTotal;
|
||||
|
||||
List<ZookeeperInfo> infoList = zookeeperService.listFromDBByCluster(clusterPhyId);
|
||||
|
||||
ClusterZookeepersStateVO vo = new ClusterZookeepersStateVO();
|
||||
@@ -81,17 +90,12 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 指标获取
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.batchCollectMetricsFromZookeeper(
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(new ZookeeperMetricParam(
|
||||
clusterPhyId,
|
||||
Arrays.asList(
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT,
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_STATE,
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED,
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL
|
||||
)
|
||||
|
||||
);
|
||||
infoList.stream().filter(elem -> elem.alive()).map(item -> new Tuple<String, Integer>(item.getHost(), item.getPort())).collect(Collectors.toList()),
|
||||
ConvertUtil.str2ObjByJson(clusterPhy.getZkProperties(), ZKConfig.class),
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT
|
||||
));
|
||||
if (metricsResult.failed()) {
|
||||
LOGGER.error(
|
||||
"class=ClusterZookeepersManagerImpl||method=getClusterPhyZookeepersState||clusterPhyId={}||errMsg={}",
|
||||
@@ -99,12 +103,8 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager {
|
||||
);
|
||||
return Result.buildSuc(vo);
|
||||
}
|
||||
|
||||
ZookeeperMetrics metrics = metricsResult.getData();
|
||||
vo.setWatchCount(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT)));
|
||||
vo.setHealthState(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_STATE)));
|
||||
vo.setHealthCheckPassed(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED)));
|
||||
vo.setHealthCheckTotal(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL)));
|
||||
Float watchCount = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT);
|
||||
vo.setWatchCount(watchCount != null? watchCount.intValue(): null);
|
||||
|
||||
return Result.buildSuc(vo);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.didiglobal.logi.log.LogFactory;
|
||||
import com.xiaojukeji.know.streaming.km.biz.cluster.MultiClusterPhyManager;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricsClusterPhyDTO;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysHealthState;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysState;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.MultiClusterDashboardDTO;
|
||||
@@ -17,7 +16,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyDashboa
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO;
|
||||
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
|
||||
import com.xiaojukeji.know.streaming.km.common.converter.ClusterVOConverter;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthStateEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil;
|
||||
@@ -77,32 +75,6 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager {
|
||||
return physState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterPhysHealthState getClusterPhysHealthState() {
|
||||
List<ClusterPhy> clusterPhyList = clusterPhyService.listAllClusters();
|
||||
|
||||
ClusterPhysHealthState physState = new ClusterPhysHealthState(clusterPhyList.size());
|
||||
for (ClusterPhy clusterPhy: clusterPhyList) {
|
||||
ClusterMetrics metrics = clusterMetricService.getLatestMetricsFromCache(clusterPhy.getId());
|
||||
Float state = metrics.getMetric(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_STATE);
|
||||
if (state == null) {
|
||||
physState.setUnknownCount(physState.getUnknownCount() + 1);
|
||||
} else if (state.intValue() == HealthStateEnum.GOOD.getDimension()) {
|
||||
physState.setGoodCount(physState.getGoodCount() + 1);
|
||||
} else if (state.intValue() == HealthStateEnum.MEDIUM.getDimension()) {
|
||||
physState.setMediumCount(physState.getMediumCount() + 1);
|
||||
} else if (state.intValue() == HealthStateEnum.POOR.getDimension()) {
|
||||
physState.setPoorCount(physState.getPoorCount() + 1);
|
||||
} else if (state.intValue() == HealthStateEnum.DEAD.getDimension()) {
|
||||
physState.setDeadCount(physState.getDeadCount() + 1);
|
||||
} else {
|
||||
physState.setUnknownCount(physState.getUnknownCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return physState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaginationResult<ClusterPhyDashboardVO> getClusterPhysDashboard(MultiClusterDashboardDTO dto) {
|
||||
// 获取集群
|
||||
@@ -176,7 +148,16 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager {
|
||||
// 获取所有的metrics
|
||||
List<ClusterMetrics> metricsList = new ArrayList<>();
|
||||
for (ClusterPhyDashboardVO vo: voList) {
|
||||
metricsList.add(clusterMetricService.getLatestMetricsFromCache(vo.getId()));
|
||||
ClusterMetrics clusterMetrics = clusterMetricService.getLatestMetricsFromCache(vo.getId());
|
||||
if (!clusterMetrics.getMetrics().containsKey(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_SCORE)) {
|
||||
Float alive = clusterMetrics.getMetrics().get(ClusterMetricVersionItems.CLUSTER_METRIC_ALIVE);
|
||||
// 如果集群没有健康分,则设置一个默认的健康分数值
|
||||
clusterMetrics.putMetric(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_SCORE,
|
||||
(alive != null && alive <= 0)? 0.0f: Constant.DEFAULT_CLUSTER_HEALTH_SCORE.floatValue()
|
||||
);
|
||||
}
|
||||
|
||||
metricsList.add(clusterMetrics);
|
||||
}
|
||||
|
||||
// 范围搜索
|
||||
|
||||
@@ -209,7 +209,7 @@ public class GroupManagerImpl implements GroupManager {
|
||||
}
|
||||
|
||||
if (!ConsumerGroupState.EMPTY.equals(description.state()) && !ConsumerGroupState.DEAD.equals(description.state())) {
|
||||
return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, String.format("group处于%s, 重置失败(仅Empty | Dead 情况可重置)", GroupStateEnum.getByRawState(description.state()).getState()));
|
||||
return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, String.format("group处于%s, 重置失败(仅Empty情况可重置)", GroupStateEnum.getByRawState(description.state()).getState()));
|
||||
}
|
||||
|
||||
// 获取offset
|
||||
|
||||
@@ -10,18 +10,14 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicCreateParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicPartitionExpandParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.Topic;
|
||||
import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.BackoffUtils;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.kafka.KafkaReplicaAssignUtil;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.topic.OpTopicService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService;
|
||||
import kafka.admin.AdminUtils;
|
||||
@@ -56,9 +52,6 @@ public class OpTopicManagerImpl implements OpTopicManager {
|
||||
@Autowired
|
||||
private ClusterPhyService clusterPhyService;
|
||||
|
||||
@Autowired
|
||||
private PartitionService partitionService;
|
||||
|
||||
@Override
|
||||
public Result<Void> createTopic(TopicCreateDTO dto, String operator) {
|
||||
log.info("method=createTopic||param={}||operator={}.", dto, operator);
|
||||
@@ -87,7 +80,7 @@ public class OpTopicManagerImpl implements OpTopicManager {
|
||||
);
|
||||
|
||||
// 创建Topic
|
||||
Result<Void> createTopicRes = opTopicService.createTopic(
|
||||
return opTopicService.createTopic(
|
||||
new TopicCreateParam(
|
||||
dto.getClusterId(),
|
||||
dto.getTopicName(),
|
||||
@@ -97,21 +90,6 @@ public class OpTopicManagerImpl implements OpTopicManager {
|
||||
),
|
||||
operator
|
||||
);
|
||||
if (createTopicRes.successful()){
|
||||
try{
|
||||
FutureUtil.quickStartupFutureUtil.submitTask(() -> {
|
||||
BackoffUtils.backoff(3000);
|
||||
Result<List<Partition>> partitionsResult = partitionService.listPartitionsFromKafka(clusterPhy, dto.getTopicName());
|
||||
if (partitionsResult.successful()){
|
||||
partitionService.updatePartitions(clusterPhy.getId(), dto.getTopicName(), partitionsResult.getData(), new ArrayList<>());
|
||||
}
|
||||
});
|
||||
}catch (Exception e) {
|
||||
log.error("method=createTopic||param={}||operator={}||msg=add partition to db failed||errMsg=exception", dto, operator, e);
|
||||
return Result.buildFromRSAndMsg(ResultStatus.MYSQL_OPERATE_FAILED, "Topic创建成功,但记录Partition到DB中失败,等待定时任务同步partition信息");
|
||||
}
|
||||
}
|
||||
return createTopicRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,7 +47,7 @@ public class VersionControlManagerImpl implements VersionControlManager {
|
||||
|
||||
@PostConstruct
|
||||
public void init(){
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_STATE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_SCORE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_FETCH_REQ, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_PRODUCE_REQ, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_UNDER_REPLICA_PARTITIONS, true));
|
||||
@@ -57,7 +57,7 @@ public class VersionControlManagerImpl implements VersionControlManager {
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_REJECTED, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_MESSAGE_IN, true));
|
||||
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_STATE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_SCORE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_IN, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_OUT, true));
|
||||
@@ -75,9 +75,9 @@ public class VersionControlManagerImpl implements VersionControlManager {
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_OFFSET_CONSUMED, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_LAG, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_STATE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_HEALTH_STATE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_HEALTH_SCORE, true));
|
||||
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_STATE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_SCORE, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_CONNECTION_COUNT, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_MESSAGE_IN, true));
|
||||
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_NETWORK_RPO_AVG_IDLE, true));
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.broker;
|
||||
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.common.IpPortData;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.po.broker.BrokerPO;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -66,13 +65,13 @@ public class Broker implements Serializable {
|
||||
*/
|
||||
private Map<String, IpPortData> endpointMap;
|
||||
|
||||
public static Broker buildFrom(Long clusterPhyId, Node node, Long startTimestamp, JmxConfig jmxConfig) {
|
||||
public static Broker buildFrom(Long clusterPhyId, Node node, Long startTimestamp) {
|
||||
Broker metadata = new Broker();
|
||||
metadata.setClusterPhyId(clusterPhyId);
|
||||
metadata.setBrokerId(node.id());
|
||||
metadata.setHost(node.host());
|
||||
metadata.setPort(node.port());
|
||||
metadata.setJmxPort(jmxConfig != null ? jmxConfig.getJmxPort() : -1);
|
||||
metadata.setJmxPort(-1);
|
||||
metadata.setStartTimestamp(startTimestamp);
|
||||
metadata.setRack(node.rack());
|
||||
metadata.setStatus(1);
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.bean.entity.cluster;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* 集群状态信息
|
||||
* @author zengqiao
|
||||
* @date 22/02/24
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ClusterPhysHealthState {
|
||||
private Integer unknownCount;
|
||||
|
||||
private Integer goodCount;
|
||||
|
||||
private Integer mediumCount;
|
||||
|
||||
private Integer poorCount;
|
||||
|
||||
private Integer deadCount;
|
||||
|
||||
private Integer total;
|
||||
|
||||
public ClusterPhysHealthState(Integer total) {
|
||||
this.unknownCount = 0;
|
||||
this.goodCount = 0;
|
||||
this.mediumCount = 0;
|
||||
this.poorCount = 0;
|
||||
this.deadCount = 0;
|
||||
this.total = total;
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,9 @@ public class BaseClusterHealthConfig extends BaseClusterConfigValue {
|
||||
* 健康检查名称
|
||||
*/
|
||||
protected HealthCheckNameEnum checkNameEnum;
|
||||
|
||||
/**
|
||||
* 权重
|
||||
*/
|
||||
protected Float weight;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author wyb
|
||||
* @date 2022/10/26
|
||||
*/
|
||||
@Data
|
||||
public class HealthAmountRatioConfig extends BaseClusterHealthConfig {
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Integer amount;
|
||||
/**
|
||||
* 比例
|
||||
*/
|
||||
private Double ratio;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.bean.entity.health;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class HealthCheckAggResult {
|
||||
private HealthCheckNameEnum checkNameEnum;
|
||||
|
||||
private List<HealthCheckResultPO> poList;
|
||||
|
||||
private Boolean passed;
|
||||
|
||||
public HealthCheckAggResult(HealthCheckNameEnum checkNameEnum, List<HealthCheckResultPO> poList) {
|
||||
this.checkNameEnum = checkNameEnum;
|
||||
this.poList = poList;
|
||||
if (!ValidateUtils.isEmptyList(poList) && poList.stream().filter(elem -> elem.getPassed() <= 0).count() <= 0) {
|
||||
passed = true;
|
||||
} else {
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getTotalCount() {
|
||||
if (poList == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return poList.size();
|
||||
}
|
||||
|
||||
public Integer getPassedCount() {
|
||||
if (poList == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) (poList.stream().filter(elem -> elem.getPassed() > 0).count());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前检查的健康分
|
||||
* 比如:计算集群Broker健康检查中的某一项的健康分
|
||||
*/
|
||||
public Integer calRawHealthScore() {
|
||||
if (poList == null || poList.isEmpty()) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return 100 * this.getPassedCount() / this.getTotalCount();
|
||||
}
|
||||
|
||||
public List<String> getNotPassedResNameList() {
|
||||
if (poList == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return poList.stream().filter(elem -> elem.getPassed() <= 0).map(elem -> elem.getResName()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
if (ValidateUtils.isEmptyList(poList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return poList.get(0).getCreateTime();
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
if (ValidateUtils.isEmptyList(poList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return poList.get(0).getUpdateTime();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,10 @@ import java.util.stream.Collectors;
|
||||
public class HealthScoreResult {
|
||||
private HealthCheckNameEnum checkNameEnum;
|
||||
|
||||
private Float presentDimensionTotalWeight;
|
||||
|
||||
private Float allDimensionTotalWeight;
|
||||
|
||||
private BaseClusterHealthConfig baseConfig;
|
||||
|
||||
private List<HealthCheckResultPO> poList;
|
||||
@@ -24,11 +28,15 @@ public class HealthScoreResult {
|
||||
private Boolean passed;
|
||||
|
||||
public HealthScoreResult(HealthCheckNameEnum checkNameEnum,
|
||||
Float presentDimensionTotalWeight,
|
||||
Float allDimensionTotalWeight,
|
||||
BaseClusterHealthConfig baseConfig,
|
||||
List<HealthCheckResultPO> poList) {
|
||||
this.checkNameEnum = checkNameEnum;
|
||||
this.baseConfig = baseConfig;
|
||||
this.poList = poList;
|
||||
this.presentDimensionTotalWeight = presentDimensionTotalWeight;
|
||||
this.allDimensionTotalWeight = allDimensionTotalWeight;
|
||||
if (!ValidateUtils.isEmptyList(poList) && poList.stream().filter(elem -> elem.getPassed() <= 0).count() <= 0) {
|
||||
passed = true;
|
||||
} else {
|
||||
@@ -51,6 +59,32 @@ public class HealthScoreResult {
|
||||
return (int) (poList.stream().filter(elem -> elem.getPassed() > 0).count());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算所有检查结果的健康分
|
||||
* 比如:计算集群健康分
|
||||
*/
|
||||
public Float calAllWeightHealthScore() {
|
||||
Float healthScore = 100 * baseConfig.getWeight() / allDimensionTotalWeight;
|
||||
if (poList == null || poList.isEmpty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return healthScore * this.getPassedCount() / this.getTotalCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前维度的健康分
|
||||
* 比如:计算集群Broker健康分
|
||||
*/
|
||||
public Float calDimensionWeightHealthScore() {
|
||||
Float healthScore = 100 * baseConfig.getWeight() / presentDimensionTotalWeight;
|
||||
if (poList == null || poList.isEmpty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return healthScore * this.getPassedCount() / this.getTotalCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前检查的健康分
|
||||
* 比如:计算集群Broker健康检查中的某一项的健康分
|
||||
@@ -68,7 +102,7 @@ public class HealthScoreResult {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return poList.stream().filter(elem -> elem.getPassed() <= 0 && !ValidateUtils.isBlank(elem.getResName())).map(elem -> elem.getResName()).collect(Collectors.toList());
|
||||
return poList.stream().filter(elem -> elem.getPassed() <= 0).map(elem -> elem.getResName()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.bean.entity.param.zookeeper;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author didi
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ZookeeperParam extends ClusterPhyParam {
|
||||
private List<Tuple<String, Integer>> zkAddressList;
|
||||
|
||||
private ZKConfig zkConfig;
|
||||
|
||||
public ZookeeperParam(Long clusterPhyId, List<Tuple<String, Integer>> zkAddressList, ZKConfig zkConfig) {
|
||||
super(clusterPhyId);
|
||||
this.zkAddressList = zkAddressList;
|
||||
this.zkConfig = zkConfig;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.bean.entity.reassign;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils;
|
||||
import lombok.Data;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
|
||||
@@ -20,10 +19,4 @@ public class ReassignResult {
|
||||
|
||||
return state.isDone();
|
||||
}
|
||||
|
||||
public boolean checkPreferredReplicaElectionUnNeed(String reassignBrokerIds, String originalBrokerIds) {
|
||||
Integer targetLeader = CommonUtils.string2IntList(reassignBrokerIds).get(0);
|
||||
Integer originalLeader = CommonUtils.string2IntList(originalBrokerIds).get(0);
|
||||
return originalLeader.equals(targetLeader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,4 @@ public class Znode {
|
||||
|
||||
@ApiModelProperty(value = "节点属性", example = "")
|
||||
private Stat stat;
|
||||
|
||||
@ApiModelProperty(value = "节点路径", example = "")
|
||||
private String namespace;
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* @author zengqiao
|
||||
* @date 22/02/24
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "集群健康状态信息")
|
||||
public class ClusterPhysHealthStateVO {
|
||||
@ApiModelProperty(value = "未知", example = "30")
|
||||
private Integer unknownCount;
|
||||
|
||||
@ApiModelProperty(value = "好", example = "30")
|
||||
private Integer goodCount;
|
||||
|
||||
@ApiModelProperty(value = "中", example = "30")
|
||||
private Integer mediumCount;
|
||||
|
||||
@ApiModelProperty(value = "差", example = "30")
|
||||
private Integer poorCount;
|
||||
|
||||
@ApiModelProperty(value = "down", example = "30")
|
||||
private Integer deadCount;
|
||||
|
||||
@ApiModelProperty(value = "总数", example = "150")
|
||||
private Integer total;
|
||||
}
|
||||
@@ -32,6 +32,9 @@ public class HealthCheckConfigVO {
|
||||
@ApiModelProperty(value="检查说明", example = "Group延迟")
|
||||
private String configDesc;
|
||||
|
||||
@ApiModelProperty(value="权重", example = "10")
|
||||
private Float weight;
|
||||
|
||||
@ApiModelProperty(value="检查配置", example = "100")
|
||||
private String value;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@ public class HealthScoreBaseResultVO extends BaseTimeVO {
|
||||
@ApiModelProperty(value="检查维度", example = "1")
|
||||
private Integer dimension;
|
||||
|
||||
@ApiModelProperty(value="检查维度名称", example = "cluster")
|
||||
private String dimensionName;
|
||||
|
||||
@ApiModelProperty(value="检查名称", example = "Group延迟")
|
||||
private String configName;
|
||||
|
||||
@@ -30,6 +27,9 @@ public class HealthScoreBaseResultVO extends BaseTimeVO {
|
||||
@ApiModelProperty(value="检查说明", example = "Group延迟")
|
||||
private String configDesc;
|
||||
|
||||
@ApiModelProperty(value="权重百分比[0-100]", example = "10")
|
||||
private Integer weightPercent;
|
||||
|
||||
@ApiModelProperty(value="得分", example = "100")
|
||||
private Integer score;
|
||||
|
||||
|
||||
@@ -19,7 +19,4 @@ public class ZnodeVO {
|
||||
@ApiModelProperty(value = "节点属性", example = "")
|
||||
private ZnodeStatVO stat;
|
||||
|
||||
@ApiModelProperty(value = "节点路径", example = "/cluster")
|
||||
private String namespace;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,9 +35,14 @@ public class Constant {
|
||||
public static final Integer DEFAULT_SESSION_TIMEOUT_UNIT_MS = 15000;
|
||||
public static final Integer DEFAULT_REQUEST_TIMEOUT_UNIT_MS = 5000;
|
||||
|
||||
public static final Float MIN_HEALTH_SCORE = 10f;
|
||||
|
||||
|
||||
/**
|
||||
* 指标相关
|
||||
*/
|
||||
public static final Integer DEFAULT_CLUSTER_HEALTH_SCORE = 90;
|
||||
|
||||
public static final Integer PER_BATCH_MAX_VALUE = 100;
|
||||
|
||||
public static final String DEFAULT_USER_NAME = "know-streaming-app";
|
||||
|
||||
@@ -15,15 +15,24 @@ public class HealthScoreVOConverter {
|
||||
private HealthScoreVOConverter() {
|
||||
}
|
||||
|
||||
public static List<HealthScoreResultDetailVO> convert2HealthScoreResultDetailVOList(List<HealthScoreResult> healthScoreResultList) {
|
||||
public static List<HealthScoreResultDetailVO> convert2HealthScoreResultDetailVOList(List<HealthScoreResult> healthScoreResultList, boolean useGlobalWeight) {
|
||||
Float globalWeightSum = 1f;
|
||||
if (!healthScoreResultList.isEmpty()) {
|
||||
globalWeightSum = healthScoreResultList.get(0).getAllDimensionTotalWeight();
|
||||
}
|
||||
|
||||
List<HealthScoreResultDetailVO> voList = new ArrayList<>();
|
||||
for (HealthScoreResult healthScoreResult: healthScoreResultList) {
|
||||
HealthScoreResultDetailVO vo = new HealthScoreResultDetailVO();
|
||||
vo.setDimension(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension());
|
||||
vo.setDimensionName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getMessage());
|
||||
vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName());
|
||||
vo.setConfigItem(healthScoreResult.getCheckNameEnum().getConfigItem());
|
||||
vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc());
|
||||
if (useGlobalWeight) {
|
||||
vo.setWeightPercent(healthScoreResult.getBaseConfig().getWeight().intValue() * 100 / globalWeightSum.intValue());
|
||||
} else {
|
||||
vo.setWeightPercent(healthScoreResult.getBaseConfig().getWeight().intValue() * 100 / healthScoreResult.getPresentDimensionTotalWeight().intValue());
|
||||
}
|
||||
|
||||
vo.setScore(healthScoreResult.calRawHealthScore());
|
||||
if (healthScoreResult.getTotalCount() <= 0) {
|
||||
@@ -48,9 +57,9 @@ public class HealthScoreVOConverter {
|
||||
for (HealthScoreResult healthScoreResult: healthScoreResultList) {
|
||||
HealthScoreBaseResultVO vo = new HealthScoreBaseResultVO();
|
||||
vo.setDimension(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension());
|
||||
vo.setDimensionName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getMessage());
|
||||
vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName());
|
||||
vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc());
|
||||
vo.setWeightPercent(healthScoreResult.getBaseConfig().getWeight().intValue() * 100 / healthScoreResult.getPresentDimensionTotalWeight().intValue());
|
||||
vo.setScore(healthScoreResult.calRawHealthScore());
|
||||
vo.setPassed(healthScoreResult.getPassedCount().equals(healthScoreResult.getTotalCount()));
|
||||
vo.setCheckConfig(convert2HealthCheckConfigVO(ConfigGroupEnum.HEALTH.name(), healthScoreResult.getBaseConfig()));
|
||||
@@ -77,6 +86,7 @@ public class HealthScoreVOConverter {
|
||||
vo.setConfigName(config.getCheckNameEnum().getConfigName());
|
||||
vo.setConfigItem(config.getCheckNameEnum().getConfigItem());
|
||||
vo.setConfigDesc(config.getCheckNameEnum().getConfigDesc());
|
||||
vo.setWeight(config.getWeight());
|
||||
vo.setValue(ConvertUtil.obj2Json(config));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.converter;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.Znode;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.zookeeper.ZookeeperUtils;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
|
||||
public class ZnodeConverter {
|
||||
@@ -11,13 +9,11 @@ public class ZnodeConverter {
|
||||
|
||||
}
|
||||
|
||||
public static Znode convert2Znode(ClusterPhy clusterPhy, Tuple<byte[], Stat> dataAndStat, String path) {
|
||||
public static Znode convert2Znode(Tuple<byte[], Stat> dataAndStat, String path) {
|
||||
Znode znode = new Znode();
|
||||
znode.setStat(dataAndStat.getV2());
|
||||
znode.setData(dataAndStat.getV1() == null ? null : new String(dataAndStat.getV1()));
|
||||
znode.setName(path.substring(path.lastIndexOf('/') + 1));
|
||||
znode.setNamespace(ZookeeperUtils.getNamespace(clusterPhy.getZookeeper()));
|
||||
|
||||
return znode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,13 @@ import lombok.Getter;
|
||||
public enum HealthCheckDimensionEnum {
|
||||
UNKNOWN(-1, "未知"),
|
||||
|
||||
CLUSTER(0, "Cluster"),
|
||||
CLUSTER(0, "Cluster维度"),
|
||||
|
||||
BROKER(1, "Broker"),
|
||||
BROKER(1, "Broker维度"),
|
||||
|
||||
TOPIC(2, "Topic"),
|
||||
TOPIC(2, "Topic维度"),
|
||||
|
||||
GROUP(3, "Group"),
|
||||
|
||||
ZOOKEEPER(4, "Zookeeper"),
|
||||
GROUP(3, "消费组维度"),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.xiaojukeji.know.streaming.km.common.enums.health;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthAmountRatioConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthDetectedInLatestMinutesConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
|
||||
@@ -20,8 +19,7 @@ public enum HealthCheckNameEnum {
|
||||
"未知",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "UNKNOWN",
|
||||
"未知",
|
||||
BaseClusterHealthConfig.class,
|
||||
false
|
||||
BaseClusterHealthConfig.class
|
||||
),
|
||||
|
||||
CLUSTER_NO_CONTROLLER(
|
||||
@@ -29,8 +27,7 @@ public enum HealthCheckNameEnum {
|
||||
"Controller",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "CLUSTER_NO_CONTROLLER",
|
||||
"集群Controller数正常",
|
||||
HealthCompareValueConfig.class,
|
||||
true
|
||||
HealthCompareValueConfig.class
|
||||
),
|
||||
|
||||
BROKER_REQUEST_QUEUE_FULL(
|
||||
@@ -38,8 +35,7 @@ public enum HealthCheckNameEnum {
|
||||
"RequestQueueSize",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "BROKER_REQUEST_QUEUE_FULL",
|
||||
"Broker-RequestQueueSize指标",
|
||||
HealthCompareValueConfig.class,
|
||||
false
|
||||
HealthCompareValueConfig.class
|
||||
),
|
||||
|
||||
BROKER_NETWORK_PROCESSOR_AVG_IDLE_TOO_LOW(
|
||||
@@ -47,8 +43,7 @@ public enum HealthCheckNameEnum {
|
||||
"NetworkProcessorAvgIdlePercent",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "BROKER_NETWORK_PROCESSOR_AVG_IDLE_TOO_LOW",
|
||||
"Broker-NetworkProcessorAvgIdlePercent指标",
|
||||
HealthCompareValueConfig.class,
|
||||
false
|
||||
HealthCompareValueConfig.class
|
||||
),
|
||||
|
||||
GROUP_RE_BALANCE_TOO_FREQUENTLY(
|
||||
@@ -56,8 +51,7 @@ public enum HealthCheckNameEnum {
|
||||
"Group Re-Balance",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "GROUP_RE_BALANCE_TOO_FREQUENTLY",
|
||||
"Group re-balance频率",
|
||||
HealthDetectedInLatestMinutesConfig.class,
|
||||
false
|
||||
HealthDetectedInLatestMinutesConfig.class
|
||||
),
|
||||
|
||||
TOPIC_NO_LEADER(
|
||||
@@ -65,8 +59,7 @@ public enum HealthCheckNameEnum {
|
||||
"NoLeader",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "TOPIC_NO_LEADER",
|
||||
"Topic 无Leader数",
|
||||
HealthCompareValueConfig.class,
|
||||
false
|
||||
HealthCompareValueConfig.class
|
||||
),
|
||||
|
||||
TOPIC_UNDER_REPLICA_TOO_LONG(
|
||||
@@ -74,66 +67,9 @@ public enum HealthCheckNameEnum {
|
||||
"UnderReplicaTooLong",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "TOPIC_UNDER_REPLICA_TOO_LONG",
|
||||
"Topic 未同步持续时间",
|
||||
HealthDetectedInLatestMinutesConfig.class,
|
||||
false
|
||||
HealthDetectedInLatestMinutesConfig.class
|
||||
),
|
||||
|
||||
ZK_BRAIN_SPLIT(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER,
|
||||
"BrainSplit",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "ZK_BRAIN_SPLIT",
|
||||
"ZK 脑裂",
|
||||
HealthCompareValueConfig.class,
|
||||
true
|
||||
),
|
||||
|
||||
ZK_OUTSTANDING_REQUESTS(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER,
|
||||
"OutstandingRequests",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "ZK_OUTSTANDING_REQUESTS",
|
||||
"ZK Outstanding 请求堆积数",
|
||||
HealthAmountRatioConfig.class,
|
||||
false
|
||||
),
|
||||
|
||||
ZK_WATCH_COUNT(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER,
|
||||
"WatchCount",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "ZK_WATCH_COUNT",
|
||||
"ZK WatchCount 数",
|
||||
HealthAmountRatioConfig.class,
|
||||
false
|
||||
),
|
||||
|
||||
ZK_ALIVE_CONNECTIONS(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER,
|
||||
"AliveConnections",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "ZK_ALIVE_CONNECTIONS",
|
||||
"ZK 连接数",
|
||||
HealthAmountRatioConfig.class,
|
||||
false
|
||||
),
|
||||
|
||||
ZK_APPROXIMATE_DATA_SIZE(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER,
|
||||
"ApproximateDataSize",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "ZK_APPROXIMATE_DATA_SIZE",
|
||||
"ZK 数据大小(Byte)",
|
||||
HealthAmountRatioConfig.class,
|
||||
false
|
||||
),
|
||||
|
||||
ZK_SENT_RATE(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER,
|
||||
"SentRate",
|
||||
Constant.HC_CONFIG_NAME_PREFIX + "ZK_SENT_RATE",
|
||||
"ZK 发包数",
|
||||
HealthAmountRatioConfig.class,
|
||||
false
|
||||
),
|
||||
|
||||
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -161,18 +97,12 @@ public enum HealthCheckNameEnum {
|
||||
*/
|
||||
private final Class configClazz;
|
||||
|
||||
/**
|
||||
* 是可用性检查?
|
||||
*/
|
||||
private final boolean availableChecker;
|
||||
|
||||
HealthCheckNameEnum(HealthCheckDimensionEnum dimensionEnum, String configItem, String configName, String configDesc, Class configClazz, boolean availableChecker) {
|
||||
HealthCheckNameEnum(HealthCheckDimensionEnum dimensionEnum, String configItem, String configName, String configDesc, Class configClazz) {
|
||||
this.dimensionEnum = dimensionEnum;
|
||||
this.configItem = configItem;
|
||||
this.configName = configName;
|
||||
this.configDesc = configDesc;
|
||||
this.configClazz = configClazz;
|
||||
this.availableChecker = availableChecker;
|
||||
}
|
||||
|
||||
public static HealthCheckNameEnum getByName(String configName) {
|
||||
|
||||
@@ -16,7 +16,7 @@ public enum HealthStateEnum {
|
||||
|
||||
POOR(2, "差"),
|
||||
|
||||
DEAD(3, "Down"),
|
||||
DEAD(3, "宕机"),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public class ZookeeperUtils {
|
||||
* 解析ZK地址
|
||||
* @see ConnectStringParser
|
||||
*/
|
||||
public static List<Tuple<String, Integer>> connectStringParser(String connectString) {
|
||||
public static List<Tuple<String, Integer>> connectStringParser(String connectString) throws Exception {
|
||||
List<Tuple<String, Integer>> ipPortList = new ArrayList<>();
|
||||
|
||||
if (connectString == null) {
|
||||
@@ -55,14 +55,5 @@ public class ZookeeperUtils {
|
||||
return ipPortList;
|
||||
}
|
||||
|
||||
public static String getNamespace(String zookeeperAddress) {
|
||||
int index = zookeeperAddress.indexOf('/');
|
||||
String namespace = "/";
|
||||
if (index != -1) {
|
||||
namespace = zookeeperAddress.substring(index);
|
||||
}
|
||||
return namespace;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ npm install -g lerna
|
||||
npm run i
|
||||
```
|
||||
|
||||
我们默认保留了 `package-lock.json` 文件,以防止可能的依赖包自动升级导致的问题。依赖默认会通过 taobao 镜像 `https://registry.npmmirror.com/` 服务下载(如需修改下载源,请见当前目录下 package.json 文件)。
|
||||
我们默认保留了 `package-lock.json` 文件,以防止可能的依赖包自动升级导致的问题。依赖默认会通过 taobao 镜像 `https://registry.npmmirror.com/` 服务下载。
|
||||
|
||||
## 三、启动项目(可选,打包构建请直接看步骤三)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"prettier": "2.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"i": "npm config set registry https://registry.npmmirror.com/ && npm install && lerna bootstrap",
|
||||
"i": "npm install && lerna bootstrap",
|
||||
"clean": "rm -rf node_modules package-lock.json packages/*/node_modules packages/*/package-lock.json",
|
||||
"start": "lerna run start",
|
||||
"build": "lerna run build",
|
||||
|
||||
@@ -1345,9 +1345,9 @@
|
||||
}
|
||||
},
|
||||
"@knowdesign/icons": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
|
||||
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.0.tgz",
|
||||
"integrity": "sha512-7c+h2TSbh2ihTkXIivuO+DddNC5wG7hVv9SS4ccmkvTKls2ZTLitPu+U0wpufDxPhkPMaKEQfsECsVJ+7jLMiw==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@knowdesign/icons": "^1.0.2",
|
||||
"@knowdesign/icons": "^1.0.0",
|
||||
"babel-preset-react-app": "^10.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"dotenv": "^16.0.1",
|
||||
|
||||
@@ -1388,9 +1388,9 @@
|
||||
}
|
||||
},
|
||||
"@knowdesign/icons": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
|
||||
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.1.tgz",
|
||||
"integrity": "sha512-EI3s25BJt+Slv7/t6B3K3zv7I6TKkk2Wf1y68zuxK80MMkWf8lqqUtyAZbFDoPUfXAjw6vHktMBH44gbMHMRFA==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"dependencies": {
|
||||
"@ant-design/compatible": "^1.0.8",
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@knowdesign/icons": "^1.0.2",
|
||||
"@knowdesign/icons": "^1.0.1",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
||||
@@ -24,7 +24,6 @@ const api = {
|
||||
logout: `${securityPrefix}/account/logout`,
|
||||
|
||||
// 全局信息
|
||||
getVersionInfo: () => getApi('/self/version'),
|
||||
getUserInfo: (userId: number) => `${securityPrefix}/user/${userId}`,
|
||||
getPermissionTree: `${securityPrefix}/permission/tree`,
|
||||
getKafkaVersionItems: () => getApi('/kafka-versions-items'),
|
||||
@@ -61,7 +60,6 @@ const api = {
|
||||
phyClustersDashbord: getApi(`/physical-clusters/dashboard`),
|
||||
supportKafkaVersion: getApi(`/support-kafka-versions`),
|
||||
phyClusterState: getApi(`/physical-clusters/state`),
|
||||
phyClusterHealthState: getApi(`/physical-clusters/health-state`),
|
||||
|
||||
getOperatingStateList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/groups-overview`),
|
||||
getGroupTopicList: (clusterPhyId: number, groupName: string) => getApi(`/clusters/${clusterPhyId}/groups/${groupName}/topics-overview`),
|
||||
@@ -203,14 +201,6 @@ const api = {
|
||||
getJobsTaskData: (clusterPhyId: string, jobId: string | number) => getApi(`/clusters/${clusterPhyId}/jobs/${jobId}/modify-detail`),
|
||||
//编辑任务
|
||||
putJobsTaskData: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/jobs`),
|
||||
|
||||
// Zookeeper 接口
|
||||
getZookeeperState: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeepers-state`),
|
||||
getZookeeperList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeepers-overview`),
|
||||
getZookeeperNodeChildren: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-children`),
|
||||
getZookeeperNodeData: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-data`),
|
||||
getZookeeperMetricsInfo: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeeper-latest-metrics`),
|
||||
getZookeeperMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeeper-metrics`),
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -3,13 +3,12 @@ import '@babel/polyfill';
|
||||
import React, { useState, useEffect, useLayoutEffect } from 'react';
|
||||
import { BrowserRouter, Switch, Route, useLocation, useHistory } from 'react-router-dom';
|
||||
import { get as lodashGet } from 'lodash';
|
||||
import { DProLayout, AppContainer, Menu, Utils, Page500, Modal } from 'knowdesign';
|
||||
import { DProLayout, AppContainer, Menu, Utils, Page403, Page404, Page500, Modal } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import dantdZhCN from 'knowdesign/es/locale/zh_CN';
|
||||
import dantdEnUS from 'knowdesign/es/locale/en_US';
|
||||
import { DotChartOutlined } from '@ant-design/icons';
|
||||
import { licenseEventBus } from './constants/axiosConfig';
|
||||
import { Page403, Page404, NoLicense } from './pages/ErrorPages';
|
||||
import intlZhCN from './locales/zh';
|
||||
import intlEnUS from './locales/en';
|
||||
import registerApps from '../config/registerApps';
|
||||
@@ -19,21 +18,13 @@ import { Login } from './pages/Login';
|
||||
import { getLicenseInfo } from './constants/common';
|
||||
import api from './api';
|
||||
import ClusterContainer from './pages/index';
|
||||
import NoLicense from './pages/NoLicense';
|
||||
import ksLogo from './assets/ks-logo.png';
|
||||
|
||||
interface ILocaleMap {
|
||||
[index: string]: any;
|
||||
}
|
||||
|
||||
interface VersionInfo {
|
||||
'git.branch': string;
|
||||
'git.build.itme': string;
|
||||
'git.build.version': string;
|
||||
'git.commit.id': string;
|
||||
'git.commit.id.abbrev': string;
|
||||
'git.commit.time': string;
|
||||
}
|
||||
|
||||
const localeMap: ILocaleMap = {
|
||||
'zh-CN': {
|
||||
dantd: dantdZhCN,
|
||||
@@ -115,7 +106,6 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
||||
const history = useHistory();
|
||||
const userInfo = localStorage.getItem('userInfo');
|
||||
const [curActiveAppName, setCurActiveAppName] = useState('');
|
||||
const [versionInfo, setVersionInfo] = useState<VersionInfo>();
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname.startsWith('/config')) {
|
||||
@@ -125,13 +115,6 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
// 获取版本信息
|
||||
useEffect(() => {
|
||||
Utils.request(api.getVersionInfo()).then((res: VersionInfo) => {
|
||||
setVersionInfo(res);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DProLayout.Container
|
||||
headerProps={{
|
||||
@@ -158,12 +141,7 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
|
||||
],
|
||||
isFixed: false,
|
||||
userDropMenuItems: [
|
||||
<Menu.Item key={0}>
|
||||
<a href="https://github.com/didi/KnowStreaming/releases" rel="noreferrer" target="_blank">
|
||||
版本: {versionInfo?.['git.build.version']}
|
||||
</a>
|
||||
</Menu.Item>,
|
||||
<Menu.Item key={1} onClick={logout}>
|
||||
<Menu.Item key={0} onClick={logout}>
|
||||
登出
|
||||
</Menu.Item>,
|
||||
],
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,12 +1,11 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import CardBar from '@src/components/CardBar';
|
||||
import { healthDataProps } from '.';
|
||||
import { Utils } from 'knowdesign';
|
||||
import { Tag, Utils } from 'knowdesign';
|
||||
import Api from '@src/api';
|
||||
import { hashDataParse } from '@src/constants/common';
|
||||
import { HealthStateEnum } from '../HealthState';
|
||||
|
||||
export default (props: { record: any }) => {
|
||||
const { record } = props;
|
||||
@@ -15,20 +14,22 @@ export default (props: { record: any }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
state: HealthStateEnum.UNKNOWN,
|
||||
score: 0,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
alive: 0,
|
||||
});
|
||||
|
||||
const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'live'];
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Utils.post(Api.getBrokerDetailMetricPoints(hashDataParse(urlLocation.hash)?.brokerId, urlParams?.clusterId), [
|
||||
'Partitions',
|
||||
'Leaders',
|
||||
'PartitionURP',
|
||||
'HealthScore',
|
||||
'HealthCheckPassed',
|
||||
'HealthCheckTotal',
|
||||
'HealthState',
|
||||
'Alive',
|
||||
]).then((data: any) => {
|
||||
setLoading(false);
|
||||
const rightData = JSON.parse(JSON.stringify(data.metrics));
|
||||
@@ -46,12 +47,14 @@ export default (props: { record: any }) => {
|
||||
value: rightData['PartitionURP'] || '-',
|
||||
},
|
||||
];
|
||||
const healthResData: any = {};
|
||||
healthResData.score = data?.metrics?.['HealthScore'] || 0;
|
||||
healthResData.passed = data?.metrics?.['HealthCheckPassed'] || 0;
|
||||
healthResData.total = data?.metrics?.['HealthCheckTotal'] || 0;
|
||||
healthResData.alive = data?.metrics?.['Alive'] || 0;
|
||||
setCardData(cordRightMap);
|
||||
setHealthData({
|
||||
state: data?.metrics?.['HealthState'],
|
||||
passed: data?.metrics?.['HealthCheckPassed'] || 0,
|
||||
total: data?.metrics?.['HealthCheckTotal'] || 0,
|
||||
});
|
||||
setHealthData(healthResData);
|
||||
// setCardData(data.metrics)
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,6 @@ import { healthDataProps } from '.';
|
||||
import { Tag, Tooltip, Utils } from 'knowdesign';
|
||||
import api from '@src/api';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { HealthStateEnum } from '../HealthState';
|
||||
|
||||
export default () => {
|
||||
const routeParams = useParams<{
|
||||
@@ -15,21 +14,26 @@ export default () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
state: HealthStateEnum.UNKNOWN,
|
||||
score: 0,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
alive: 0,
|
||||
});
|
||||
const healthItems = ['HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'HealthState'];
|
||||
|
||||
const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize'];
|
||||
const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'Alive'];
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
// 获取左侧健康度
|
||||
const brokerMetric = Utils.post(api.getBrokerMetricPoints(Number(routeParams.clusterId)), healthItems).then((data: any) => {
|
||||
setHealthData({
|
||||
state: data?.metrics?.['HealthState'],
|
||||
passed: data?.metrics?.['HealthCheckPassed_Brokers'] || 0,
|
||||
total: data?.metrics?.['HealthCheckTotal_Brokers'] || 0,
|
||||
});
|
||||
const healthResData: any = {};
|
||||
// healthResData.score = data?.find((item:any) => item.metricName === 'HealthScore_Brokers')?.value || 0;
|
||||
// healthResData.passed = data?.find((item:any) => item.metricName === 'HealthCheckPassed_Brokers')?.value || 0;
|
||||
// healthResData.total = data?.find((item:any) => item.metricName === 'HealthCheckTotal_Brokers')?.value || 0;
|
||||
healthResData.score = data?.metrics?.['HealthScore_Brokers'] || 0;
|
||||
healthResData.passed = data?.metrics?.['HealthCheckPassed_Brokers'] || 0;
|
||||
healthResData.total = data?.metrics?.['HealthCheckTotal_Brokers'] || 0;
|
||||
healthResData.alive = data?.metrics?.['Alive'] || 0;
|
||||
setHealthData(healthResData);
|
||||
});
|
||||
// 获取右侧状态
|
||||
const brokersState = Utils.request(api.getBrokersState(routeParams?.clusterId)).then((data) => {
|
||||
@@ -111,6 +115,6 @@ export default () => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [routeParams.clusterId]);
|
||||
|
||||
// console.log('cardData', cardData, healthData);
|
||||
return <CardBar scene="broker" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import CardBar from '@src/components/CardBar';
|
||||
import { healthDataProps } from '.';
|
||||
import { Utils } from 'knowdesign';
|
||||
import api from '@src/api';
|
||||
import { HealthStateEnum } from '../HealthState';
|
||||
|
||||
export default () => {
|
||||
const routeParams = useParams<{
|
||||
@@ -13,17 +12,22 @@ export default () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
state: HealthStateEnum.UNKNOWN,
|
||||
score: 0,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
alive: 0,
|
||||
});
|
||||
const [healthDetail, setHealthDetail] = useState([]);
|
||||
const cardItems = ['Groups', 'GroupActives', 'GroupEmptys', 'GroupRebalances', 'GroupDeads'];
|
||||
const healthItems = ['HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'HealthState'];
|
||||
|
||||
const healthItems = ['HealthScore_Groups', 'HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'Alive'];
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
|
||||
setLoading(false);
|
||||
// setCardData(data
|
||||
// .filter((item: any) => cardItems.indexOf(item.metricName) >= 0)
|
||||
// .map((item: any) => ({ title: item.metricName, value: item.value }))
|
||||
// )
|
||||
setCardData(
|
||||
cardItems.map((item) => {
|
||||
if (item === 'GroupDeads') {
|
||||
@@ -32,11 +36,12 @@ export default () => {
|
||||
return { title: item, value: data.metrics[item] };
|
||||
})
|
||||
);
|
||||
setHealthData({
|
||||
state: data?.metrics?.['HealthState'],
|
||||
passed: data?.metrics?.['HealthCheckPassed_Groups'] || 0,
|
||||
total: data?.metrics?.['HealthCheckTotal_Groups'] || 0,
|
||||
});
|
||||
const healthResData: any = {};
|
||||
healthResData.score = data.metrics['HealthScore_Groups'] || 0;
|
||||
healthResData.passed = data.metrics['HealthCheckPassed_Groups'] || 0;
|
||||
healthResData.total = data.metrics['HealthCheckTotal_Groups'] || 0;
|
||||
healthResData.alive = data.metrics['Alive'] || 0;
|
||||
setHealthData(healthResData);
|
||||
});
|
||||
}, []);
|
||||
return <CardBar scene="group" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import CardBar from '@src/components/CardBar';
|
||||
import { Utils } from 'knowdesign';
|
||||
import { healthDataProps } from '.';
|
||||
import { Tag, Utils } from 'knowdesign';
|
||||
import Api from '@src/api';
|
||||
|
||||
export default () => {
|
||||
@@ -11,7 +12,14 @@ export default () => {
|
||||
}>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
score: 0,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
alive: 0,
|
||||
});
|
||||
const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize'];
|
||||
const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'alive'];
|
||||
const getCordRightMap = (data: any) => {
|
||||
const cordRightMap = [
|
||||
{
|
||||
@@ -41,7 +49,6 @@ export default () => {
|
||||
];
|
||||
return cordRightMap;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
// 获取状态
|
||||
|
||||
@@ -32,6 +32,7 @@ const LoadRebalanceCardBar = (props: any) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [normsVisible, setNormsVisible] = useState(null);
|
||||
const cardItems = ['AclEnable', 'Acls', 'AclUsers', 'AclTopics', 'AclGroups'];
|
||||
const onClose = () => {
|
||||
setNormsVisible(false);
|
||||
};
|
||||
@@ -44,9 +45,11 @@ const LoadRebalanceCardBar = (props: any) => {
|
||||
// 获取右侧状态
|
||||
getCartInfo()
|
||||
.then((res: any) => {
|
||||
// const { AclEnable, Acls, AclUsers, AclTopics, AclGroups } = res.metrics;
|
||||
const { next, sub, status } = res;
|
||||
const { cpu, disk, bytesIn, bytesOut } = sub;
|
||||
const newNextDate: any = transUnitTimePro(moment(next).valueOf() - moment().valueOf());
|
||||
// const newNextDate = parseInt(`${transUnitTimePro(moment(next).valueOf() - moment().valueOf())}`);
|
||||
const cardMap = [
|
||||
{
|
||||
title() {
|
||||
@@ -77,15 +80,20 @@ const LoadRebalanceCardBar = (props: any) => {
|
||||
>
|
||||
{!status ? '已均衡' : '未均衡'}
|
||||
</Tag>
|
||||
{/* <Tag style={{ padding: '2px 4px', backgroundColor: 'rgba(85,110,230,0.10)', color: '#556EE6' }}>已均衡</Tag> */}
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>
|
||||
周期均衡 <IconFont className="cutomIcon" type={`${!status ? 'icon-zhengchang' : 'icon-warning'}`} />
|
||||
</span>
|
||||
{/* <span>
|
||||
周期均衡 <IconFont className="cutomIcon" type="icon-zhengchang" />
|
||||
</span> */}
|
||||
<span>
|
||||
距下次均衡还剩{newNextDate?.value || 0}
|
||||
{newNextDate?.unit || '分钟'}
|
||||
</span>
|
||||
{/* {<span>距下次均衡还剩{1}小时</span>} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -98,6 +106,73 @@ const LoadRebalanceCardBar = (props: any) => {
|
||||
padding: '12px 12px 8px 12px',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// // title: 'CPU avg',
|
||||
// title() {
|
||||
// return (
|
||||
// <div>
|
||||
// <span style={{ display: 'inline-block', marginRight: '8px' }}>CPU AVG</span>
|
||||
// {!cpu?.interval && cpu?.interval !== 0 && (
|
||||
// <Tooltip overlayClassName="rebalance-tooltip" title="未设置均衡策略">
|
||||
// <QuestionCircleOutlined />
|
||||
// </Tooltip>
|
||||
// )}
|
||||
// {/* <IconFont className="cutomIcon" onClick={() => setNormsVisible(true)} type="icon-shezhi"></IconFont> */}
|
||||
// </div>
|
||||
// );
|
||||
// },
|
||||
// value(visibleType: boolean) {
|
||||
// return (
|
||||
// <div id="CPU" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
|
||||
// <div style={{ display: 'inline-block' }}>
|
||||
// <div style={{ margin: '5px 0', fontFamily: 'DIDIFD-Medium' }}>
|
||||
// <span style={{ fontSize: '24px' }}>{cpu?.avg || 0}</span>
|
||||
// <span style={{ fontSize: '14px', display: 'inline-block', marginLeft: '4px' }}>%</span>
|
||||
// </div>
|
||||
// <div style={{ marginTop: '-4px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
// <span>均衡区间: ±{cpu?.interval || 0}%</span>
|
||||
// </div>
|
||||
// </div>
|
||||
// <Popover
|
||||
// // visible={visibleType} // 修改为hover柱状图
|
||||
// overlayClassName="custom-popover"
|
||||
// content={
|
||||
// <div style={{ color: '#495057' }}>
|
||||
// <div>
|
||||
// <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
|
||||
// 超过均衡区间的有: {cpu?.bigNu || 0}
|
||||
// </div>
|
||||
// <div style={{ margin: '6px 0' }}>
|
||||
// <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
|
||||
// 在均衡区间内的有: {cpu?.betweenNu || 0}
|
||||
// </div>
|
||||
// <div>
|
||||
// <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
|
||||
// 低于均衡区间的有: {cpu?.smallNu || 0}
|
||||
// </div>
|
||||
// </div>
|
||||
// }
|
||||
// getPopupContainer={(triggerNode: any) => {
|
||||
// return triggerNode;
|
||||
// }}
|
||||
// color="#ffffff"
|
||||
// >
|
||||
// <div style={{ width: '44px', height: '30px' }}>
|
||||
// <StateChart
|
||||
// data={[
|
||||
// { name: 'bigNu', value: cpu?.bigNu || 0 },
|
||||
// { name: 'betweenNu', value: cpu?.betweenNu || 0 },
|
||||
// { name: 'smallNu', value: cpu?.smallNu || 0 },
|
||||
// ]}
|
||||
// />
|
||||
// </div>
|
||||
// </Popover>
|
||||
// </div>
|
||||
// );
|
||||
// },
|
||||
// className: 'custom-card-bar',
|
||||
// valueClassName: 'custom-card-bar-value',
|
||||
// },
|
||||
{
|
||||
title() {
|
||||
return (
|
||||
|
||||
@@ -5,10 +5,8 @@ import { healthDataProps } from '.';
|
||||
import { Utils } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import api from '@src/api';
|
||||
import { healthScoreCondition } from './const';
|
||||
import { hashDataParse } from '@src/constants/common';
|
||||
import { HealthStateEnum } from '../HealthState';
|
||||
|
||||
const healthItems = ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState'];
|
||||
|
||||
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
|
||||
return typeof v === 'function' ? v(visibleType) : v;
|
||||
@@ -21,12 +19,14 @@ export default (props: { record: any }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
state: HealthStateEnum.UNKNOWN,
|
||||
score: 0,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
alive: 0,
|
||||
});
|
||||
const [healthDetail, setHealthDetail] = useState([]);
|
||||
const [clusterAlive, setClusterAlive] = useState(0);
|
||||
|
||||
const healthItems = ['HealthScore', 'HealthCheckPassed', 'HealthCheckTotal', 'alive'];
|
||||
const getNumAndSubTitles = (cardColumnsItemData: any) => {
|
||||
return (
|
||||
<div style={{ width: '100%', display: 'flex', alignItems: 'end' }}>
|
||||
@@ -40,21 +40,21 @@ export default (props: { record: any }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
const topicName = hashDataParse(location.hash)['topicName'];
|
||||
const detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then(
|
||||
let detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then(
|
||||
(data: any) => {
|
||||
setHealthData({
|
||||
state: data.metrics['HealthState'],
|
||||
passed: data.metrics['HealthCheckPassed'] || 0,
|
||||
total: data.metrics['HealthCheckTotal'] || 0,
|
||||
});
|
||||
let healthResData: any = {};
|
||||
healthResData.score = data.metrics['HealthScore'] || 0;
|
||||
healthResData.passed = data.metrics['HealthCheckPassed'] || 0;
|
||||
healthResData.total = data.metrics['HealthCheckTotal'] || 0;
|
||||
// healthResData.alive = data.metrics['alive'] || 0
|
||||
setHealthData(healthResData);
|
||||
}
|
||||
);
|
||||
|
||||
const detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => {
|
||||
let detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => {
|
||||
setCardData([
|
||||
{
|
||||
title: 'Partitions',
|
||||
@@ -87,12 +87,13 @@ export default (props: { record: any }) => {
|
||||
]);
|
||||
});
|
||||
// 获取集群维度的指标信息
|
||||
const clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then(
|
||||
let clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then(
|
||||
(clusterHealthState: any) => {
|
||||
setClusterAlive(clusterHealthState?.metrics?.Alive || 0);
|
||||
let clusterAlive = clusterHealthState?.metrics?.Alive || 0;
|
||||
setClusterAlive(clusterAlive);
|
||||
}
|
||||
);
|
||||
Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then(() => {
|
||||
Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then((res) => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
@@ -100,7 +101,7 @@ export default (props: { record: any }) => {
|
||||
<CardBar
|
||||
record={record}
|
||||
scene="topic"
|
||||
healthData={{ ...healthData, state: clusterAlive ? healthData.state : HealthStateEnum.DOWN }}
|
||||
healthData={{ ...healthData, alive: clusterAlive }}
|
||||
cardColumns={cardData}
|
||||
showCardBg={false}
|
||||
loading={loading}
|
||||
|
||||
@@ -4,7 +4,6 @@ import CardBar from '@src/components/CardBar';
|
||||
import { healthDataProps } from '.';
|
||||
import { Utils } from 'knowdesign';
|
||||
import api from '@src/api';
|
||||
import { HealthStateEnum } from '../HealthState';
|
||||
|
||||
export default () => {
|
||||
const routeParams = useParams<{
|
||||
@@ -13,12 +12,14 @@ export default () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
state: HealthStateEnum.UNKNOWN,
|
||||
score: 0,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
alive: 0,
|
||||
});
|
||||
const [healthDetail, setHealthDetail] = useState([]);
|
||||
const cardItems = ['Topics', 'Partitions', 'PartitionNoLeader', 'PartitionMinISR_S', 'PartitionMinISR_E', 'PartitionURP'];
|
||||
const healthItems = ['HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'HealthState'];
|
||||
const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'Alive'];
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
|
||||
@@ -41,6 +42,12 @@ export default () => {
|
||||
PartitionURP: 'URP',
|
||||
PartitionNoLeader: 'No Leader',
|
||||
};
|
||||
// setCardData(data
|
||||
// .filter(item => cardItems.indexOf(item.name) >= 0)
|
||||
// .map(item => {
|
||||
// return { title: metricElmMap[item.name] || item.name, value: item.value }
|
||||
// })
|
||||
// )
|
||||
setCardData(
|
||||
cardItems.map((item) => {
|
||||
let title = item;
|
||||
@@ -59,11 +66,12 @@ export default () => {
|
||||
return { title, value: data.metrics[item] };
|
||||
})
|
||||
);
|
||||
setHealthData({
|
||||
state: data.metrics['HealthState'],
|
||||
passed: data.metrics['HealthCheckPassed_Topics'] || 0,
|
||||
total: data.metrics['HealthCheckTotal_Topics'] || 0,
|
||||
});
|
||||
const healthResData: any = {};
|
||||
healthResData.score = data.metrics['HealthScore_Topics'] || 0;
|
||||
healthResData.passed = data.metrics['HealthCheckPassed_Topics'] || 0;
|
||||
healthResData.total = data.metrics['HealthCheckTotal_Topics'] || 0;
|
||||
healthResData.alive = data.metrics['Alive'] || 0;
|
||||
setHealthData(healthResData);
|
||||
});
|
||||
}, []);
|
||||
return <CardBar scene="topic" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import CardBar, { healthDataProps } from './index';
|
||||
import { Utils } from 'knowdesign';
|
||||
import api from '@src/api';
|
||||
import { HealthStateEnum } from '../HealthState';
|
||||
|
||||
interface ZookeeperState {
|
||||
aliveFollowerCount: number;
|
||||
aliveObserverCount: number;
|
||||
aliveServerCount: number;
|
||||
healthCheckPassed: number;
|
||||
healthCheckTotal: number;
|
||||
healthState: number;
|
||||
leaderNode: string;
|
||||
totalFollowerCount: number;
|
||||
totalObserverCount: number;
|
||||
totalServerCount: number;
|
||||
watchCount: number;
|
||||
}
|
||||
|
||||
const getVal = (val: string | number | undefined | null) => {
|
||||
return val === undefined || val === null || val === '' ? '-' : val;
|
||||
};
|
||||
|
||||
const ZookeeperCard = () => {
|
||||
const { clusterId } = useParams<{
|
||||
clusterId: string;
|
||||
}>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cardData, setCardData] = useState([]);
|
||||
const [healthData, setHealthData] = useState<healthDataProps>({
|
||||
state: HealthStateEnum.UNKNOWN,
|
||||
passed: 0,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const getHealthData = () => {
|
||||
return Utils.post(api.getZookeeperMetricsInfo(Number(clusterId)), ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState']).then(
|
||||
(data: any) => {
|
||||
setHealthData({
|
||||
state: data?.metrics?.['HealthState'],
|
||||
passed: data?.metrics?.['HealthCheckPassed'] || 0,
|
||||
total: data?.metrics?.['HealthCheckTotal'] || 0,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getCardInfo = () => {
|
||||
return Utils.request(api.getZookeeperState(clusterId)).then((res: ZookeeperState) => {
|
||||
const {
|
||||
aliveFollowerCount,
|
||||
aliveObserverCount,
|
||||
aliveServerCount,
|
||||
totalFollowerCount,
|
||||
totalObserverCount,
|
||||
totalServerCount,
|
||||
watchCount,
|
||||
leaderNode,
|
||||
} = res || {};
|
||||
const cardMap = [
|
||||
{
|
||||
title: 'Node Count',
|
||||
value() {
|
||||
return (
|
||||
<span>
|
||||
{aliveServerCount || '-'}/{totalServerCount || '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
customStyle: {
|
||||
// 自定义cardbar样式
|
||||
marginLeft: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Watch Count',
|
||||
value: getVal(watchCount),
|
||||
},
|
||||
{
|
||||
title: 'Leader',
|
||||
value() {
|
||||
return <span style={{ fontSize: 24 }}>{leaderNode || '-'}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Follower',
|
||||
value() {
|
||||
return (
|
||||
<span>
|
||||
{getVal(aliveFollowerCount)}/{getVal(totalFollowerCount)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Observer',
|
||||
value() {
|
||||
return (
|
||||
<span>
|
||||
{getVal(aliveObserverCount)}/{getVal(totalObserverCount)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
setCardData(cardMap);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Promise.all([getHealthData(), getCardInfo()]).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [clusterId]);
|
||||
return <CardBar scene="zookeeper" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
|
||||
};
|
||||
|
||||
export default ZookeeperCard;
|
||||
@@ -10,15 +10,48 @@
|
||||
height: 88px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
// justify-content: space-between;
|
||||
align-items: center;
|
||||
.card-bar-health {
|
||||
width: 240px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// justify-content: space-between;
|
||||
.card-bar-health-process {
|
||||
padding-top: 30px;
|
||||
margin-right: 20px;
|
||||
height: 100%;
|
||||
margin-right: 24px;
|
||||
.dcloud-progress-inner {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dcloud-progress-status-normal {
|
||||
.dcloud-progress-inner {
|
||||
background: rgba(85, 110, 230, 0.03);
|
||||
}
|
||||
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
|
||||
stroke: rgb(85, 110, 230);
|
||||
}
|
||||
}
|
||||
.dcloud-progress-status-success {
|
||||
.dcloud-progress-inner {
|
||||
background: rgba(0, 192, 162, 0.03);
|
||||
}
|
||||
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
|
||||
stroke: rgb(0, 192, 162);
|
||||
}
|
||||
}
|
||||
.dcloud-progress-status-exception {
|
||||
.dcloud-progress-inner {
|
||||
background: rgba(255, 112, 102, 0.03);
|
||||
}
|
||||
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
|
||||
stroke: rgb(255, 112, 102);
|
||||
}
|
||||
}
|
||||
.dcloud-progress-inner {
|
||||
font-family: DIDIFD-Regular;
|
||||
font-size: 40px !important;
|
||||
}
|
||||
}
|
||||
.state {
|
||||
font-size: 13px;
|
||||
@@ -28,6 +61,20 @@
|
||||
line-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.health-status-image {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background-size: cover;
|
||||
}
|
||||
.health-status-image-success {
|
||||
background-image: url('../../assets/health-status-success.png');
|
||||
}
|
||||
.health-status-image-exception {
|
||||
background-image: url('../../assets/health-status-exception.png');
|
||||
}
|
||||
.health-status-image-normal {
|
||||
background-image: url('../../assets/health-status-normal.png');
|
||||
}
|
||||
}
|
||||
.value-bar {
|
||||
display: flex;
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Drawer, Spin, Table, Utils } from 'knowdesign';
|
||||
import { Drawer, Select, Spin, Table } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import { Utils, Progress } from 'knowdesign';
|
||||
import './index.less';
|
||||
import api from '@src/api';
|
||||
import moment from 'moment';
|
||||
import TagsWithHide from '../TagsWithHide/index';
|
||||
import HealthState, { getHealthStateDesc, getHealthStateEmoji, HealthStateEnum } from '../HealthState';
|
||||
import { getConfigItemDetailDesc } from '@src/pages/SingleClusterDetail/config';
|
||||
import { getHealthProcessColor } from '@src/pages/SingleClusterDetail/config';
|
||||
|
||||
export interface healthDataProps {
|
||||
state: HealthStateEnum;
|
||||
score: number;
|
||||
passed: number;
|
||||
total: number;
|
||||
alive: number;
|
||||
}
|
||||
export interface CardBarProps {
|
||||
cardColumns?: any[];
|
||||
healthData?: healthDataProps;
|
||||
showCardBg?: boolean;
|
||||
scene: 'topic' | 'broker' | 'group' | 'zookeeper';
|
||||
scene: 'topic' | 'broker' | 'group';
|
||||
record?: any;
|
||||
loading?: boolean;
|
||||
needProgress?: boolean;
|
||||
@@ -26,27 +27,36 @@ export interface CardBarProps {
|
||||
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
|
||||
return typeof v === 'function' ? v(visibleType) : v;
|
||||
};
|
||||
const sceneCodeMap = {
|
||||
broker: {
|
||||
code: 1,
|
||||
fieldName: 'brokerId',
|
||||
alias: 'Brokers',
|
||||
const statusTxtEmojiMap = {
|
||||
success: {
|
||||
emoji: '👍',
|
||||
txt: '优异',
|
||||
},
|
||||
normal: {
|
||||
emoji: '😊',
|
||||
txt: '正常',
|
||||
},
|
||||
exception: {
|
||||
emoji: '👻',
|
||||
txt: '异常',
|
||||
},
|
||||
};
|
||||
const sceneCodeMap = {
|
||||
topic: {
|
||||
code: 2,
|
||||
fieldName: 'topicName',
|
||||
alias: 'Topics',
|
||||
},
|
||||
broker: {
|
||||
code: 1,
|
||||
fieldName: 'brokerId',
|
||||
alias: 'Brokers',
|
||||
},
|
||||
group: {
|
||||
code: 3,
|
||||
fieldName: 'groupName',
|
||||
alias: 'Consumers',
|
||||
},
|
||||
zookeeper: {
|
||||
code: 4,
|
||||
fieldName: 'zookeeperId',
|
||||
alias: 'Zookeeper',
|
||||
},
|
||||
};
|
||||
const CardColumnsItem: any = (cardItem: any) => {
|
||||
const { cardColumnsItemData, showCardBg } = cardItem;
|
||||
@@ -82,7 +92,16 @@ const CardBar = (props: CardBarProps) => {
|
||||
}>();
|
||||
const { healthData, cardColumns, showCardBg = true, scene, record, loading, needProgress = true } = props;
|
||||
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
||||
const [progressStatus, setProgressStatus] = useState<'success' | 'exception' | 'normal'>('success');
|
||||
const [healthCheckDetailList, setHealthCheckDetailList] = useState([]);
|
||||
const [isAlive, setIsAlive] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (healthData) {
|
||||
setProgressStatus(!isAlive ? 'exception' : healthData.score >= 90 ? 'success' : 'normal');
|
||||
setIsAlive(healthData.alive === 1);
|
||||
}
|
||||
}, [healthData, isAlive]);
|
||||
|
||||
useEffect(() => {
|
||||
const sceneObj = sceneCodeMap[scene];
|
||||
@@ -101,24 +120,23 @@ const CardBar = (props: CardBarProps) => {
|
||||
const columns = [
|
||||
{
|
||||
title: '检查项',
|
||||
dataIndex: 'checkConfig',
|
||||
render(config: any, record: any) {
|
||||
let valueGroup = {};
|
||||
try {
|
||||
valueGroup = JSON.parse(config.value);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-';
|
||||
},
|
||||
dataIndex: 'configDesc',
|
||||
key: 'configDesc',
|
||||
},
|
||||
{
|
||||
title: '权重',
|
||||
dataIndex: 'weightPercent',
|
||||
key: 'weightPercent',
|
||||
},
|
||||
{
|
||||
title: '得分',
|
||||
dataIndex: 'score',
|
||||
key: 'score',
|
||||
},
|
||||
// {
|
||||
// title: '得分',
|
||||
// dataIndex: 'score',
|
||||
// },
|
||||
{
|
||||
title: '检查时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
render: (value: number) => {
|
||||
return moment(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
@@ -126,6 +144,8 @@ const CardBar = (props: CardBarProps) => {
|
||||
{
|
||||
title: '检查结果',
|
||||
dataIndex: 'passed',
|
||||
key: 'passed',
|
||||
width: 280,
|
||||
render(value: boolean, record: any) {
|
||||
const icon = value ? <IconFont type="icon-zhengchang"></IconFont> : <IconFont type="icon-yichang"></IconFont>;
|
||||
const txt = value ? '已通过' : '未通过';
|
||||
@@ -148,13 +168,40 @@ const CardBar = (props: CardBarProps) => {
|
||||
{!loading && healthData && needProgress && (
|
||||
<div className="card-bar-health">
|
||||
<div className="card-bar-health-process">
|
||||
<HealthState state={healthData?.state} width={74} height={74} />
|
||||
<Progress
|
||||
width={70}
|
||||
type="circle"
|
||||
percent={!isAlive ? 100 : healthData.score}
|
||||
status={progressStatus}
|
||||
format={(percent, successPercent) => {
|
||||
return !isAlive ? (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'HelveticaNeue-Medium',
|
||||
fontSize: 22,
|
||||
color: getHealthProcessColor(healthData.score, healthData.alive),
|
||||
}}
|
||||
>
|
||||
Down
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textIndent: Math.round(percent) >= 100 ? '-4px' : '',
|
||||
color: getHealthProcessColor(healthData.score, healthData.alive),
|
||||
}}
|
||||
>
|
||||
{Math.round(percent)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
strokeWidth={3}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="state">
|
||||
{getHealthStateEmoji(healthData?.state)}
|
||||
{sceneCodeMap[scene].alias}
|
||||
{getHealthStateDesc(healthData?.state)}
|
||||
<div className={`health-status-image health-status-image-${progressStatus}`}></div>
|
||||
{sceneCodeMap[scene].alias}状态{statusTxtEmojiMap[progressStatus].txt}
|
||||
</div>
|
||||
<div className="value-bar">
|
||||
<div className="value">{`${healthData?.passed}/${healthData?.total}`}</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ const OptionsDefault = [
|
||||
|
||||
const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
const {
|
||||
hasCustomScope,
|
||||
customScopeList: customList,
|
||||
scopeName = '',
|
||||
scopeLabel = '自定义范围',
|
||||
@@ -128,75 +129,79 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div className="flx_r">
|
||||
<h6 className="time_title">{scopeLabel}</h6>
|
||||
<div className="custom-scope">
|
||||
<div className="check-row">
|
||||
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||
全选
|
||||
</Checkbox>
|
||||
<Input
|
||||
className="search-input"
|
||||
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||
size="small"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setScopeSearchValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed-height">
|
||||
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
||||
<Row gutter={[10, 12]}>
|
||||
{customList
|
||||
.filter((item) => item.label.includes(scopeSearchValue))
|
||||
.map((item) => (
|
||||
<Col span={12} key={item.value}>
|
||||
<Checkbox value={item.value}>{item.label}</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
{hasCustomScope && (
|
||||
<div className="flx_r">
|
||||
<h6 className="time_title">{scopeLabel}</h6>
|
||||
<div className="custom-scope">
|
||||
<div className="check-row">
|
||||
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||
全选
|
||||
</Checkbox>
|
||||
<Input
|
||||
className="search-input"
|
||||
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||
size="small"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setScopeSearchValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed-height">
|
||||
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
||||
<Row gutter={[10, 12]}>
|
||||
{customList
|
||||
.filter((item) => item.label.includes(scopeSearchValue))
|
||||
.map((item) => (
|
||||
<Col span={12} key={item.value}>
|
||||
<Checkbox value={item.value}>{item.label}</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
|
||||
<div className="btn-con">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
className="btn-sure"
|
||||
onClick={customSure}
|
||||
disabled={checkedListTemp?.length > 0 ? false : true}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
<Button size="small" onClick={customCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<div className="btn-con">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
className="btn-sure"
|
||||
onClick={customSure}
|
||||
disabled={checkedListTemp?.length > 0 ? false : true}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
<Button size="small" onClick={customCancel}>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div id="d-node-scope">
|
||||
<div className="scope-title">{scopeName}筛选:</div>
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
visible={popVisible}
|
||||
content={clickContent}
|
||||
placement="bottomRight"
|
||||
overlayClassName="d-node-scope-popover large-size"
|
||||
onVisibleChange={visibleChange}
|
||||
>
|
||||
<span className="input-span">
|
||||
<Input
|
||||
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
|
||||
value={inputValue}
|
||||
readOnly={true}
|
||||
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
</div>
|
||||
<>
|
||||
<div id="d-node-scope">
|
||||
<div className="scope-title">{scopeName}筛选:</div>
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
visible={popVisible}
|
||||
content={clickContent}
|
||||
placement="bottomRight"
|
||||
overlayClassName={`d-node-scope-popover ${hasCustomScope ? 'large-size' : ''}`}
|
||||
onVisibleChange={visibleChange}
|
||||
>
|
||||
<span className="input-span">
|
||||
<Input
|
||||
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
|
||||
value={inputValue}
|
||||
readOnly={true}
|
||||
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface IcustomScope {
|
||||
}
|
||||
|
||||
export interface InodeScopeModule {
|
||||
hasCustomScope: boolean;
|
||||
customScopeList: IcustomScope[];
|
||||
scopeName?: string;
|
||||
scopeLabel?: string;
|
||||
@@ -86,6 +87,7 @@ const GRID_SIZE_OPTIONS = [
|
||||
const MetricOperateBar = ({
|
||||
metricSelect,
|
||||
nodeScopeModule = {
|
||||
hasCustomScope: false,
|
||||
customScopeList: [],
|
||||
},
|
||||
hideNodeScope = false,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfi
|
||||
const METRIC_DASHBOARD_REQ_MAP = {
|
||||
[MetricType.Broker]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Broker),
|
||||
[MetricType.Topic]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Topic),
|
||||
[MetricType.Zookeeper]: (clusterId: string) => api.getZookeeperMetrics(clusterId),
|
||||
[MetricType.Zookeeper]: (clusterId: string) => '',
|
||||
};
|
||||
|
||||
export const getMetricDashboardReq = (clusterId: string, type: MetricType.Broker | MetricType.Topic | MetricType.Zookeeper) =>
|
||||
|
||||
@@ -108,10 +108,10 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
|
||||
startTime,
|
||||
endTime,
|
||||
metricsNames: selectedMetricNames,
|
||||
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
|
||||
},
|
||||
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic
|
||||
? {
|
||||
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
|
||||
[dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop
|
||||
? null
|
||||
: curHeaderOptions.scopeData.data,
|
||||
@@ -233,8 +233,8 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
|
||||
<div id="dashboard-drag-chart" className="topic-dashboard">
|
||||
<ChartOperateBar
|
||||
onChange={ksHeaderChange}
|
||||
hideNodeScope={dashboardType === MetricType.Zookeeper}
|
||||
nodeScopeModule={{
|
||||
hasCustomScope: !(dashboardType === MetricType.Zookeeper),
|
||||
customScopeList: scopeList,
|
||||
scopeName: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper',
|
||||
scopeLabel: `自定义 ${
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
.health-state {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import GoodState from '@src/assets/health-good.png';
|
||||
import MediumState from '@src/assets/health-medium.png';
|
||||
import PoorState from '@src/assets/health-poor.png';
|
||||
import DownState from '@src/assets/health-down.png';
|
||||
import UnknownState from '@src/assets/health-unknown.png';
|
||||
import GoodStateEmoji from '@src/assets/health-good-emoji.png';
|
||||
import MediumStateEmoji from '@src/assets/health-medium-emoji.png';
|
||||
import PoorStateEmoji from '@src/assets/health-poor-emoji.png';
|
||||
import DownStateEmoji from '@src/assets/health-down-emoji.png';
|
||||
import './index.less';
|
||||
|
||||
export enum HealthStateEnum {
|
||||
UNKNOWN = -1,
|
||||
GOOD,
|
||||
MEDIUM,
|
||||
POOR,
|
||||
DOWN,
|
||||
}
|
||||
|
||||
interface HealthStateProps {
|
||||
state: HealthStateEnum;
|
||||
width: string | number;
|
||||
height: string | number;
|
||||
}
|
||||
|
||||
const HEALTH_STATE_MAP = {
|
||||
[HealthStateEnum.GOOD]: GoodState,
|
||||
[HealthStateEnum.MEDIUM]: MediumState,
|
||||
[HealthStateEnum.POOR]: PoorState,
|
||||
[HealthStateEnum.DOWN]: DownState,
|
||||
[HealthStateEnum.UNKNOWN]: UnknownState,
|
||||
};
|
||||
|
||||
const HEALTH_STATE_EMOJI_MAP = {
|
||||
[HealthStateEnum.GOOD]: GoodStateEmoji,
|
||||
[HealthStateEnum.MEDIUM]: MediumStateEmoji,
|
||||
[HealthStateEnum.POOR]: PoorStateEmoji,
|
||||
[HealthStateEnum.DOWN]: DownStateEmoji,
|
||||
[HealthStateEnum.UNKNOWN]: DownStateEmoji,
|
||||
};
|
||||
|
||||
const HEALTH_STATE_DESC_MAP = {
|
||||
[HealthStateEnum.GOOD]: '状态优异',
|
||||
[HealthStateEnum.MEDIUM]: '状态良好',
|
||||
[HealthStateEnum.POOR]: '状态较差',
|
||||
[HealthStateEnum.DOWN]: '状态异常',
|
||||
[HealthStateEnum.UNKNOWN]: '状态异常',
|
||||
};
|
||||
|
||||
export const getHealthStateEmoji = (state: HealthStateEnum, width = 16, height = 16) => {
|
||||
return (
|
||||
<img
|
||||
width={width}
|
||||
height={height}
|
||||
style={{ marginTop: -3 }}
|
||||
src={HEALTH_STATE_EMOJI_MAP[state] || HEALTH_STATE_EMOJI_MAP[HealthStateEnum.UNKNOWN]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const getHealthStateDesc = (state: HealthStateEnum) => {
|
||||
return HEALTH_STATE_DESC_MAP[state] || HEALTH_STATE_DESC_MAP[HealthStateEnum.UNKNOWN];
|
||||
};
|
||||
|
||||
const HealthState = (props: HealthStateProps) => {
|
||||
const { state, width, height } = props;
|
||||
|
||||
return (
|
||||
<div className="health-state" style={{ width, height }}>
|
||||
<img src={HEALTH_STATE_MAP[state] || UnknownState} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HealthState;
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { ClustersPermissionMap } from '@src/pages/CommonConfig';
|
||||
import { ClusterRunState } from '@src/pages/MutliClusterPage/List';
|
||||
|
||||
const pkgJson = require('../../package');
|
||||
export const systemKey = pkgJson.ident;
|
||||
|
||||
export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
export const leftMenus = (clusterId?: string) => ({
|
||||
name: `${systemKey}`,
|
||||
icon: 'icon-jiqun',
|
||||
path: `cluster/${clusterId}`,
|
||||
@@ -12,12 +11,12 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
{
|
||||
name: 'cluster',
|
||||
path: 'cluster',
|
||||
icon: 'icon-Cluster1',
|
||||
icon: 'icon-Cluster',
|
||||
},
|
||||
{
|
||||
name: 'broker',
|
||||
path: 'broker',
|
||||
icon: 'icon-Brokers1',
|
||||
icon: 'icon-Brokers',
|
||||
children: [
|
||||
{
|
||||
name: 'dashbord',
|
||||
@@ -39,7 +38,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
{
|
||||
name: 'topic',
|
||||
path: 'topic',
|
||||
icon: 'icon-Topics1',
|
||||
icon: 'icon-Topics',
|
||||
children: [
|
||||
{
|
||||
name: 'dashbord',
|
||||
@@ -53,36 +52,10 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
},
|
||||
],
|
||||
},
|
||||
clusterRunState && clusterRunState !== ClusterRunState.Raft
|
||||
? {
|
||||
name: (intl: any) => {
|
||||
return (
|
||||
<div className="menu-item-with-beta-tag">
|
||||
<span>{intl.formatMessage({ id: 'menu.cluster.zookeeper' })}</span>
|
||||
<div className="beta-tag"></div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
path: 'zookeeper',
|
||||
icon: 'icon-Zookeeper',
|
||||
children: [
|
||||
{
|
||||
name: (intl: any) => <span>{intl.formatMessage({ id: 'menu.cluster.zookeeper.dashboard' })}</span>,
|
||||
path: '',
|
||||
icon: '#icon-luoji',
|
||||
},
|
||||
{
|
||||
name: (intl: any) => <span>{intl.formatMessage({ id: 'menu.cluster.zookeeper.servers' })}</span>,
|
||||
path: 'servers',
|
||||
icon: 'icon-Jobs',
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
name: 'consumer-group',
|
||||
path: 'consumers',
|
||||
icon: 'icon-Consumer',
|
||||
icon: 'icon-ConsumerGroups',
|
||||
// children: [
|
||||
// {
|
||||
// name: 'operating-state',
|
||||
@@ -99,7 +72,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
{
|
||||
name: 'operation',
|
||||
path: 'operation',
|
||||
icon: 'icon-Operation',
|
||||
icon: 'icon-Jobs',
|
||||
children: [
|
||||
process.env.BUSINESS_VERSION
|
||||
? {
|
||||
@@ -119,7 +92,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
? {
|
||||
name: 'produce-consume',
|
||||
path: 'testing',
|
||||
icon: 'icon-Message',
|
||||
icon: 'icon-a-ProduceConsume',
|
||||
permissionPoint: [ClustersPermissionMap.TEST_CONSUMER, ClustersPermissionMap.TEST_PRODUCER],
|
||||
children: [
|
||||
{
|
||||
@@ -140,7 +113,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
|
||||
{
|
||||
name: 'security',
|
||||
path: 'security',
|
||||
icon: 'icon-Security',
|
||||
icon: 'icon-ACLs',
|
||||
children: [
|
||||
{
|
||||
name: 'acls',
|
||||
@@ -259,21 +259,6 @@ li {
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-with-beta-tag {
|
||||
display: flex;
|
||||
.beta-tag {
|
||||
width: 26px;
|
||||
margin-left: 4px;
|
||||
background: no-repeat center/26px 15px url('./assets/beta-tag.png');
|
||||
}
|
||||
}
|
||||
|
||||
.dcloud-menu-item-selected .menu-item-with-beta-tag .beta-tag {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.empty-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -51,10 +51,6 @@ export default {
|
||||
|
||||
[`menu.${systemKey}.jobs`]: 'Job',
|
||||
|
||||
[`menu.${systemKey}.zookeeper`]: 'Zookeeper',
|
||||
[`menu.${systemKey}.zookeeper.dashboard`]: 'Overview',
|
||||
[`menu.${systemKey}.zookeeper.servers`]: 'Servers',
|
||||
|
||||
'access.cluster': '接入集群',
|
||||
'access.cluster.low.version.tip': '监测到当前Version较低,建议维护Zookeeper信息以便得到更好的产品体验',
|
||||
'edit.cluster': '编辑集群',
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import pageNoLicense from '@src/assets/page-no-license.png';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div className="error-page">
|
||||
<img width={230} height={150} src={pageNoLicense} />
|
||||
<div className="title">license 限制</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import page403 from '@src/assets/page403.png';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div className="error-page">
|
||||
<img width={230} height={150} src={page403} />
|
||||
<div className="title">抱歉,您没有权限访问该页面~</div>
|
||||
<a className="link" href="/">
|
||||
返回首页
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import page404 from '@src/assets/page404.png';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div className="error-page">
|
||||
<img width={230} height={150} src={page404} />
|
||||
<div className="title">很抱歉,页面走丢了~</div>
|
||||
<div className="desc">请检查页面地址是否正确或刷新页面</div>
|
||||
<a className="link" href="/">
|
||||
返回首页
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: calc(100% - 48px);
|
||||
> img {
|
||||
margin-top: 165px;
|
||||
}
|
||||
.title {
|
||||
margin: 16px 0;
|
||||
font-family: @font-family-bold;
|
||||
font-size: 18px;
|
||||
color: #495057;
|
||||
line-height: 25px;
|
||||
}
|
||||
.desc {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #74788d;
|
||||
font-weight: 400;
|
||||
}
|
||||
.link {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import './index.less';
|
||||
export { default as Page403 } from './Page403';
|
||||
export { default as Page404 } from './Page404';
|
||||
export { default as NoLicense } from './NoLicense';
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer, Tooltip } from 'knowdesign';
|
||||
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import API from '@src/api';
|
||||
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
|
||||
import { healthSorceList, sliderValueMap, sortFieldList, sortTypes, statusFilters } from './config';
|
||||
import './index.less';
|
||||
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
|
||||
import ClusterList from './List';
|
||||
import AccessClusters from './AccessCluster';
|
||||
import CustomCheckGroup from './CustomCheckGroup';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
import './index.less';
|
||||
|
||||
const CheckboxGroup = Checkbox.Group;
|
||||
const { Option } = Select;
|
||||
@@ -19,17 +19,8 @@ interface ClustersState {
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface ClustersHealthState {
|
||||
deadCount: number;
|
||||
goodCount: number;
|
||||
mediumCount: number;
|
||||
poorCount: number;
|
||||
total: number;
|
||||
unknownCount: number;
|
||||
}
|
||||
|
||||
export interface SearchParams {
|
||||
healthState?: number[];
|
||||
healthScoreRange?: [number, number];
|
||||
checkedKafkaVersions?: string[];
|
||||
sortInfo?: {
|
||||
sortField: string;
|
||||
@@ -83,24 +74,16 @@ const MultiClusterPage = () => {
|
||||
liveCount: 0,
|
||||
total: 0,
|
||||
});
|
||||
const [clustersHealthState, setClustersHealthState] = React.useState<ClustersHealthState>();
|
||||
const [sliderInfo, setSliderInfo] = React.useState<{
|
||||
value: [number, number];
|
||||
desc: string;
|
||||
}>({
|
||||
value: [0, 5],
|
||||
desc: '',
|
||||
});
|
||||
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
|
||||
const [searchParams, setSearchParams] = React.useState<SearchParams>({
|
||||
keywords: '',
|
||||
checkedKafkaVersions: [],
|
||||
healthScoreRange: [0, 100],
|
||||
sortInfo: {
|
||||
sortField: 'HealthState',
|
||||
sortField: 'HealthScore',
|
||||
sortType: 'asc',
|
||||
},
|
||||
clusterStatus: [0, 1],
|
||||
healthState: [-1, 0, 1, 2, 3],
|
||||
// 是否拉取当前所有数据
|
||||
isReloadAll: false,
|
||||
});
|
||||
@@ -108,28 +91,10 @@ const MultiClusterPage = () => {
|
||||
const searchKeyword = useRef('');
|
||||
const isReload = useRef(false);
|
||||
|
||||
const getPhyClusterHealthState = () => {
|
||||
Utils.request(API.phyClusterHealthState).then((res: ClustersHealthState) => {
|
||||
setClustersHealthState(res || undefined);
|
||||
|
||||
const result: string[] = [];
|
||||
for (let i = 0; i < sliderInfo.value[1] - sliderInfo.value[0]; i++) {
|
||||
const val = sliderValueMap[(sliderInfo.value[1] - i) as keyof typeof sliderValueMap];
|
||||
result.push(`${val.name}: ${res?.[val.key as keyof ClustersHealthState]}`);
|
||||
}
|
||||
|
||||
setSliderInfo((cur) => ({
|
||||
...cur,
|
||||
desc: result.reverse().join(', '),
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
// 获取集群状态
|
||||
const getPhyClusterState = () => {
|
||||
getPhyClusterHealthState();
|
||||
Utils.request(API.phyClusterState)
|
||||
.then((res: ClustersState) => {
|
||||
.then((res: any) => {
|
||||
setStateInfo(res);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -146,14 +111,13 @@ const MultiClusterPage = () => {
|
||||
|
||||
const updateSearchParams = (params: SearchParams) => {
|
||||
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
|
||||
getPhyClusterHealthState();
|
||||
};
|
||||
|
||||
const searchParamsChangeFunc = {
|
||||
// 健康分改变
|
||||
onSilderChange: (value: number[]) =>
|
||||
onSilderChange: (value: [number, number]) =>
|
||||
updateSearchParams({
|
||||
healthState: value,
|
||||
healthScoreRange: value,
|
||||
}),
|
||||
// 排序信息改变
|
||||
onSortInfoChange: (type: string, value: string) =>
|
||||
@@ -287,41 +251,16 @@ const MultiClusterPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
|
||||
<h3 className="header-filter-bottom-item-title title-right">健康状态</h3>
|
||||
<Tooltip title={sliderInfo.desc} overlayClassName="cluster-health-state-tooltip">
|
||||
<div className="header-filter-bottom-item-content" id="clusters-slider">
|
||||
<Slider
|
||||
dots
|
||||
range={{ draggableTrack: true }}
|
||||
step={1}
|
||||
max={5}
|
||||
marks={healthSorceList}
|
||||
value={sliderInfo.value}
|
||||
tooltipVisible={false}
|
||||
onChange={(value: [number, number]) => {
|
||||
if (value[0] !== value[1]) {
|
||||
const result = [];
|
||||
for (let i = 0; i < value[1] - value[0]; i++) {
|
||||
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
|
||||
result.push(`${val.name}: ${clustersHealthState?.[val.key as keyof ClustersHealthState]}`);
|
||||
}
|
||||
setSliderInfo({
|
||||
value,
|
||||
desc: result.reverse().join(', '),
|
||||
});
|
||||
}
|
||||
}}
|
||||
onAfterChange={(value: [number, number]) => {
|
||||
const result = [];
|
||||
for (let i = 0; i < value[1] - value[0]; i++) {
|
||||
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
|
||||
result.push(val.code);
|
||||
}
|
||||
searchParamsChangeFunc.onSilderChange(result);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<h3 className="header-filter-bottom-item-title title-right">健康分</h3>
|
||||
<div className="header-filter-bottom-item-content">
|
||||
<Slider
|
||||
range
|
||||
step={20}
|
||||
defaultValue={[0, 100]}
|
||||
marks={healthSorceList}
|
||||
onAfterChange={searchParamsChangeFunc.onSilderChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,7 +269,7 @@ const MultiClusterPage = () => {
|
||||
<div className="multi-cluster-filter-select">
|
||||
<Select
|
||||
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
|
||||
defaultValue="HealthState"
|
||||
defaultValue="HealthScore"
|
||||
style={{ width: 170, marginRight: 12 }}
|
||||
>
|
||||
{sortFieldList.map((item) => (
|
||||
|
||||
@@ -10,15 +10,15 @@ import { timeFormat, oneDayMillims } from '@src/constants/common';
|
||||
import { IMetricPoint, linesMetric, pointsMetric } from './config';
|
||||
import { useIntl } from 'react-intl';
|
||||
import api, { MetricType } from '@src/api';
|
||||
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
import { getDataUnit } from '@src/constants/chartConfig';
|
||||
import SmallChart from '@src/components/SmallChart';
|
||||
import HealthState, { HealthStateEnum } from '@src/components/HealthState';
|
||||
import { SearchParams } from './HomePage';
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
export enum ClusterRunState {
|
||||
enum ClusterRunState {
|
||||
Raft = 2,
|
||||
}
|
||||
|
||||
@@ -165,9 +165,12 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
|
||||
fieldName: 'kafkaVersion',
|
||||
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
|
||||
},
|
||||
],
|
||||
rangeFilterDTOList: [
|
||||
{
|
||||
fieldName: 'HealthState',
|
||||
fieldValueList: searchParams.healthState,
|
||||
fieldMaxValue: searchParams.healthScoreRange[1],
|
||||
fieldMinValue: searchParams.healthScoreRange[0],
|
||||
fieldName: 'HealthScore',
|
||||
},
|
||||
],
|
||||
searchKeywords: searchParams.keywords,
|
||||
@@ -254,7 +257,7 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
|
||||
Zookeepers: zks,
|
||||
HealthCheckPassed: healthCheckPassed,
|
||||
HealthCheckTotal: healthCheckTotal,
|
||||
HealthState: healthState,
|
||||
HealthScore: healthScore,
|
||||
ZookeepersAvailable: zookeepersAvailable,
|
||||
LoadReBalanceCpu: loadReBalanceCpu,
|
||||
LoadReBalanceDisk: loadReBalanceDisk,
|
||||
@@ -269,16 +272,28 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
|
||||
history.push(`/cluster/${itemData.id}/cluster`);
|
||||
}}
|
||||
>
|
||||
<div className="multi-cluster-list-item">
|
||||
<div className={'multi-cluster-list-item'}>
|
||||
<div className="multi-cluster-list-item-healthy">
|
||||
<div className="healthy-box">
|
||||
<HealthState state={healthState} width={70} height={70} />
|
||||
<div className="healthy-degree">
|
||||
<span className="healthy-degree-status">通过</span>
|
||||
<span className="healthy-degree-proportion">
|
||||
{healthCheckPassed}/{healthCheckTotal}
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
type="circle"
|
||||
status={!itemData.alive ? 'exception' : healthScore >= 90 ? 'success' : 'normal'}
|
||||
strokeWidth={4}
|
||||
// className={healthScore > 90 ? 'green-circle' : ''}
|
||||
className={+itemData.alive <= 0 ? 'red-circle' : +healthScore < 90 ? 'blue-circle' : 'green-circle'}
|
||||
strokeColor={getHealthProcessColor(healthScore, itemData.alive)}
|
||||
percent={itemData.alive ? healthScore : 100}
|
||||
format={() => (
|
||||
<div className={`healthy-percent ${getHealthClassName(healthScore, itemData?.alive)}`}>
|
||||
{getHealthText(healthScore, itemData?.alive)}
|
||||
</div>
|
||||
)}
|
||||
width={70}
|
||||
/>
|
||||
<div className="healthy-degree">
|
||||
<span className="healthy-degree-status">通过</span>
|
||||
<span className="healthy-degree-proportion">
|
||||
{healthCheckPassed}/{healthCheckTotal}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="multi-cluster-list-item-right">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { HealthStateEnum } from '@src/components/HealthState';
|
||||
import { FormItemType, IFormItem } from 'knowdesign/es/extend/x-form';
|
||||
|
||||
export const bootstrapServersErrCodes = [10, 11, 12];
|
||||
@@ -22,8 +21,8 @@ export const sortFieldList = [
|
||||
value: 'createTime',
|
||||
},
|
||||
{
|
||||
label: '健康状态',
|
||||
value: 'HealthState',
|
||||
label: '健康分',
|
||||
value: 'HealthScore',
|
||||
},
|
||||
{
|
||||
label: 'Messages',
|
||||
@@ -72,41 +71,18 @@ export const metricNameMap = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export const sliderValueMap = {
|
||||
1: {
|
||||
code: HealthStateEnum.GOOD,
|
||||
key: 'goodCount',
|
||||
name: '好',
|
||||
},
|
||||
2: {
|
||||
code: HealthStateEnum.MEDIUM,
|
||||
key: 'mediumCount',
|
||||
name: '中',
|
||||
},
|
||||
3: {
|
||||
code: HealthStateEnum.POOR,
|
||||
key: 'poorCount',
|
||||
name: '差',
|
||||
},
|
||||
4: {
|
||||
code: HealthStateEnum.DOWN,
|
||||
key: 'deadCount',
|
||||
name: 'Down',
|
||||
},
|
||||
5: {
|
||||
code: HealthStateEnum.UNKNOWN,
|
||||
key: 'unknownCount',
|
||||
name: 'Unknown',
|
||||
},
|
||||
};
|
||||
|
||||
export const healthSorceList = {
|
||||
0: '',
|
||||
1: '好',
|
||||
2: '中',
|
||||
3: '差',
|
||||
4: 'Down',
|
||||
5: 'Unknown',
|
||||
0: 0,
|
||||
10: '',
|
||||
20: 20,
|
||||
30: '',
|
||||
40: 40,
|
||||
50: '',
|
||||
60: 60,
|
||||
70: '',
|
||||
80: 80,
|
||||
90: '',
|
||||
100: 100,
|
||||
};
|
||||
|
||||
export interface IMetricPoint {
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
@error-color: #f46a6a;
|
||||
|
||||
.cluster-health-state-tooltip {
|
||||
.dcloud-tooltip-arrow,
|
||||
.dcloud-tooltip-inner {
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
.dcloud-tooltip-inner {
|
||||
padding: 2px 4px;
|
||||
min-height: 25px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-cluster-page {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -241,9 +229,6 @@
|
||||
&-slider {
|
||||
width: 298px;
|
||||
padding-right: 20px;
|
||||
.dcloud-slider-mark {
|
||||
left: -27px;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@@ -419,29 +404,59 @@
|
||||
transition: 0.5s all;
|
||||
|
||||
&-healthy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 24px;
|
||||
.healthy-box {
|
||||
position: relative;
|
||||
height: 70px;
|
||||
margin-top: 2px;
|
||||
.dcloud-progress-inner {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.green-circle {
|
||||
.dcloud-progress-inner {
|
||||
background: #f5fdfc;
|
||||
}
|
||||
}
|
||||
.red-circle {
|
||||
.dcloud-progress-inner {
|
||||
background: #fffafa;
|
||||
}
|
||||
}
|
||||
.healthy-percent {
|
||||
font-family: DIDIFD-Regular;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
color: #00c0a2;
|
||||
|
||||
&.less-90 {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&.no-info {
|
||||
color: #e9e7e7;
|
||||
}
|
||||
|
||||
&.down {
|
||||
font-family: @font-family-bold;
|
||||
font-size: 22px;
|
||||
color: #ff7066;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.healthy-degree {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
font-family: @font-family-bold;
|
||||
font-size: 12px;
|
||||
color: #495057;
|
||||
line-height: 1;
|
||||
line-height: 15px;
|
||||
margin-top: 6px;
|
||||
text-align: center;
|
||||
|
||||
&-status {
|
||||
margin-right: 6px;
|
||||
color: #74788d;
|
||||
}
|
||||
|
||||
&-proportion {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { Result } from 'knowdesign';
|
||||
import React from 'react';
|
||||
|
||||
export default () => <Result status="403" title="No License" subTitle="很抱歉,您的 Licence 无法使用" />;
|
||||
@@ -6,27 +6,16 @@ import { useIntl } from 'react-intl';
|
||||
import { getHealthySettingColumn } from './config';
|
||||
import API from '../../api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import notification from '@src/components/Notification';
|
||||
|
||||
interface HealthConfig {
|
||||
dimensionCode: number;
|
||||
dimensionName: string;
|
||||
configGroup: string;
|
||||
configItem: string;
|
||||
configName: string;
|
||||
configDesc: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm();
|
||||
const { clusterId } = useParams<{ clusterId: string }>();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [initialValues, setInitialValues] = useState({} as any);
|
||||
|
||||
const [data, setData] = React.useState<HealthConfig[]>([]);
|
||||
const [data, setData] = React.useState([]);
|
||||
const { clusterId } = useParams<{ clusterId: string }>();
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
setVisible,
|
||||
@@ -34,25 +23,29 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
||||
}));
|
||||
|
||||
const getHealthconfig = () => {
|
||||
return Utils.request(API.getClusterHealthyConfigs(+clusterId)).then((res: HealthConfig[]) => {
|
||||
return Utils.request(API.getClusterHealthyConfigs(+clusterId)).then((res: any) => {
|
||||
const values = {} as any;
|
||||
res.sort((a, b) => a.dimensionCode - b.dimensionCode);
|
||||
|
||||
try {
|
||||
res.forEach((item) => {
|
||||
res = res.map((item: any) => {
|
||||
const itemValue = JSON.parse(item.value);
|
||||
const { value, latestMinutes, detectedTimes, amount, ratio } = itemValue;
|
||||
item.weight = itemValue?.weight;
|
||||
|
||||
value && (values[`value_${item.configItem}`] = value);
|
||||
latestMinutes && (values[`latestMinutes_${item.configItem}`] = latestMinutes);
|
||||
detectedTimes && (values[`detectedTimes_${item.configItem}`] = detectedTimes);
|
||||
amount && (values[`amount_${item.configItem}`] = amount);
|
||||
ratio && (values[`ratio_${item.configItem}`] = ratio);
|
||||
item.configItemName =
|
||||
item.configItem.indexOf('Group Re-Balance') > -1
|
||||
? 'ReBalance'
|
||||
: item.configItem.includes('副本未同步')
|
||||
? 'UNDER_REPLICA'
|
||||
: item.configItem;
|
||||
|
||||
values[`weight_${item.configItemName}`] = itemValue?.weight;
|
||||
values[`value_${item.configItemName}`] = itemValue?.value;
|
||||
values[`latestMinutes_${item.configItemName}`] = itemValue?.latestMinutes;
|
||||
values[`detectedTimes_${item.configItemName}`] = itemValue?.detectedTimes;
|
||||
return item;
|
||||
});
|
||||
} catch (err) {
|
||||
notification.error({
|
||||
message: '健康项检查规则解析失败',
|
||||
});
|
||||
//
|
||||
}
|
||||
const formItemsValue = {
|
||||
...initialValues,
|
||||
@@ -77,11 +70,10 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
||||
clusterId: +clusterId,
|
||||
value: JSON.stringify({
|
||||
clusterPhyId: +clusterId,
|
||||
detectedTimes: res[`detectedTimes_${item.configItem}`],
|
||||
latestMinutes: res[`latestMinutes_${item.configItem}`],
|
||||
amount: res[`amount_${item.configItem}`],
|
||||
ratio: res[`ratio_${item.configItem}`],
|
||||
value: item.configItem === 'Controller' ? 1 : res[`value_${item.configItem}`],
|
||||
detectedTimes: res[`detectedTimes_${item.configItemName}`],
|
||||
latestMinutes: res[`latestMinutes_${item.configItemName}`],
|
||||
weight: res[`weight_${item.configItemName}`],
|
||||
value: item.configItemName === 'Controller' ? 1 : res[`value_${item.configItemName}`],
|
||||
}),
|
||||
valueGroup: item.configGroup,
|
||||
valueName: item.configName,
|
||||
@@ -128,7 +120,7 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
|
||||
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
|
||||
<ProTable
|
||||
tableProps={{
|
||||
rowKey: 'configItem',
|
||||
rowKey: 'dimensionCode',
|
||||
showHeader: false,
|
||||
dataSource: data,
|
||||
columns: getHealthySettingColumn(form, data, clusterId),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppContainer, Divider, Tooltip, Utils } from 'knowdesign';
|
||||
import { AppContainer, Divider, Progress, Tooltip, Utils } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import AccessClusters from '../MutliClusterPage/AccessCluster';
|
||||
@@ -7,9 +7,8 @@ import API from '../../api';
|
||||
import HealthySetting from './HealthySetting';
|
||||
import CheckDetail from './CheckDetail';
|
||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||
import { renderToolTipValue } from './config';
|
||||
import { getHealthClassName, getHealthProcessColor, getHealthState, getHealthText, renderToolTipValue } from './config';
|
||||
import { ClustersPermissionMap } from '../CommonConfig';
|
||||
import HealthState, { getHealthStateDesc, getHealthStateEmoji } from '@src/components/HealthState';
|
||||
|
||||
const LeftSider = () => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
@@ -41,7 +40,6 @@ const LeftSider = () => {
|
||||
return Utils.post(
|
||||
API.getPhyClusterMetrics(+clusterId),
|
||||
[
|
||||
'HealthState',
|
||||
'HealthScore',
|
||||
'HealthCheckPassed',
|
||||
'HealthCheckTotal',
|
||||
@@ -104,12 +102,23 @@ const LeftSider = () => {
|
||||
<>
|
||||
<div className="left-sider">
|
||||
<div className="state-card">
|
||||
<HealthState state={clusterMetrics?.HealthState} width={74} height={74} />
|
||||
<Progress
|
||||
type="circle"
|
||||
status="active"
|
||||
strokeWidth={4}
|
||||
strokeColor={getHealthProcessColor(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}
|
||||
percent={clusterMetrics?.HealthScore ?? '-'}
|
||||
className={+clusterMetrics.Alive <= 0 ? 'red-circle' : +clusterMetrics?.HealthScore < 90 ? 'blue-circle' : 'green-circle'}
|
||||
format={() => (
|
||||
<div className={`healthy-percent ${getHealthClassName(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}`}>
|
||||
{getHealthText(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}
|
||||
</div>
|
||||
)}
|
||||
width={75}
|
||||
/>
|
||||
<div className="healthy-state">
|
||||
<div className="healthy-state-status">
|
||||
<span>
|
||||
{getHealthStateEmoji(clusterMetrics?.HealthState)} 集群{getHealthStateDesc(clusterMetrics?.HealthState)}
|
||||
</span>
|
||||
<span>{getHealthState(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}</span>
|
||||
{/* 健康分设置 */}
|
||||
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_HEALTHY) ? (
|
||||
<span
|
||||
|
||||
@@ -7,15 +7,30 @@ import { IconFont } from '@knowdesign/icons';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { systemKey } from '../../constants/menu';
|
||||
|
||||
const statusTxtEmojiMap = {
|
||||
success: {
|
||||
emoji: '👍',
|
||||
txt: '优异',
|
||||
},
|
||||
normal: {
|
||||
emoji: '😊',
|
||||
txt: '正常',
|
||||
},
|
||||
exception: {
|
||||
emoji: '👻',
|
||||
txt: '异常',
|
||||
},
|
||||
};
|
||||
|
||||
export const dimensionMap = {
|
||||
// '-1': {
|
||||
// label: 'Unknown',
|
||||
// href: ``,
|
||||
// },
|
||||
// 0: {
|
||||
// label: 'Cluster',
|
||||
// href: ``,
|
||||
// },
|
||||
'-1': {
|
||||
label: 'Unknown',
|
||||
href: ``,
|
||||
},
|
||||
0: {
|
||||
label: 'Cluster',
|
||||
href: ``,
|
||||
},
|
||||
1: {
|
||||
label: 'Broker',
|
||||
href: `/broker`,
|
||||
@@ -28,100 +43,30 @@ export const dimensionMap = {
|
||||
label: 'ConsumerGroup',
|
||||
href: `/consumers`,
|
||||
},
|
||||
4: {
|
||||
label: 'Zookeeper',
|
||||
href: '/zookeeper',
|
||||
},
|
||||
} as any;
|
||||
|
||||
const toLowerCase = (name = '') => {
|
||||
const [first, ...rest] = name.split('');
|
||||
return first.toUpperCase() + rest.join('').toLowerCase();
|
||||
};
|
||||
|
||||
const CONFIG_ITEM_DETAIL_DESC = {
|
||||
Controller: () => {
|
||||
return '集群 Controller 数等于 1';
|
||||
},
|
||||
RequestQueueSize: (valueGroup: any) => {
|
||||
return `Broker-RequestQueueSize 小于 ${valueGroup?.value}`;
|
||||
},
|
||||
NoLeader: (valueGroup: any) => {
|
||||
return `Topic 无 Leader 数小于 ${valueGroup?.value}`;
|
||||
},
|
||||
NetworkProcessorAvgIdlePercent: (valueGroup: any) => {
|
||||
return `Broker-NetworkProcessorAvgIdlePercent 的 idle 大于 ${valueGroup?.value * 100}%`;
|
||||
},
|
||||
UnderReplicaTooLong: (valueGroup: any) => {
|
||||
return `Topic 小于 ${parseFloat(((valueGroup?.detectedTimes / valueGroup?.latestMinutes) * 100).toFixed(2))}% 周期处于未同步状态`;
|
||||
},
|
||||
'Group Re-Balance': (valueGroup: any) => {
|
||||
return `Consumer Group 小于 ${parseFloat(
|
||||
((valueGroup?.detectedTimes / valueGroup?.latestMinutes) * 100).toFixed(2)
|
||||
)}% 周期处于 Re-balance 状态`;
|
||||
},
|
||||
BrainSplit: () => {
|
||||
return `Zookeeper 未脑裂`;
|
||||
},
|
||||
OutstandingRequests: (valueGroup: any) => {
|
||||
return `Zookeeper 请求堆积数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||
},
|
||||
WatchCount: (valueGroup: any) => {
|
||||
return `Zookeeper 订阅数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||
},
|
||||
AliveConnections: (valueGroup: any) => {
|
||||
return `Zookeeper 连接数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||
},
|
||||
ApproximateDataSize: (valueGroup: any) => {
|
||||
return `Zookeeper 数据大小小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||
},
|
||||
SentRate: (valueGroup: any) => {
|
||||
return `Zookeeper 首发包数小于 ${valueGroup?.ratio * 100}% 总容量`;
|
||||
},
|
||||
};
|
||||
|
||||
export const getConfigItemDetailDesc = (item: keyof typeof CONFIG_ITEM_DETAIL_DESC, valueGroup: any) => {
|
||||
return CONFIG_ITEM_DETAIL_DESC[item]?.(valueGroup);
|
||||
};
|
||||
|
||||
const getFormItem = (params: { configItem: string; type?: string; percent?: boolean; attrs?: any; validator?: any }) => {
|
||||
const { validator, configItem, percent, type = 'value', attrs = { min: 0 } } = params;
|
||||
export const getHealthState = (value: number, down: number) => {
|
||||
if (value === undefined) return '-';
|
||||
const progressStatus = +down <= 0 ? 'exception' : value >= 90 ? 'success' : 'normal';
|
||||
return (
|
||||
<Form.Item
|
||||
name={`${type}_${configItem}`}
|
||||
label=""
|
||||
rules={
|
||||
validator
|
||||
? [
|
||||
{
|
||||
required: true,
|
||||
validator: validator,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
{percent ? (
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={0}
|
||||
max={1}
|
||||
style={{ width: 86 }}
|
||||
formatter={(value) => `${value * 100}%`}
|
||||
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
|
||||
/>
|
||||
) : (
|
||||
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
|
||||
)}
|
||||
</Form.Item>
|
||||
<span>
|
||||
{statusTxtEmojiMap[progressStatus].emoji} 集群状态{statusTxtEmojiMap[progressStatus].txt}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const getHealthText = (value: number, down: number) => {
|
||||
return +down <= 0 ? 'Down' : value ? value.toFixed(0) : '-';
|
||||
};
|
||||
|
||||
export const getHealthProcessColor = (value: number, down: number) => {
|
||||
return +down <= 0 ? '#FF7066' : +value < 90 ? '#556EE6' : '#00C0A2';
|
||||
};
|
||||
|
||||
export const getHealthClassName = (value: number, down: number) => {
|
||||
return +down <= 0 ? 'down' : value === undefined ? 'no-info' : +value < 90 ? 'less-90' : '';
|
||||
};
|
||||
|
||||
export const renderToolTipValue = (value: string, num: number) => {
|
||||
return (
|
||||
<>
|
||||
@@ -143,40 +88,48 @@ export const getDetailColumn = (clusterId: number) => [
|
||||
title: '检查模块',
|
||||
dataIndex: 'dimension',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: (text: number, record: any) => {
|
||||
return dimensionMap[text] ? (
|
||||
<Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{toLowerCase(record?.dimensionName)}</Link>
|
||||
) : (
|
||||
toLowerCase(record?.dimensionName)
|
||||
);
|
||||
render: (text: number) => {
|
||||
if (text === 0 || text === -1) return dimensionMap[text]?.label;
|
||||
return <Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{dimensionMap[text]?.label}</Link>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '检查项',
|
||||
dataIndex: 'checkConfig',
|
||||
render(config: any, record: any) {
|
||||
let valueGroup = {};
|
||||
try {
|
||||
valueGroup = JSON.parse(config.value);
|
||||
} catch (e) {
|
||||
//
|
||||
const valueGroup = JSON.parse(config.value);
|
||||
if (record.configItem === 'Controller') {
|
||||
return '集群 Controller 数等于 1';
|
||||
} else if (record.configItem === 'RequestQueueSize') {
|
||||
return `Broker-RequestQueueSize 小于 ${valueGroup.value}`;
|
||||
} else if (record.configItem === 'NoLeader') {
|
||||
return `Topic 无 Leader 数小于 ${valueGroup.value}`;
|
||||
} else if (record.configItem === 'NetworkProcessorAvgIdlePercent') {
|
||||
return `Broker-NetworkProcessorAvgIdlePercent 的 idle 大于 ${valueGroup.value}%`;
|
||||
} else if (record.configItem === 'UnderReplicaTooLong') {
|
||||
return `Topic 小于 ${parseFloat(((valueGroup.detectedTimes / valueGroup.latestMinutes) * 100).toFixed(2))}% 周期处于未同步状态`;
|
||||
} else if (record.configItem === 'Group Re-Balance') {
|
||||
return `Consumer Group 小于 ${parseFloat(
|
||||
((valueGroup.detectedTimes / valueGroup.latestMinutes) * 100).toFixed(2)
|
||||
)}% 周期处于 Re-balance 状态`;
|
||||
}
|
||||
return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-';
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '权重',
|
||||
// dataIndex: 'weightPercent',
|
||||
// width: 80,
|
||||
// render(value: number) {
|
||||
// return `${value}%`;
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// title: '得分',
|
||||
// dataIndex: 'score',
|
||||
// width: 60,
|
||||
// },
|
||||
{
|
||||
title: '权重',
|
||||
dataIndex: 'weightPercent',
|
||||
width: 80,
|
||||
render(value: number) {
|
||||
return `${value}%`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '得分',
|
||||
dataIndex: 'score',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '检查时间',
|
||||
width: 190,
|
||||
@@ -215,160 +168,168 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string)
|
||||
{
|
||||
title: '检查模块',
|
||||
dataIndex: 'dimensionCode',
|
||||
width: 140,
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: (text: number, record: any) => {
|
||||
return dimensionMap[text] ? (
|
||||
<Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{toLowerCase(record?.dimensionName)}</Link>
|
||||
) : (
|
||||
toLowerCase(record?.dimensionName)
|
||||
);
|
||||
render: (text: number) => {
|
||||
if (text === 0 || text === -1) return dimensionMap[text]?.label;
|
||||
return <Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{dimensionMap[text]?.label}</Link>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '检查项',
|
||||
dataIndex: 'configItem',
|
||||
width: 230,
|
||||
width: 200,
|
||||
needTooltip: true,
|
||||
},
|
||||
{
|
||||
title: '检查项描述',
|
||||
dataIndex: 'configDesc',
|
||||
width: 310,
|
||||
width: 240,
|
||||
needToolTip: true,
|
||||
},
|
||||
// {
|
||||
// title: '权重',
|
||||
// dataIndex: 'weight',
|
||||
// // width: 180,
|
||||
// // eslint-disable-next-line react/display-name
|
||||
// render: (text: number, record: any) => {
|
||||
// return (
|
||||
// <>
|
||||
// <Form.Item
|
||||
// name={`weight_${record.configItemName}`}
|
||||
// label=""
|
||||
// rules={[
|
||||
// {
|
||||
// required: true,
|
||||
// validator: async (rule: any, value: string) => {
|
||||
// const otherWeightCongigName: string[] = [];
|
||||
// let totalPercent = 0;
|
||||
// data.map((item: any) => {
|
||||
// if (item.configItemName !== record.configItemName) {
|
||||
// otherWeightCongigName.push(`weight_${item.configItemName}`);
|
||||
// totalPercent += form.getFieldValue(`weight_${item.configItemName}`) ?? 0;
|
||||
// }
|
||||
// });
|
||||
// if (!value) {
|
||||
// return Promise.reject('请输入权重');
|
||||
// }
|
||||
// if (+value < 0) {
|
||||
// return Promise.reject('最小为0');
|
||||
// }
|
||||
// if (+value + totalPercent !== 100) {
|
||||
// return Promise.reject('总和应为100%');
|
||||
// }
|
||||
// form.setFields(otherWeightCongigName.map((i) => ({ name: i, errors: [] })));
|
||||
// return Promise.resolve('');
|
||||
// },
|
||||
// },
|
||||
// ]}
|
||||
// >
|
||||
// <InputNumber
|
||||
// size="small"
|
||||
// min={0}
|
||||
// max={100}
|
||||
// formatter={(value) => `${value}%`}
|
||||
// parser={(value: any) => value.replace('%', '')}
|
||||
// />
|
||||
// </Form.Item>
|
||||
// </>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
{
|
||||
title: '权重',
|
||||
dataIndex: 'weight',
|
||||
// width: 180,
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: (text: number, record: any) => {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={`weight_${record.configItemName}`}
|
||||
label=""
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validator: async (rule: any, value: string) => {
|
||||
const otherWeightCongigName: string[] = [];
|
||||
let totalPercent = 0;
|
||||
data.map((item: any) => {
|
||||
if (item.configItemName !== record.configItemName) {
|
||||
otherWeightCongigName.push(`weight_${item.configItemName}`);
|
||||
totalPercent += form.getFieldValue(`weight_${item.configItemName}`) ?? 0;
|
||||
}
|
||||
});
|
||||
if (!value) {
|
||||
return Promise.reject('请输入权重');
|
||||
}
|
||||
if (+value < 0) {
|
||||
return Promise.reject('最小为0');
|
||||
}
|
||||
if (+value + totalPercent !== 100) {
|
||||
return Promise.reject('总和应为100%');
|
||||
}
|
||||
form.setFields(otherWeightCongigName.map((i) => ({ name: i, errors: [] })));
|
||||
return Promise.resolve('');
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={0}
|
||||
max={100}
|
||||
formatter={(value) => `${value}%`}
|
||||
parser={(value: any) => value.replace('%', '')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '检查规则',
|
||||
// width: 350,
|
||||
dataIndex: 'passed',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: (text: any, record: any) => {
|
||||
const configItem = record.configItem;
|
||||
const getFormItem = (params: { type?: string; percent?: boolean; attrs?: any; validator?: any }) => {
|
||||
const { validator, percent, type = 'value', attrs = { min: 0 } } = params;
|
||||
return (
|
||||
<Form.Item
|
||||
name={`${type}_${record.configItemName}`}
|
||||
label=""
|
||||
rules={
|
||||
validator
|
||||
? [
|
||||
{
|
||||
required: true,
|
||||
validator: validator,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
{percent ? (
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={0}
|
||||
max={1}
|
||||
style={{ width: 86 }}
|
||||
formatter={(value) => `${value * 100}%`}
|
||||
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
|
||||
/>
|
||||
) : (
|
||||
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
};
|
||||
|
||||
switch (configItem) {
|
||||
case 'Controller': {
|
||||
return <div className="table-form-item">≠ 1 则不通过</div>;
|
||||
}
|
||||
case 'BrainSplit': {
|
||||
return <div className="table-form-item">脑裂则不通过</div>;
|
||||
}
|
||||
case 'RequestQueueSize':
|
||||
case 'NoLeader': {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
<span className="left-text">≥</span>
|
||||
{getFormItem({ configItem, attrs: { min: 0, max: 99998 } })}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case 'SentRate':
|
||||
case 'WatchCount':
|
||||
case 'AliveConnections':
|
||||
case 'ApproximateDataSize':
|
||||
case 'OutstandingRequests': {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
<span className="left-text">总容量指标</span>
|
||||
{getFormItem({ configItem, type: 'amount' })}
|
||||
<span className="left-text">, ≥</span>
|
||||
{getFormItem({ configItem, type: 'ratio', percent: true })}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case 'NetworkProcessorAvgIdlePercent': {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
<span className="left-text">≤</span>
|
||||
{getFormItem({ configItem, percent: true })}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case 'UnderReplicaTooLong':
|
||||
case 'Group Re-Balance': {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
{getFormItem({ type: 'latestMinutes', configItem, attrs: { min: 1, max: 10080 } })}
|
||||
<span className="right-text left-text">周期内,≥</span>
|
||||
{getFormItem({
|
||||
type: 'detectedTimes',
|
||||
configItem,
|
||||
attrs: { min: 1, max: 10080 },
|
||||
validator: async (rule: any, value: string) => {
|
||||
const latestMinutesValue = form.getFieldValue(`latestMinutes_${configItem}`);
|
||||
|
||||
if (!value) {
|
||||
return Promise.reject('请输入');
|
||||
}
|
||||
if (+value < 1) {
|
||||
return Promise.reject('最小为1');
|
||||
}
|
||||
if (+value > +latestMinutesValue) {
|
||||
return Promise.reject('值不能大于周期');
|
||||
}
|
||||
return Promise.resolve('');
|
||||
},
|
||||
})}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return <></>;
|
||||
}
|
||||
if (record.configItemName === 'Controller') {
|
||||
return <div className="table-form-item">≠ 1 则不通过</div>;
|
||||
}
|
||||
if (record.configItemName === 'RequestQueueSize' || record.configItemName === 'NoLeader') {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
<span className="left-text">≥</span>
|
||||
{getFormItem({ attrs: { min: 0, max: 99998 } })}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (record.configItemName === 'NetworkProcessorAvgIdlePercent') {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
<span className="left-text">≤</span>
|
||||
{getFormItem({ percent: true })}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (record.configItemName === 'UnderReplicaTooLong' || record.configItemName === 'ReBalance') {
|
||||
return (
|
||||
<div className="table-form-item">
|
||||
{getFormItem({ type: 'latestMinutes', attrs: { min: 1, max: 10080 } })}
|
||||
<span className="right-text left-text">周期内,≥</span>
|
||||
{getFormItem({
|
||||
type: 'detectedTimes',
|
||||
attrs: { min: 1, max: 10080 },
|
||||
validator: async (rule: any, value: string) => {
|
||||
const latestMinutesValue = form.getFieldValue(`latestMinutes_${record.configItemName}`);
|
||||
|
||||
if (!value) {
|
||||
return Promise.reject('请输入');
|
||||
}
|
||||
if (+value < 1) {
|
||||
return Promise.reject('最小为1');
|
||||
}
|
||||
if (+value > +latestMinutesValue) {
|
||||
return Promise.reject('值不能大于周期');
|
||||
}
|
||||
return Promise.resolve('');
|
||||
},
|
||||
})}
|
||||
<span className="right-text">则不通过</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
] as any;
|
||||
|
||||
@@ -195,14 +195,15 @@
|
||||
}
|
||||
|
||||
.healthy-state {
|
||||
margin-left: 10px;
|
||||
margin-left: 14px;
|
||||
margin-top: 8px;
|
||||
|
||||
&-status {
|
||||
font-size: 13px;
|
||||
color: #495057;
|
||||
letter-spacing: 0;
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 13px;
|
||||
|
||||
.icon {
|
||||
margin-left: 4px;
|
||||
@@ -224,7 +225,7 @@
|
||||
}
|
||||
|
||||
.dcloud-divider-horizontal {
|
||||
margin: 0 16px 14px 0;
|
||||
margin: 16px 4px;
|
||||
padding: 0px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import SwitchTab from '@src/components/SwitchTab';
|
||||
|
||||
const isJSON = (str: string) => {
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const ZKData = ({ nodeData }: { nodeData: string }) => {
|
||||
const [showMode, setShowMode] = useState('default');
|
||||
return (
|
||||
<>
|
||||
<div className="zk-detail-layout-right-content-format">
|
||||
<SwitchTab defaultKey={showMode} onChange={(key) => setShowMode(key)}>
|
||||
<SwitchTab.TabItem key="default">
|
||||
<div style={{ padding: '0 10px' }}>原始格式</div>
|
||||
</SwitchTab.TabItem>
|
||||
<SwitchTab.TabItem key="JSON">
|
||||
<div style={{ padding: '0 10px' }}>JSON格式</div>
|
||||
</SwitchTab.TabItem>
|
||||
</SwitchTab>
|
||||
</div>
|
||||
{showMode === 'default' && (
|
||||
<div className={'zk-detail-layout-right-content-data'}>
|
||||
{isJSON(nodeData) ? JSON.stringify(JSON.parse(nodeData), null, 2) : nodeData}
|
||||
</div>
|
||||
)}
|
||||
{showMode === 'JSON' && (
|
||||
<div className={'zk-detail-layout-right-content-code'}>
|
||||
<CodeMirror
|
||||
className={'zk-detail-layout-right-content-code-data'}
|
||||
value={isJSON(nodeData) ? JSON.stringify(JSON.parse(nodeData), null, 2) : nodeData}
|
||||
options={{
|
||||
mode: 'application/json',
|
||||
lineNumbers: true,
|
||||
lineWrapper: true,
|
||||
autoCloseBrackets: true,
|
||||
smartIndent: true,
|
||||
tabSize: 2,
|
||||
}}
|
||||
onBeforeChange={() => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZKData;
|
||||
@@ -1,188 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Drawer, Utils, AppContainer, Spin, Empty } from 'knowdesign';
|
||||
import ZKDetailMenu from './Sider';
|
||||
import Api from '@src/api';
|
||||
import ZKInfo from './Info';
|
||||
import ZKData from './Data';
|
||||
const { request } = Utils;
|
||||
import './index.less';
|
||||
import { DataNode } from './config';
|
||||
|
||||
const ZookeeperDetail = ({ visible, setVisible }: { visible: boolean; setVisible: (visible: boolean) => void }) => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
// const { visible, setVisible } = props;
|
||||
const [detailLoading, setDetailLoading] = useState(true);
|
||||
const [isDetail, setIsDetail] = useState(true);
|
||||
const [detailInfoLoading, setDetailInfoLoading] = useState(false);
|
||||
const [pathList, setPathList] = useState([]);
|
||||
const [node, setNode] = useState<any>({});
|
||||
const [idenKey, setIdenKey] = useState([]);
|
||||
const [siderWidth, setSiderWidth] = useState(200);
|
||||
const [startPageX, setStartPageX] = useState(0);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [detailTreeData, setDetailTreeData] = useState([]);
|
||||
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
setPathList([]);
|
||||
setNode({});
|
||||
setIdenKey([]);
|
||||
};
|
||||
|
||||
const siderMouseDown = (e: { pageX: number }) => {
|
||||
setStartPageX(e.pageX);
|
||||
setDragging(true);
|
||||
};
|
||||
|
||||
const siderMouseMove = (e: { pageX: number }) => {
|
||||
const currentSiderWidth = siderWidth + e.pageX - startPageX;
|
||||
|
||||
if (currentSiderWidth < 200) {
|
||||
setSiderWidth(200);
|
||||
} else if (currentSiderWidth > 320) {
|
||||
setSiderWidth(320);
|
||||
} else {
|
||||
setSiderWidth(currentSiderWidth);
|
||||
}
|
||||
|
||||
setStartPageX(e.pageX);
|
||||
};
|
||||
|
||||
const siderMouseUp = () => {
|
||||
setDragging(false);
|
||||
};
|
||||
|
||||
const rootClick = () => {
|
||||
setPathList([]);
|
||||
setIdenKey([]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 第一次加载不触发详情信息的loading
|
||||
!detailLoading && setDetailInfoLoading(true);
|
||||
visible &&
|
||||
request(Api.getZookeeperNodeData(+global?.clusterInfo?.id), {
|
||||
params: { path: '/' + pathList.map((item: DataNode) => item.title).join('/') },
|
||||
})
|
||||
.then((res) => {
|
||||
setNode(res || {});
|
||||
})
|
||||
.catch(() => {
|
||||
setNode({});
|
||||
})
|
||||
.finally(() => {
|
||||
setDetailInfoLoading(false);
|
||||
});
|
||||
}, [pathList]);
|
||||
|
||||
useEffect(() => {
|
||||
setDetailLoading(true);
|
||||
visible &&
|
||||
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: { path: '/', keyword: '' } })
|
||||
// zkDetailInfo()
|
||||
.then((res: string[]) => {
|
||||
const newData =
|
||||
res && res.length > 0
|
||||
? res.map((item: string, index: number) => {
|
||||
return {
|
||||
title: item,
|
||||
key: `${index}`,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
if (newData.length > 0) {
|
||||
setIsDetail(false);
|
||||
setDetailTreeData(newData);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setDetailLoading(false);
|
||||
});
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
push={false}
|
||||
title={'Zookeeper 详情'}
|
||||
width={1080}
|
||||
placement="right"
|
||||
onClose={onClose}
|
||||
visible={visible}
|
||||
className="zookeeper-detail-drawer"
|
||||
destroyOnClose
|
||||
maskClosable={false}
|
||||
>
|
||||
<Spin spinning={detailLoading}>
|
||||
{isDetail ? (
|
||||
<div className="zk-detail-empty">
|
||||
<Empty image={Empty.PRESENTED_IMAGE_CUSTOM} description="暂无数据" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="zk-detail-layout">
|
||||
<div className="zk-detail-layout-left" style={{ width: siderWidth + 'px' }}>
|
||||
<div className="zk-detail-layout-left-title">目录结构</div>
|
||||
<div className="zk-detail-layout-left-content">
|
||||
{visible && (
|
||||
<ZKDetailMenu
|
||||
setDetailInfoLoading={setDetailInfoLoading}
|
||||
detailTreeData={detailTreeData}
|
||||
setPathList={setPathList}
|
||||
setIdenKey={setIdenKey}
|
||||
idenKey={idenKey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="zk-detail-layout-resizer" style={{ left: siderWidth + 'px' }} onMouseDown={siderMouseDown}>
|
||||
{dragging && <div className="resize-mask" onMouseMove={siderMouseMove} onMouseUp={siderMouseUp} />}
|
||||
</div>
|
||||
<div className="zk-detail-layout-right">
|
||||
<div className="zk-detail-layout-right-title">详细信息</div>
|
||||
<div className="zk-detail-layout-right-content">
|
||||
{visible && (
|
||||
<Spin spinning={detailInfoLoading}>
|
||||
<div className="zk-detail-layout-right-content-countheight">
|
||||
<div className="zk-detail-layout-right-content-path">
|
||||
<span onClick={rootClick}>{node.namespace}</span>
|
||||
{pathList.length > 0 &&
|
||||
pathList.map((item, index) => {
|
||||
if (item.key === idenKey[0]) {
|
||||
return (
|
||||
<span key={index}>
|
||||
{' '}
|
||||
/
|
||||
<a key={index} onClick={() => setIdenKey([item.key])}>
|
||||
{item.title}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span key={index}>
|
||||
{' '}
|
||||
/
|
||||
<span key={index} onClick={() => setIdenKey([item.key])}>
|
||||
{item.title}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* <div>{'/' + pathList.map((item: any) => item.title).join(' / ') || '/'}</div> */}
|
||||
<div className="zk-detail-layout-right-content-info">
|
||||
<ZKInfo siderWidth={siderWidth} nodeInfo={node?.stat || {}} />
|
||||
</div>
|
||||
<ZKData nodeData={node?.data || ''} />
|
||||
</div>
|
||||
</Spin>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Spin>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZookeeperDetail;
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Descriptions } from 'knowdesign';
|
||||
import moment from 'moment';
|
||||
const ZKInfo = ({ nodeInfo, siderWidth }: any) => {
|
||||
const smallStyle = {
|
||||
width: 82,
|
||||
};
|
||||
return (
|
||||
<Descriptions
|
||||
style={{ fontSize: '13px' }}
|
||||
column={siderWidth >= 270 ? 2 : 3}
|
||||
labelStyle={{
|
||||
display: 'flex',
|
||||
textAlign: 'right',
|
||||
justifyContent: 'end',
|
||||
color: '#74788D',
|
||||
fontSize: '13px',
|
||||
width: siderWidth >= 270 ? 144 : 100,
|
||||
}}
|
||||
contentStyle={{ fontSize: '13px' }}
|
||||
>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="aversion">
|
||||
{nodeInfo.aversion || nodeInfo.aversion === 0 ? nodeInfo.aversion : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="ctime">{nodeInfo.ctime || nodeInfo.ctime === 0 ? nodeInfo.ctime : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="ctime-pretty">
|
||||
{nodeInfo.ctime ? moment(nodeInfo.ctime).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="cversion">
|
||||
{nodeInfo.cversion || nodeInfo.cversion === 0 ? nodeInfo.cversion : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="czxid">{nodeInfo.czxid || nodeInfo.czxid === 0 ? nodeInfo.czxid : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="dataLength">
|
||||
{nodeInfo.dataLength || nodeInfo.dataLength === 0 ? nodeInfo.dataLength : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="mtime">
|
||||
{nodeInfo.mtime || nodeInfo.mtime === 0 ? nodeInfo.mtime : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="mtime-pretty">
|
||||
{nodeInfo.mtime ? moment(nodeInfo.mtime).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="numChildren">
|
||||
{nodeInfo.numChildren || nodeInfo.numChildren === 0 ? nodeInfo.numChildren : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="mzxid">
|
||||
{nodeInfo.mzxid || nodeInfo.mzxid === 0 ? nodeInfo.mzxid : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="ephemeralOwner">
|
||||
{nodeInfo.ephemeralOwner || nodeInfo.ephemeralOwner === 0 ? nodeInfo.ephemeralOwner : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item style={{ paddingBottom: '12px' }} label="pzxid">
|
||||
{nodeInfo.pzxid || nodeInfo.pzxid === 0 ? nodeInfo.pzxid : '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} style={{ paddingBottom: '12px' }} label="version">
|
||||
{nodeInfo.version || nodeInfo.version === 0 ? nodeInfo.version : '-'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZKInfo;
|
||||
@@ -1,179 +0,0 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Tree, SearchInput, AppContainer, Utils } from 'knowdesign';
|
||||
import { DataNode, DetailMenuType, getPathByKey, updateTreeData } from './config';
|
||||
import Api from '@src/api';
|
||||
|
||||
const { request } = Utils;
|
||||
|
||||
const ZKDetailMenu = (props: DetailMenuType) => {
|
||||
const { detailTreeData, setDetailInfoLoading, setPathList, setIdenKey, idenKey } = props;
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [treeData, setTreeData] = useState<DataNode[]>(detailTreeData);
|
||||
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [loadedKeys, setLoadedKeys] = useState<string[]>([]);
|
||||
const [childrenClose, setChildrenClose] = useState<boolean>(false);
|
||||
// const [searchValue, setSearchValue] = useState<string>('');
|
||||
|
||||
// const treeRef = useRef();
|
||||
|
||||
// 处理参数
|
||||
const getParams = (key: string, searchValue?: string) => {
|
||||
const path =
|
||||
'/' +
|
||||
getPathByKey(key, treeData)
|
||||
.map((item: any) => item.title)
|
||||
.join('/');
|
||||
return {
|
||||
path,
|
||||
keyword: searchValue ? searchValue : '',
|
||||
};
|
||||
};
|
||||
|
||||
const onSelect = (selectedKeys: string[]) => {
|
||||
// 控制右侧详情内容的Loading
|
||||
setDetailInfoLoading(true);
|
||||
setIdenKey(selectedKeys);
|
||||
};
|
||||
|
||||
const onLoadData = ({ key, children = null }: any) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// 节点关闭,在展开要重新发送请求,以保证节点的准确性
|
||||
if (children && !childrenClose) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: getParams(key) })
|
||||
.then((res: string[]) => {
|
||||
const newData =
|
||||
res && res.length > 0
|
||||
? res.map((item: string, index: number) => {
|
||||
return {
|
||||
title: item,
|
||||
key: `${key}-${index}`,
|
||||
};
|
||||
})
|
||||
: [
|
||||
{
|
||||
title: '暂无子节点',
|
||||
key: `${key}-${1}`,
|
||||
disabled: true,
|
||||
selectable: false,
|
||||
isLeaf: true,
|
||||
},
|
||||
];
|
||||
setAutoExpandParent(true);
|
||||
setTreeData((origin: DataNode[]) => updateTreeData(origin, key, newData));
|
||||
})
|
||||
.finally(() => {
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const searchChange = (e: string) => {
|
||||
if (idenKey[0] && idenKey[0].length > 0) {
|
||||
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: getParams(idenKey[0], e) }).then((res: string[]) => {
|
||||
const newData =
|
||||
res && res.length > 0
|
||||
? res.map((item: string, index: number) => {
|
||||
return {
|
||||
title: item,
|
||||
key: `${idenKey[0]}-${index}`,
|
||||
};
|
||||
})
|
||||
: [
|
||||
// 如果查询不到节点或者所查询的父节点下没有子节点人为插入一个节点
|
||||
{
|
||||
title: e ? '未搜索到相关节点' : '暂无子节点',
|
||||
key: `${idenKey[0]}-${0}`,
|
||||
disabled: true,
|
||||
selectable: false,
|
||||
isLeaf: true,
|
||||
},
|
||||
];
|
||||
|
||||
setTreeData((origin: DataNode[]) => {
|
||||
return updateTreeData(origin, idenKey[0], newData);
|
||||
});
|
||||
// 筛选打开的节点中非选中节点的其他节点(排除选中节点以及选中节点的叶子节点)
|
||||
const filterExpandedKeys = expandedKeys.filter((item) => item.slice(0, idenKey[0].length) !== idenKey[0]);
|
||||
// 将当前选中的节点再次合并
|
||||
const newExpandedKeys = [...filterExpandedKeys, ...idenKey];
|
||||
setExpandedKeys(newExpandedKeys);
|
||||
setLoadedKeys(newExpandedKeys);
|
||||
setAutoExpandParent(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 展开收起
|
||||
const onExpand = (keys: any, arg: any) => {
|
||||
const { node, expanded } = arg;
|
||||
let filterExpandedKeys = keys;
|
||||
let newLoadKeys = loadedKeys;
|
||||
setChildrenClose(false);
|
||||
if (!expanded) {
|
||||
idenKey[0] !== node.key && (idenKey[0] as string)?.slice(0, node.key.length) === node.key && setIdenKey([]);
|
||||
filterExpandedKeys = keys.filter((item: string) => {
|
||||
return item !== node.key && item.slice(0, node.key.length) !== node.key;
|
||||
});
|
||||
newLoadKeys = loadedKeys.filter((i: string) => i.slice(0, node.key.length) !== node.key);
|
||||
setChildrenClose(true);
|
||||
}
|
||||
setAutoExpandParent(false);
|
||||
setExpandedKeys(filterExpandedKeys);
|
||||
setLoadedKeys(newLoadKeys);
|
||||
};
|
||||
|
||||
const onLoad = (loadedKeys: string[]) => {
|
||||
setLoadedKeys(loadedKeys);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// treeRef?.current?.scrollTo({ key: idenKey[0] });
|
||||
const pathKey = getPathByKey(idenKey[0], treeData);
|
||||
setPathList(pathKey);
|
||||
}, [idenKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (detailTreeData.length > 0) {
|
||||
setTreeData(detailTreeData);
|
||||
}
|
||||
}, [detailTreeData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchInput
|
||||
onSearch={searchChange}
|
||||
attrs={{
|
||||
placeholder: '在当前节点进行搜索',
|
||||
// onChange: searchChange,
|
||||
size: 'small',
|
||||
style: {
|
||||
marginBottom: '15px',
|
||||
},
|
||||
// value: searchValue,
|
||||
// onChange: (value: string) => setSearchValue(value),
|
||||
}}
|
||||
/>
|
||||
<div className="zk-detail-layout-left-content-text">
|
||||
<Tree
|
||||
// ref={treeRef}
|
||||
// height={300}
|
||||
onLoad={onLoad}
|
||||
loadData={onLoadData}
|
||||
loadedKeys={loadedKeys}
|
||||
expandedKeys={expandedKeys}
|
||||
selectedKeys={idenKey}
|
||||
onExpand={onExpand}
|
||||
autoExpandParent={autoExpandParent}
|
||||
onSelect={onSelect}
|
||||
treeData={treeData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZKDetailMenu;
|
||||
@@ -1,133 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Tag } from 'knowdesign';
|
||||
export interface DetailMenuType {
|
||||
detailTreeData: any;
|
||||
setDetailInfoLoading: (loading: boolean) => void;
|
||||
setPathList: (pathList: DataNode[]) => void;
|
||||
setIdenKey: (idenKey: string[]) => void;
|
||||
idenKey: string[];
|
||||
}
|
||||
export interface DataNode {
|
||||
title: string;
|
||||
key: string;
|
||||
isLeaf?: boolean;
|
||||
children?: DataNode[];
|
||||
}
|
||||
|
||||
// 角色
|
||||
const roleType: any = {
|
||||
leader: 'Leader',
|
||||
follower: 'Follower',
|
||||
ovsever: 'Obsever',
|
||||
};
|
||||
|
||||
export const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
|
||||
return list.map((node) => {
|
||||
if (node.key === key) {
|
||||
return {
|
||||
...node,
|
||||
children,
|
||||
};
|
||||
}
|
||||
if (node.children) {
|
||||
return {
|
||||
...node,
|
||||
children: updateTreeData(node.children, key, children),
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
};
|
||||
|
||||
export const getZookeeperColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Host',
|
||||
dataIndex: 'host',
|
||||
key: 'host',
|
||||
width: 200,
|
||||
render: (t: string, r: any) => {
|
||||
return (
|
||||
<span>
|
||||
{t}
|
||||
{r?.status ? <Tag className="tag-success">Live</Tag> : <Tag className="tag-error">Down</Tag>}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Port',
|
||||
dataIndex: 'port',
|
||||
key: 'port',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Role',
|
||||
dataIndex: 'role',
|
||||
key: 'role',
|
||||
width: 200,
|
||||
render(t: string, r: any) {
|
||||
return (
|
||||
<Tag
|
||||
style={{
|
||||
background: t === 'leader' ? 'rgba(85,110,230,0.10)' : t === 'follower' ? 'rgba(0,192,162,0.10)' : '#fff3e4',
|
||||
color: t === 'leader' ? '#556EE6' : t === 'follower' ? '#00C0A2' : '#F58342',
|
||||
padding: '3px 6px',
|
||||
}}
|
||||
>
|
||||
{roleType[t]}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
|
||||
export const defaultPagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
position: 'bottomRight',
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
|
||||
};
|
||||
|
||||
export const getPathByKey = (curKey: string, data: DataNode[]) => {
|
||||
/** 存放搜索到的树节点到顶部节点的路径节点 */
|
||||
let result: any[] = [];
|
||||
|
||||
const traverse = (curKey: string, path: any[], data: DataNode[]) => {
|
||||
// 树为空时,不执行函数
|
||||
if (data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历存放树的数组
|
||||
for (const item of data) {
|
||||
// 遍历的数组元素存入path参数数组中
|
||||
path.push(item);
|
||||
// 如果目的节点的id值等于当前遍历元素的节点id值
|
||||
if (item.key === curKey) {
|
||||
// 把获取到的节点路径数组path赋值到result数组
|
||||
result = JSON.parse(JSON.stringify(path));
|
||||
return;
|
||||
}
|
||||
|
||||
// 当前元素的children是数组
|
||||
const children = Array.isArray(item.children) ? item.children : [];
|
||||
// 递归遍历子数组内容
|
||||
traverse(curKey, path, children);
|
||||
// 利用回溯思想,当没有在当前叶树找到目的节点,依次删除存入到的path数组路径
|
||||
path.pop();
|
||||
}
|
||||
};
|
||||
traverse(curKey, [], data);
|
||||
// 返回找到的树节点路径
|
||||
return result;
|
||||
};
|
||||
@@ -1,131 +0,0 @@
|
||||
.zookeeper-detail-drawer {
|
||||
.zk-detail-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #eff2f7;
|
||||
height: calc(100vh - 90px);
|
||||
}
|
||||
.zk-detail-layout {
|
||||
display: flex;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #eff2f7;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
&-left,
|
||||
&-right {
|
||||
&-title {
|
||||
background-color: rgba(116, 120, 141, 0.04);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: @font-family-bold;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 2px solid #eff2f7;
|
||||
}
|
||||
&-content {
|
||||
height: calc(100vh - 128px);
|
||||
}
|
||||
}
|
||||
&-left {
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
&-content {
|
||||
padding: 10px;
|
||||
&-text {
|
||||
height: calc(100vh - 190px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.dcloud-tree-title {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
.dcloud-tree-treenode {
|
||||
font-size: 13px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.dcloud-tree-node-content-wrapper {
|
||||
&:hover:not(.dcloud-tree-node-content-wrapper-normal) {
|
||||
background: #f1f3ff !important;
|
||||
color: #556ee6;
|
||||
}
|
||||
}
|
||||
.dcloud-tree .dcloud-tree-node-content-wrapper.dcloud-tree-node-selected {
|
||||
background-color: transparent;
|
||||
color: #556ee6;
|
||||
}
|
||||
}
|
||||
&-resizer {
|
||||
.resize-mask {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
cursor: col-resize;
|
||||
}
|
||||
border: 1px solid #eff2f7;
|
||||
position: absolute;
|
||||
left: 200px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
cursor: col-resize;
|
||||
&:hover {
|
||||
border: 1px solid #556ee6;
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
&-content {
|
||||
padding: 12px 20px 16px;
|
||||
overflow: auto;
|
||||
&-info {
|
||||
background: rgba(116, 120, 141, 0.04);
|
||||
border-radius: 8px;
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
&-countheight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 156px);
|
||||
}
|
||||
&-path {
|
||||
margin-bottom: 16px;
|
||||
|
||||
& > *:hover {
|
||||
color: #556ee6;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&-format {
|
||||
margin: 12px 0 6px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
}
|
||||
&-data {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: rgba(116, 120, 141, 0.04);
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px 0;
|
||||
}
|
||||
&-code {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
&-data {
|
||||
height: 100% !important;
|
||||
}
|
||||
.cm-s-default {
|
||||
height: 100% !important;
|
||||
// border-radius: 8px !important;
|
||||
overflow: hidden;
|
||||
background: rgba(116, 120, 141, 0.04);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
import { useParams, useHistory, useLocation } from 'react-router-dom';
|
||||
import { ProTable, Button, Utils, AppContainer, SearchInput } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import API from '../../api';
|
||||
import { getZookeeperColumns, defaultPagination } from './config';
|
||||
import { tableHeaderPrefix } from '@src/constants/common';
|
||||
import ZookeeperDetail from './Detail';
|
||||
import ZookeeperCard from '@src/components/CardBar/ZookeeperCard';
|
||||
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
||||
import './index.less';
|
||||
const { request } = Utils;
|
||||
|
||||
const ZookeeperList: React.FC = () => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [detailVisible, setDetailVisible] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
const [searchKeywords, setSearchKeywords] = useState('');
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
|
||||
// 请求接口获取数据
|
||||
const genData = async ({ pageNo, pageSize, filters, sorter }: any) => {
|
||||
if (global?.clusterInfo?.id === undefined) return;
|
||||
|
||||
setLoading(true);
|
||||
const params = {
|
||||
searchKeywords: searchKeywords.slice(0, 128),
|
||||
pageNo,
|
||||
pageSize,
|
||||
};
|
||||
|
||||
request(API.getZookeeperList(global?.clusterInfo?.id), { method: 'POST', data: params })
|
||||
.then((res: any) => {
|
||||
setPagination({
|
||||
current: res.pagination?.pageNo,
|
||||
pageSize: res.pagination?.pageSize,
|
||||
total: res.pagination?.total,
|
||||
});
|
||||
const newData =
|
||||
res?.bizData.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
...item?.latestMetrics?.metrics,
|
||||
};
|
||||
}) || [];
|
||||
setData(newData);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
genData({
|
||||
pageNo: 1,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}, [searchKeywords]);
|
||||
|
||||
return (
|
||||
<div key="brokerList" className="brokerList">
|
||||
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
|
||||
<DBreadcrumb
|
||||
breadcrumbs={[
|
||||
{ label: '多集群管理', aHref: '/' },
|
||||
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
|
||||
{ label: 'Zookeeper', aHref: `/cluster/${global?.clusterInfo?.id}/zookepper` },
|
||||
{ label: 'Servers', aHref: `` },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ margin: '12px 0' }}>
|
||||
<ZookeeperCard />
|
||||
</div>
|
||||
<div className="clustom-table-content">
|
||||
<div className={tableHeaderPrefix}>
|
||||
<div className={`${tableHeaderPrefix}-left`}>
|
||||
<div
|
||||
className={`${tableHeaderPrefix}-left-refresh`}
|
||||
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
|
||||
>
|
||||
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${tableHeaderPrefix}-right`}>
|
||||
<SearchInput
|
||||
onSearch={setSearchKeywords}
|
||||
attrs={{
|
||||
placeholder: '请输入Host',
|
||||
style: { width: '248px', borderRiadus: '8px' },
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
<Button type="primary" onClick={() => setDetailVisible(true)}>
|
||||
查看Zookeeper详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ProTable
|
||||
key="zookeeper-table"
|
||||
showQueryForm={false}
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
rowKey: 'zookeeper_list',
|
||||
loading: loading,
|
||||
columns: getZookeeperColumns(),
|
||||
dataSource: data,
|
||||
paginationProps: { ...pagination },
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: { y: 'calc(100vh - 400px)' },
|
||||
bordered: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{<ZookeeperDetail visible={detailVisible} setVisible={setDetailVisible} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZookeeperList;
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { MetricType } from '@src/api';
|
||||
import DraggableCharts from '@src/components/DraggableCharts';
|
||||
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
||||
import { AppContainer } from 'knowdesign';
|
||||
import ZookeeperCard from '@src/components/CardBar/ZookeeperCard';
|
||||
|
||||
const ZookeeperDashboard = (): JSX.Element => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
return (
|
||||
<>
|
||||
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
|
||||
<DBreadcrumb
|
||||
breadcrumbs={[
|
||||
{ label: '多集群管理', aHref: '/' },
|
||||
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
|
||||
{ label: 'Zookeeper', aHref: `` },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<ZookeeperCard />
|
||||
<DraggableCharts type={MetricType.Zookeeper} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZookeeperDashboard;
|
||||
@@ -7,7 +7,6 @@ import { leftMenus, systemKey } from '@src/constants/menu';
|
||||
import { ClustersPermissionMap } from './CommonConfig';
|
||||
import { getLicenseInfo } from '@src/constants/common';
|
||||
import { licenseEventBus } from '@src/constants/axiosConfig';
|
||||
import { ClusterRunState } from './MutliClusterPage/List';
|
||||
|
||||
export const NoMatch = <Redirect to="/404" />;
|
||||
|
||||
@@ -36,13 +35,6 @@ const LayoutContainer = () => {
|
||||
const [showSider, setShowSider] = useState<boolean>(!(notCurrentSystemKey || hasNoSiderPage));
|
||||
const [handledLeftMenus, setHandledLeftMenus] = useState(leftMenus());
|
||||
|
||||
const forbidenPaths = (path: string) => {
|
||||
// Raft 模式运行的集群没有 ZK 页面
|
||||
if (path.includes('zookeeper') && global.clusterInfo?.runState === ClusterRunState.Raft) {
|
||||
history.replace('/404');
|
||||
}
|
||||
};
|
||||
|
||||
const isShowMenu = useCallback(
|
||||
(nodes: ClustersPermissionMap | ClustersPermissionMap[]) => {
|
||||
let isAllow = false;
|
||||
@@ -75,7 +67,6 @@ const LayoutContainer = () => {
|
||||
if (permissionNode) {
|
||||
// 判断用户是否有当前页面的权限
|
||||
if (global.hasPermission(permissionNode)) {
|
||||
forbidenPaths(path);
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
// 用户没有当前页面权限,跳转到多集群首页
|
||||
@@ -86,7 +77,6 @@ const LayoutContainer = () => {
|
||||
return Promise.reject(false);
|
||||
}
|
||||
}
|
||||
forbidenPaths(path);
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
[global.clusterInfo, global.hasPermission, global.getMetricDefine]
|
||||
@@ -96,14 +86,12 @@ const LayoutContainer = () => {
|
||||
const notCurrentSystemKey = window.location.pathname.split('/')?.[1] !== systemKey;
|
||||
const hasNoSiderPage = noSiderPages.findIndex((item) => item.path === getCurrentPathname(pathname)) > -1;
|
||||
setShowSider(notCurrentSystemKey || hasNoSiderPage ? false : true);
|
||||
|
||||
if (pathname.startsWith('/cluster') || pathname === '/') {
|
||||
const items = pathname.split('/');
|
||||
const clusterId = items[2];
|
||||
clusterId !== global.clusterInfo?.id &&
|
||||
setHandledLeftMenus(leftMenus(clusterId, pathname === '/' ? undefined : global.clusterInfo?.runState));
|
||||
setHandledLeftMenus(leftMenus(clusterId));
|
||||
}
|
||||
}, [pathname, global.clusterInfo]);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<div id="sub-system" style={{ display: 'flex' }}>
|
||||
|
||||
@@ -22,9 +22,6 @@ import SecurityACLs from './SecurityACLs';
|
||||
import SecurityUsers from './SecurityUsers';
|
||||
import LoadRebalance from './LoadRebalance';
|
||||
|
||||
import Zookeeper from './Zookeeper';
|
||||
import ZookeeperDashboard from './ZookeeperDashboard';
|
||||
|
||||
const pageRoutes = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -118,18 +115,6 @@ const pageRoutes = [
|
||||
component: Jobs,
|
||||
noSider: false,
|
||||
},
|
||||
{
|
||||
path: 'zookeeper',
|
||||
exact: true,
|
||||
component: ZookeeperDashboard,
|
||||
noSider: false,
|
||||
},
|
||||
{
|
||||
path: 'zookeeper/servers',
|
||||
exact: true,
|
||||
component: Zookeeper,
|
||||
noSider: false,
|
||||
},
|
||||
{
|
||||
path: 'security/acls',
|
||||
exact: true,
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
|
||||
import com.xiaojukeji.know.streaming.km.core.cache.CollectedMetricsLocalCache;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerMetricService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.replica.ReplicaMetricService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService;
|
||||
@@ -82,7 +82,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
|
||||
private ReplicaMetricService replicaMetricService;
|
||||
|
||||
@Autowired
|
||||
private HealthStateService healthStateService;
|
||||
private HealthScoreService healthScoreService;
|
||||
|
||||
@Autowired
|
||||
private KafkaJMXClient kafkaJMXClient;
|
||||
@@ -108,6 +108,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
|
||||
registerVCHandler( BROKER_METHOD_GET_HEALTH_SCORE, this::getMetricHealthScore);
|
||||
registerVCHandler( BROKER_METHOD_GET_PARTITIONS_SKEW, this::getPartitionsSkew);
|
||||
registerVCHandler( BROKER_METHOD_GET_LEADERS_SKEW, this::getLeadersSkew);
|
||||
// registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, this::getLogSize);
|
||||
|
||||
registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, V_0_10_0_0, V_1_0_0, "getLogSizeFromJmx", this::getLogSizeFromJmx);
|
||||
registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, V_1_0_0, V_MAX, "getLogSizeFromClient", this::getLogSizeFromClient);
|
||||
@@ -317,7 +318,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
|
||||
Long clusterId = param.getClusterId();
|
||||
Integer brokerId = param.getBrokerId();
|
||||
|
||||
BrokerMetrics brokerMetrics = healthStateService.calBrokerHealthMetrics(clusterId, brokerId);
|
||||
BrokerMetrics brokerMetrics = healthScoreService.calBrokerHealthScore(clusterId, brokerId);
|
||||
return Result.buildSuc(brokerMetrics);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok
|
||||
newBrokerPO.setId(inDBBrokerPO.getId());
|
||||
newBrokerPO.setStatus(Constant.ALIVE);
|
||||
newBrokerPO.setCreateTime(inDBBrokerPO.getCreateTime());
|
||||
newBrokerPO.setUpdateTime(new Date());
|
||||
newBrokerPO.setUpdateTime(inDBBrokerPO.getUpdateTime());
|
||||
if (newBrokerPO.getStartTimestamp() == null) {
|
||||
// 如果当前broker获取不到启动时间
|
||||
// 如果DB中的broker状态为down,则使用当前时间,否则使用db中已有broker的时间
|
||||
@@ -363,11 +363,11 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok
|
||||
try {
|
||||
Long startTime = jmxDAO.getServerStartTime(clusterPhyId, newNode.host(), null, jmxConfig);
|
||||
|
||||
return Broker.buildFrom(clusterPhyId, newNode, startTime, jmxConfig);
|
||||
return Broker.buildFrom(clusterPhyId, newNode, startTime);
|
||||
} catch (Exception e) {
|
||||
log.error("class=BrokerServiceImpl||method=getStartTimeAndBuildBroker||clusterPhyId={}||brokerNode={}||jmxConfig={}||errMsg=exception!", clusterPhyId, newNode, jmxConfig, e);
|
||||
}
|
||||
|
||||
return Broker.buildFrom(clusterPhyId, newNode, null, jmxConfig);
|
||||
return Broker.buildFrom(clusterPhyId, newNode, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterMetricService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.group.GroupService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.job.JobService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.kafkacontroller.KafkaControllerService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
|
||||
@@ -85,7 +85,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
|
||||
public static final String CLUSTER_METHOD_GET_TOTAL_LOG_SIZE = "getTotalLogSize";
|
||||
public static final String CLUSTER_METHOD_GET_PARTITION_SIZE = "getPartitionSize";
|
||||
public static final String CLUSTER_METHOD_GET_PARTITION_NO_LEADER_SIZE = "getPartitionNoLeaderSize";
|
||||
public static final String CLUSTER_METHOD_GET_HEALTH_METRICS = "getClusterHealthMetrics";
|
||||
public static final String CLUSTER_METHOD_GET_HEALTH_SCORE = "getClusterHealthScore";
|
||||
public static final String CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_TOTAL_BROKERS_JMX = "getMetricFromKafkaByTotalBrokersJMX";
|
||||
public static final String CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_CONTROLLER_JMX = "getMetricFromKafkaByControllerJMX";
|
||||
public static final String CLUSTER_METHOD_GET_ZK_COUNT = "getZKCount";
|
||||
@@ -114,7 +114,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
|
||||
public static final String CLUSTER_METHOD_GET_JOBS_FAILED = "getJobsFailed";
|
||||
|
||||
@Autowired
|
||||
private HealthStateService healthStateService;
|
||||
private HealthScoreService healthScoreService;
|
||||
|
||||
@Autowired
|
||||
private BrokerService brokerService;
|
||||
@@ -188,7 +188,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
|
||||
registerVCHandler( CLUSTER_METHOD_GET_PARTITION_SIZE, this::getPartitionSize);
|
||||
registerVCHandler( CLUSTER_METHOD_GET_PARTITION_NO_LEADER_SIZE, this::getPartitionNoLeaderSize);
|
||||
|
||||
registerVCHandler( CLUSTER_METHOD_GET_HEALTH_METRICS, this::getClusterHealthMetrics);
|
||||
registerVCHandler( CLUSTER_METHOD_GET_HEALTH_SCORE, this::getClusterHealthScore);
|
||||
|
||||
registerVCHandler( CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_TOTAL_BROKERS_JMX, this::getMetricFromKafkaByTotalBrokersJMX);
|
||||
registerVCHandler( CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_CONTROLLER_JMX, this::getMetricFromKafkaByControllerJMX);
|
||||
@@ -361,13 +361,15 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
|
||||
}
|
||||
|
||||
|
||||
private Result<ClusterMetrics> getClusterHealthMetrics(VersionItemParam metricParam){
|
||||
/**
|
||||
* 获取集群的健康分
|
||||
*/
|
||||
private Result<ClusterMetrics> getClusterHealthScore(VersionItemParam metricParam){
|
||||
ClusterMetricParam param = (ClusterMetricParam)metricParam;
|
||||
ClusterMetrics clusterMetrics = healthStateService.calClusterHealthMetrics(param.getClusterId());
|
||||
ClusterMetrics clusterMetrics = healthScoreService.calClusterHealthScore(param.getClusterId());
|
||||
return Result.buildSuc(clusterMetrics);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取集群的 totalLogSize
|
||||
* @param metricParam
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.group.GroupMetricService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.group.GroupService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.version.BaseMetricService;
|
||||
import com.xiaojukeji.know.streaming.km.persistence.es.dao.GroupMetricESDAO;
|
||||
@@ -64,7 +64,7 @@ public class GroupMetricServiceImpl extends BaseMetricService implements GroupMe
|
||||
private GroupService groupService;
|
||||
|
||||
@Autowired
|
||||
private HealthStateService healthStateService;
|
||||
private HealthScoreService healthScoreService;
|
||||
|
||||
@Autowired
|
||||
private PartitionService partitionService;
|
||||
@@ -265,8 +265,8 @@ public class GroupMetricServiceImpl extends BaseMetricService implements GroupMe
|
||||
private Result<List<GroupMetrics>> getMetricHealthScore(VersionItemParam param) {
|
||||
GroupMetricParam groupMetricParam = (GroupMetricParam)param;
|
||||
|
||||
return Result.buildSuc(Arrays.asList(
|
||||
healthStateService.calGroupHealthMetrics(groupMetricParam.getClusterPhyId(), groupMetricParam.getGroupName()))
|
||||
return Result.buildSuc(Arrays.asList(healthScoreService.calGroupHealthScore(
|
||||
groupMetricParam.getClusterPhyId(), groupMetricParam.getGroupName()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.core.service.health.checker.zookeeper;
|
||||
|
||||
import com.didiglobal.logi.log.ILog;
|
||||
import com.didiglobal.logi.log.LogFactory;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthAmountRatioConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ZookeeperMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.ZookeeperMetricParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.zookeeper.ZookeeperParam;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.ZookeeperInfo;
|
||||
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.zookeeper.ZKRoleEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.zookeeper.ZookeeperUtils;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperMetricService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class HealthCheckZookeeperService extends AbstractHealthCheckService {
|
||||
private static final ILog log = LogFactory.getLog(HealthCheckZookeeperService.class);
|
||||
|
||||
@Autowired
|
||||
private ClusterPhyService clusterPhyService;
|
||||
|
||||
@Autowired
|
||||
private ZookeeperService zookeeperService;
|
||||
|
||||
@Autowired
|
||||
private ZookeeperMetricService zookeeperMetricService;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_BRAIN_SPLIT.getConfigName(), this::checkBrainSplit);
|
||||
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_OUTSTANDING_REQUESTS.getConfigName(), this::checkOutstandingRequests);
|
||||
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_WATCH_COUNT.getConfigName(), this::checkWatchCount);
|
||||
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_ALIVE_CONNECTIONS.getConfigName(), this::checkAliveConnections);
|
||||
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_APPROXIMATE_DATA_SIZE.getConfigName(), this::checkApproximateDataSize);
|
||||
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_SENT_RATE.getConfigName(), this::checkSentRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClusterPhyParam> getResList(Long clusterPhyId) {
|
||||
ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(clusterPhyId);
|
||||
if (clusterPhy == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
try {
|
||||
return Arrays.asList(new ZookeeperParam(
|
||||
clusterPhyId,
|
||||
ZookeeperUtils.connectStringParser(clusterPhy.getZookeeper()),
|
||||
ConvertUtil.str2ObjByJson(clusterPhy.getZkProperties(), ZKConfig.class)
|
||||
));
|
||||
} catch (Exception e) {
|
||||
log.error("class=HealthCheckZookeeperService||method=getResList||clusterPhyId={}||errMsg=exception!", clusterPhyId, e);
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthCheckDimensionEnum getHealthCheckDimensionEnum() {
|
||||
return HealthCheckDimensionEnum.ZOOKEEPER;
|
||||
}
|
||||
|
||||
private HealthCheckResult checkBrainSplit(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
|
||||
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
|
||||
HealthCompareValueConfig valueConfig = (HealthCompareValueConfig) singleConfigSimpleTuple.getV2();
|
||||
|
||||
List<ZookeeperInfo> infoList = zookeeperService.listFromDBByCluster(param.getClusterPhyId());
|
||||
HealthCheckResult checkResult = new HealthCheckResult(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
|
||||
HealthCheckNameEnum.ZK_BRAIN_SPLIT.getConfigName(),
|
||||
param.getClusterPhyId(),
|
||||
""
|
||||
);
|
||||
|
||||
long value = infoList.stream().filter(elem -> ZKRoleEnum.LEADER.getRole().equals(elem.getRole())).count();
|
||||
|
||||
checkResult.setPassed(value == valueConfig.getValue().longValue() ? Constant.YES : Constant.NO);
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
private HealthCheckResult checkOutstandingRequests(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
|
||||
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
|
||||
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
|
||||
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
|
||||
new ZookeeperMetricParam(
|
||||
param.getClusterPhyId(),
|
||||
param.getZkAddressList(),
|
||||
param.getZkConfig(),
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS
|
||||
)
|
||||
);
|
||||
if (metricsResult.failed() || !metricsResult.hasData()) {
|
||||
log.error(
|
||||
"class=HealthCheckZookeeperService||method=checkOutstandingRequests||param={}||config={}||result={}||errMsg=get metrics failed",
|
||||
param, valueConfig, metricsResult
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
HealthCheckResult checkResult = new HealthCheckResult(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
|
||||
HealthCheckNameEnum.ZK_OUTSTANDING_REQUESTS.getConfigName(),
|
||||
param.getClusterPhyId(),
|
||||
""
|
||||
);
|
||||
|
||||
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS);
|
||||
|
||||
|
||||
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
private HealthCheckResult checkWatchCount(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
|
||||
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
|
||||
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
|
||||
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
|
||||
new ZookeeperMetricParam(
|
||||
param.getClusterPhyId(),
|
||||
param.getZkAddressList(),
|
||||
param.getZkConfig(),
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT
|
||||
)
|
||||
);
|
||||
|
||||
if (metricsResult.failed() || !metricsResult.hasData()) {
|
||||
log.error(
|
||||
"class=HealthCheckZookeeperService||method=checkWatchCount||param={}||config={}||result={}||errMsg=get metrics failed",
|
||||
param, valueConfig, metricsResult
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
HealthCheckResult checkResult = new HealthCheckResult(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
|
||||
HealthCheckNameEnum.ZK_WATCH_COUNT.getConfigName(),
|
||||
param.getClusterPhyId(),
|
||||
""
|
||||
);
|
||||
|
||||
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT);
|
||||
|
||||
|
||||
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
private HealthCheckResult checkAliveConnections(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
|
||||
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
|
||||
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
|
||||
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
|
||||
new ZookeeperMetricParam(
|
||||
param.getClusterPhyId(),
|
||||
param.getZkAddressList(),
|
||||
param.getZkConfig(),
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_NUM_ALIVE_CONNECTIONS
|
||||
)
|
||||
);
|
||||
|
||||
if (metricsResult.failed() || !metricsResult.hasData()) {
|
||||
log.error(
|
||||
"class=HealthCheckZookeeperService||method=checkAliveConnections||param={}||config={}||result={}||errMsg=get metrics failed",
|
||||
param, valueConfig, metricsResult
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
HealthCheckResult checkResult = new HealthCheckResult(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
|
||||
HealthCheckNameEnum.ZK_ALIVE_CONNECTIONS.getConfigName(),
|
||||
param.getClusterPhyId(),
|
||||
""
|
||||
);
|
||||
|
||||
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_NUM_ALIVE_CONNECTIONS);
|
||||
|
||||
|
||||
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
private HealthCheckResult checkApproximateDataSize(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
|
||||
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
|
||||
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
|
||||
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
|
||||
new ZookeeperMetricParam(
|
||||
param.getClusterPhyId(),
|
||||
param.getZkAddressList(),
|
||||
param.getZkConfig(),
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_APPROXIMATE_DATA_SIZE
|
||||
)
|
||||
);
|
||||
|
||||
if (metricsResult.failed() || !metricsResult.hasData()) {
|
||||
log.error(
|
||||
"class=HealthCheckZookeeperService||method=checkApproximateDataSize||param={}||config={}||result={}||errMsg=get metrics failed",
|
||||
param, valueConfig, metricsResult
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
HealthCheckResult checkResult = new HealthCheckResult(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
|
||||
HealthCheckNameEnum.ZK_APPROXIMATE_DATA_SIZE.getConfigName(),
|
||||
param.getClusterPhyId(),
|
||||
""
|
||||
);
|
||||
|
||||
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_APPROXIMATE_DATA_SIZE);
|
||||
|
||||
|
||||
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
private HealthCheckResult checkSentRate(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
|
||||
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
|
||||
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
|
||||
|
||||
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
|
||||
new ZookeeperMetricParam(
|
||||
param.getClusterPhyId(),
|
||||
param.getZkAddressList(),
|
||||
param.getZkConfig(),
|
||||
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_PACKETS_SENT
|
||||
)
|
||||
);
|
||||
|
||||
if (metricsResult.failed() || !metricsResult.hasData()) {
|
||||
log.error(
|
||||
"class=HealthCheckZookeeperService||method=checkSentRate||param={}||config={}||result={}||errMsg=get metrics failed",
|
||||
param, valueConfig, metricsResult
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
HealthCheckResult checkResult = new HealthCheckResult(
|
||||
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
|
||||
HealthCheckNameEnum.ZK_SENT_RATE.getConfigName(),
|
||||
param.getClusterPhyId(),
|
||||
""
|
||||
);
|
||||
|
||||
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_PACKETS_SENT);
|
||||
|
||||
|
||||
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.xiaojukeji.know.streaming.km.core.service.health.score;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface HealthScoreService {
|
||||
/**
|
||||
* 获取集群健康分指标
|
||||
* @param clusterPhyId 集群ID
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
ClusterMetrics calClusterHealthScore(Long clusterPhyId);
|
||||
|
||||
/**
|
||||
* 获取集群健康分指标
|
||||
* @param clusterPhyId 集群ID
|
||||
* @param topicName Topic名称
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
TopicMetrics calTopicHealthScore(Long clusterPhyId, String topicName);
|
||||
|
||||
/**
|
||||
* 获取集群健康分指标
|
||||
* @param clusterPhyId 集群ID
|
||||
* @param brokerId brokerId
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
BrokerMetrics calBrokerHealthScore(Long clusterPhyId, Integer brokerId);
|
||||
|
||||
/**
|
||||
* 获取集群健康分指标
|
||||
* @param clusterPhyId 集群ID
|
||||
* @param groupName group名称
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
GroupMetrics calGroupHealthScore(Long clusterPhyId, String groupName);
|
||||
|
||||
/**
|
||||
* 获取集群健康分结果
|
||||
* @param clusterPhyId 集群ID
|
||||
* @return
|
||||
*/
|
||||
List<HealthScoreResult> getClusterHealthScoreResult(Long clusterPhyId);
|
||||
|
||||
/**
|
||||
* 获取集群某个维度健康分结果
|
||||
* @param clusterPhyId 集群ID
|
||||
* @param dimensionEnum 维度
|
||||
* @return
|
||||
*/
|
||||
List<HealthScoreResult> getDimensionHealthScoreResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum);
|
||||
|
||||
/**
|
||||
* 获取集群某个资源的健康分结果
|
||||
* @param clusterPhyId 集群ID
|
||||
* @param dimension 维度
|
||||
* @param resNme 资源名称
|
||||
* @return
|
||||
*/
|
||||
List<HealthScoreResult> getResHealthScoreResult(Long clusterPhyId, Integer dimension, String resNme);
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
package com.xiaojukeji.know.streaming.km.core.service.health.score.impl;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO;
|
||||
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
|
||||
import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService;
|
||||
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetricVersionItems.*;
|
||||
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.*;
|
||||
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ClusterMetricVersionItems.*;
|
||||
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.*;
|
||||
|
||||
|
||||
@Service
|
||||
public class HealthScoreServiceImpl implements HealthScoreService {
|
||||
@Autowired
|
||||
private HealthCheckResultService healthCheckResultService;
|
||||
|
||||
@Override
|
||||
public ClusterMetrics calClusterHealthScore(Long clusterPhyId) {
|
||||
List<HealthScoreResult> allHealthScoreResultList = this.getClusterHealthScoreResult(clusterPhyId);
|
||||
Map<Integer, List<HealthScoreResult>> healthScoreResultMap = new HashMap<>();
|
||||
for (HealthScoreResult healthScoreResult: allHealthScoreResultList) {
|
||||
healthScoreResultMap.putIfAbsent(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension(), new ArrayList<>());
|
||||
healthScoreResultMap.get(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension()).add(healthScoreResult);
|
||||
}
|
||||
|
||||
Float healthScore = 0f;
|
||||
Float healthCheckPassed = 0f;
|
||||
Float healthCheckTotal = 0f;
|
||||
|
||||
ClusterMetrics metrics = new ClusterMetrics(clusterPhyId);
|
||||
|
||||
// cluster维度
|
||||
List<HealthScoreResult> healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.CLUSTER.getDimension());
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_CLUSTER, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER, 0.0f);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER, 0.0f);
|
||||
|
||||
healthScore += 0;
|
||||
healthCheckPassed += 0;
|
||||
healthCheckTotal += 0;
|
||||
} else {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_CLUSTER, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER, this.getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER, Float.valueOf(healthScoreResultList.size()));
|
||||
|
||||
healthScore += this.getAllHealthScore(healthScoreResultList);
|
||||
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER);
|
||||
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER);
|
||||
}
|
||||
|
||||
// broker维度
|
||||
healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.BROKER.getDimension());
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_BROKERS, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS, 0.0f);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS, 0.0f);
|
||||
|
||||
healthScore += 0;
|
||||
healthCheckPassed += 0;
|
||||
healthCheckTotal += 0;
|
||||
} else {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_BROKERS, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS, this.getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS, Float.valueOf(healthScoreResultList.size()));
|
||||
|
||||
healthScore += this.getAllHealthScore(healthScoreResultList);
|
||||
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS);
|
||||
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS);
|
||||
}
|
||||
|
||||
|
||||
// topic维度
|
||||
healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.TOPIC.getDimension());
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_TOPICS, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_TOPICS, 0.0f);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_TOPICS, 0.0f);
|
||||
|
||||
healthScore += 0;
|
||||
healthCheckPassed += 0;
|
||||
healthCheckTotal += 0;
|
||||
} else {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_TOPICS, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_TOPICS, this.getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_TOPICS, Float.valueOf(healthScoreResultList.size()));
|
||||
|
||||
healthScore += this.getAllHealthScore(healthScoreResultList);
|
||||
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_TOPICS);
|
||||
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_TOPICS);
|
||||
}
|
||||
|
||||
// group维度
|
||||
healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.GROUP.getDimension());
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_GROUPS, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS, 0.0f);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS, 0.0f);
|
||||
|
||||
healthScore += 0;
|
||||
healthCheckPassed += 0;
|
||||
healthCheckTotal += 0;
|
||||
} else {
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_GROUPS, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS, this.getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS, Float.valueOf(healthScoreResultList.size()));
|
||||
|
||||
healthScore += this.getAllHealthScore(healthScoreResultList);
|
||||
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS);
|
||||
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS);
|
||||
}
|
||||
|
||||
// 集群最终的
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE, Math.max(healthScore, Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED, healthCheckPassed);
|
||||
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL, healthCheckTotal);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopicMetrics calTopicHealthScore(Long clusterPhyId, String topicName) {
|
||||
List<HealthScoreResult> healthScoreResultList = this.getResHealthScoreResult(clusterPhyId, HealthCheckDimensionEnum.TOPIC.getDimension(), topicName);
|
||||
|
||||
TopicMetrics metrics = new TopicMetrics(topicName, clusterPhyId,true);
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_SCORE, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_PASSED, 0.0f);
|
||||
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_TOTAL, 0.0f);
|
||||
} else {
|
||||
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_SCORE, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_PASSED, getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size()));
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrokerMetrics calBrokerHealthScore(Long clusterPhyId, Integer brokerId) {
|
||||
List<HealthScoreResult> healthScoreResultList = this.getResHealthScoreResult(clusterPhyId, HealthCheckDimensionEnum.BROKER.getDimension(), String.valueOf(brokerId));
|
||||
|
||||
BrokerMetrics metrics = new BrokerMetrics(clusterPhyId, brokerId);
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(BROKER_METRIC_HEALTH_SCORE, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_PASSED, 0.0f);
|
||||
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_TOTAL, 0.0f);
|
||||
} else {
|
||||
metrics.getMetrics().put(BROKER_METRIC_HEALTH_SCORE, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_PASSED, getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size()));
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupMetrics calGroupHealthScore(Long clusterPhyId, String groupName) {
|
||||
List<HealthScoreResult> healthScoreResultList = this.getResHealthScoreResult(clusterPhyId, HealthCheckDimensionEnum.GROUP.getDimension(), groupName);
|
||||
|
||||
GroupMetrics metrics = new GroupMetrics(clusterPhyId, groupName, true);
|
||||
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
|
||||
metrics.getMetrics().put(GROUP_METRIC_HEALTH_SCORE, Constant.MIN_HEALTH_SCORE);
|
||||
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_PASSED, 0.0f);
|
||||
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_TOTAL, 0.0f);
|
||||
} else {
|
||||
metrics.getMetrics().put(GROUP_METRIC_HEALTH_SCORE, Math.max(this.getDimensionHealthScore(healthScoreResultList),Constant.MIN_HEALTH_SCORE));
|
||||
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_PASSED, getHealthCheckPassed(healthScoreResultList));
|
||||
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size()));
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthScoreResult> getClusterHealthScoreResult(Long clusterPhyId) {
|
||||
List<HealthCheckResultPO> poList = healthCheckResultService.getClusterHealthCheckResult(clusterPhyId);
|
||||
|
||||
// <检查项,<检查结果>>
|
||||
Map<String, List<HealthCheckResultPO>> checkResultMap = new HashMap<>();
|
||||
for (HealthCheckResultPO po: poList) {
|
||||
checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>());
|
||||
checkResultMap.get(po.getConfigName()).add(po);
|
||||
}
|
||||
|
||||
// 每个维度的权重和
|
||||
Map<String, Float> dimensionTotalWeightMap = new HashMap<>();
|
||||
Float allDimensionTotalWeight = 0f;
|
||||
|
||||
Map<String, BaseClusterHealthConfig> configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId);
|
||||
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.values()) {
|
||||
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
|
||||
if (baseConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
allDimensionTotalWeight += baseConfig.getWeight();
|
||||
|
||||
Float totalWeight = dimensionTotalWeightMap.getOrDefault(nameEnum.getDimensionEnum().name(), 0f);
|
||||
dimensionTotalWeightMap.put(nameEnum.getDimensionEnum().name(), totalWeight + baseConfig.getWeight());
|
||||
}
|
||||
|
||||
List<HealthScoreResult> healthScoreResultList = new ArrayList<>();
|
||||
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.values()) {
|
||||
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
|
||||
if (baseConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
healthScoreResultList.add(new HealthScoreResult(
|
||||
nameEnum,
|
||||
dimensionTotalWeightMap.getOrDefault(nameEnum.getDimensionEnum().name(), 0f),
|
||||
allDimensionTotalWeight,
|
||||
baseConfig,
|
||||
checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))
|
||||
);
|
||||
}
|
||||
|
||||
return healthScoreResultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthScoreResult> getDimensionHealthScoreResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) {
|
||||
List<HealthCheckResultPO> poList = healthCheckResultService.getClusterResourcesHealthCheckResult(clusterPhyId, dimensionEnum.getDimension());
|
||||
|
||||
// <检查项,<通过的数量,不通过的数量>>
|
||||
Map<String, List<HealthCheckResultPO>> checkResultMap = new HashMap<>();
|
||||
for (HealthCheckResultPO po: poList) {
|
||||
checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>());
|
||||
checkResultMap.get(po.getConfigName()).add(po);
|
||||
}
|
||||
|
||||
// 每个维度的权重和
|
||||
Float totalWeight = 0f;
|
||||
|
||||
Map<String, BaseClusterHealthConfig> configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId);
|
||||
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimension(dimensionEnum)) {
|
||||
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
|
||||
if (baseConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalWeight += baseConfig.getWeight();
|
||||
}
|
||||
|
||||
Float allDimensionTotalWeight = configMap.values().stream().map(elem -> elem.getWeight()).reduce(Float::sum).get();
|
||||
|
||||
List<HealthScoreResult> healthScoreResultList = new ArrayList<>();
|
||||
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimension(dimensionEnum)) {
|
||||
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
|
||||
if (baseConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
healthScoreResultList.add(new HealthScoreResult(nameEnum, totalWeight, allDimensionTotalWeight, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>())));
|
||||
}
|
||||
|
||||
return healthScoreResultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthScoreResult> getResHealthScoreResult(Long clusterPhyId, Integer dimension, String resNme) {
|
||||
List<HealthCheckResultPO> poList = healthCheckResultService.getClusterResourcesHealthCheckResult(clusterPhyId, dimension);
|
||||
Map<String, List<HealthCheckResultPO>> checkResultMap = new HashMap<>();
|
||||
for (HealthCheckResultPO po: poList) {
|
||||
checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>());
|
||||
checkResultMap.get(po.getConfigName()).add(po);
|
||||
}
|
||||
|
||||
// 每个维度的权重和
|
||||
Float totalWeight = 0f;
|
||||
|
||||
Map<String, BaseClusterHealthConfig> configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId);
|
||||
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimensionCode(dimension)) {
|
||||
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
|
||||
if (baseConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalWeight += baseConfig.getWeight();
|
||||
}
|
||||
|
||||
List<HealthScoreResult> healthScoreResultList = new ArrayList<>();
|
||||
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimensionCode(dimension)) {
|
||||
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
|
||||
if (baseConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
healthScoreResultList.add(new HealthScoreResult(nameEnum, totalWeight, null, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>())));
|
||||
}
|
||||
|
||||
return healthScoreResultList;
|
||||
}
|
||||
|
||||
private float getAllHealthScore(List<HealthScoreResult> healthScoreResultList){
|
||||
if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;}
|
||||
|
||||
return healthScoreResultList.stream().map(elem -> elem.calAllWeightHealthScore()).reduce(Float::sum).get();
|
||||
}
|
||||
|
||||
private float getDimensionHealthScore(List<HealthScoreResult> healthScoreResultList){
|
||||
if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;}
|
||||
|
||||
return healthScoreResultList.stream().map(elem -> elem.calDimensionWeightHealthScore()).reduce(Float::sum).get();
|
||||
}
|
||||
|
||||
private float getHealthCheckPassed(List<HealthScoreResult> healthScoreResultList){
|
||||
if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;}
|
||||
|
||||
return Float.valueOf(healthScoreResultList.stream().filter(elem -> elem.getPassed()).count());
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.xiaojukeji.know.streaming.km.core.service.health.state;
|
||||
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult;
|
||||
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.*;
|
||||
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface HealthStateService {
|
||||
/**
|
||||
* 集群健康指标
|
||||
*/
|
||||
ClusterMetrics calClusterHealthMetrics(Long clusterPhyId);
|
||||
|
||||
/**
|
||||
* 获取Broker健康指标
|
||||
*/
|
||||
BrokerMetrics calBrokerHealthMetrics(Long clusterPhyId, Integer brokerId);
|
||||
|
||||
/**
|
||||
* 获取Topic健康指标
|
||||
*/
|
||||
TopicMetrics calTopicHealthMetrics(Long clusterPhyId, String topicName);
|
||||
|
||||
/**
|
||||
* 获取Group健康指标
|
||||
*/
|
||||
GroupMetrics calGroupHealthMetrics(Long clusterPhyId, String groupName);
|
||||
|
||||
/**
|
||||
* 获取Zookeeper健康指标
|
||||
*/
|
||||
ZookeeperMetrics calZookeeperHealthMetrics(Long clusterPhyId);
|
||||
|
||||
/**
|
||||
* 获取集群健康检查结果
|
||||
*/
|
||||
List<HealthScoreResult> getClusterHealthResult(Long clusterPhyId);
|
||||
|
||||
/**
|
||||
* 获取集群某个维度健康检查结果
|
||||
*/
|
||||
List<HealthScoreResult> getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum);
|
||||
|
||||
/**
|
||||
* 获取集群某个资源的健康检查结果
|
||||
*/
|
||||
List<HealthScoreResult> getResHealthResult(Long clusterPhyId, Integer dimension, String resNme);
|
||||
}
|
||||