From 5c9096d56467aedcccf6c750de103173e91665bd Mon Sep 17 00:00:00 2001 From: "night.liang" Date: Tue, 1 Nov 2022 10:41:04 +0800 Subject: [PATCH 001/150] [Bugfix] fix replica dsl (#708) --- .../es/dao/ReplicationMetricESDAO.java | 2 +- .../getAggSingleReplicationMetrics | 74 +++++++++++-------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java index 1f604cc0..fbd874f2 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java @@ -61,7 +61,7 @@ public class ReplicationMetricESDAO extends BaseMetricESDAO { String aggDsl = buildAggsDSL(metrics, aggType); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_REPLICATION_AGG_SINGLE_METRICS, clusterPhyId, topic, brokerId, partitionId, startTime, endTime, aggDsl); + DslsConstant.GET_REPLICATION_AGG_SINGLE_METRICS, clusterPhyId, brokerId,topic, partitionId, startTime, endTime, aggDsl); return esOpClient.performRequestWithRouting(String.valueOf(brokerId), realIndex, dsl, s -> handleSingleESQueryResponse(s, metrics, aggType), 3); diff --git a/km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics b/km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics index f01e0259..59e4abca 100644 --- a/km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics +++ b/km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics @@ -1,34 +1,48 @@ { - "size": 0, - "query": { - "bool": { - "must": [ - { - "term": { - "clusterPhyId": { - "value": %d - } - } - }, - { - "term": { - "brokerId": { - "value": %d - } - } - }, - { - "range": { - "timestamp": { - "gte": %d, - "lte": %d - } - } + "size":0, + "query":{ + "bool":{ + "must":[ + { + "term":{ + "clusterPhyId":{ + "value":%d + } + } + }, + { + "term":{ + "brokerId":{ + "value":%d + } + } + }, + { + "term":{ + "topic":{ + "value":"%s" + } + } + }, + { + "term":{ + "partitionId":{ + "value":%d + } + } + }, + { + "range":{ + "timestamp":{ + "gte":%d, + "lte":%d + } + } + } + ] } - ] - } - }, - "aggs": { - %s + }, + "aggs":{ + %s } } \ No newline at end of file From a60378361511b4ba324d96adc13cb1a193f784de Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 1 Nov 2022 14:10:01 +0800 Subject: [PATCH 002/150] =?UTF-8?q?[Optimize].gitignore=20=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20flatten.xml=20=E8=BF=87=E6=BB=A4=EF=BC=8C?= =?UTF-8?q?=E4=B8=BA=E5=90=8E=E7=BB=AD=E5=BC=95=E5=85=A5flatten=20?= =?UTF-8?q?=E5=81=9A=E5=87=86=E5=A4=87(#732)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 045ec395..cfef6e76 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,8 @@ out/* dist/ dist/* km-rest/src/main/resources/templates/ -*dependency-reduced-pom* \ No newline at end of file +*dependency-reduced-pom* +#filter flattened xml +*/.flattened-pom.xml +.flattened-pom.xml +*/*/.flattened-pom.xml \ No newline at end of file From 34926887332e22f62026fe04e0ba0b7e61f47da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Sat, 29 Oct 2022 17:43:26 +0800 Subject: [PATCH 003/150] =?UTF-8?q?feat:Consumer=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=8C=89=E9=92=AE=E6=96=B0=E5=A2=9Ehover?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/pages/Consumers/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx index 53c0f02c..81674cf3 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/display-name */ import React, { useState, useEffect } from 'react'; -import { AppContainer, Divider, Form, Input, ProTable, Select, Utils } from 'knowdesign'; +import { AppContainer, Divider, Tooltip, Input, ProTable, Select, Utils } from 'knowdesign'; import { IconFont } from '@knowdesign/icons'; import './index.less'; import Api from '@src/api/index'; @@ -186,9 +186,11 @@ const AutoPage = (props: any) => { {scene !== 'topicDetail' && (
-
searchFn()}> - -
+ +
searchFn()}> + +
+
Date: Mon, 31 Oct 2022 11:02:52 +0800 Subject: [PATCH 004/150] =?UTF-8?q?styles:=E6=B6=88=E6=81=AF=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E6=B5=8B=E8=AF=95=E5=BC=B9=E6=A1=86=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E6=95=B0=E6=98=BE=E7=A4=BA=E5=AD=97=E4=BD=93=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/layout-clusters-fe/src/pages/TopicList/index.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less index 0fa44077..6ab58a80 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less @@ -123,8 +123,9 @@ .statistics-info { text-align: right; span { + font-size: 14px; margin-left: 10px; - color: #adb5bc; + color: #979A9A; } } } From 39d2fe6195add0f4fcb475abe7374b46e500b91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Tue, 1 Nov 2022 10:42:36 +0800 Subject: [PATCH 005/150] =?UTF-8?q?styles:=E6=B6=88=E6=81=AF=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E6=B5=8B=E8=AF=95=E5=BC=B9=E7=AA=97=E4=B8=8B=E6=96=B9?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E5=AD=97=E4=BD=93=E5=8A=A0=E7=B2=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/layout-clusters-fe/src/pages/TopicList/index.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less index 6ab58a80..21510dbc 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less @@ -124,8 +124,9 @@ text-align: right; span { font-size: 14px; + font-weight: bold; margin-left: 10px; - color: #979A9A; + color: #495057; } } } From ed0efd6bd2c058de6caa1ae91e20a4f69c670fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Thu, 3 Nov 2022 11:49:20 +0800 Subject: [PATCH 006/150] =?UTF-8?q?styles:=E5=AD=97=E4=BD=93=E9=A2=9C?= =?UTF-8?q?=E8=89=B2#adb5bc=E5=8F=98=E6=9B=B4=E4=B8=BA#74788D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/pages/TopicList/index.less | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less index 21510dbc..41a407b6 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less @@ -123,10 +123,8 @@ .statistics-info { text-align: right; span { - font-size: 14px; - font-weight: bold; margin-left: 10px; - color: #495057; + color: #74788D; } } } From b51ffb81a3ad4efebf00154f769b03087c0ab582 Mon Sep 17 00:00:00 2001 From: Richard <49510754+f1558@users.noreply.github.com> Date: Fri, 4 Nov 2022 23:31:41 +0800 Subject: [PATCH 007/150] [Bugfix] No thread-bound request found. (#743) --- .../core/service/oprecord/impl/OpLogWrapServiceImpl.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/oprecord/impl/OpLogWrapServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/oprecord/impl/OpLogWrapServiceImpl.java index 3918da19..ffcc2eba 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/oprecord/impl/OpLogWrapServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/oprecord/impl/OpLogWrapServiceImpl.java @@ -7,6 +7,9 @@ import com.didiglobal.logi.security.service.OplogService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; @Service public class OpLogWrapServiceImpl implements OpLogWrapService { @@ -18,6 +21,12 @@ public class OpLogWrapServiceImpl implements OpLogWrapService { @Override public Integer saveOplogAndIgnoreException(OplogDTO oplogDTO) { try { + // fix request that cannot find thread binding (issue#743) + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (null == servletRequestAttributes) { + servletRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest()); + RequestContextHolder.setRequestAttributes(servletRequestAttributes, true); + } return oplogService.saveOplog(oplogDTO); } catch (Exception e) { log.error("method=saveOplogAndIgnoreException||oplogDTO={}||errMsg=exception.", oplogDTO, e); From dd5869e2469e71938514085fc1d0b62008918bb1 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 4 Nov 2022 17:11:48 +0800 Subject: [PATCH 008/150] =?UTF-8?q?[Optimize]=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=EF=BC=8C=E4=B8=BAConnect?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=81=9A=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/ClusterZookeepersManagerImpl.java | 2 +- .../cluster/impl/MultiClusterPhyManagerImpl.java | 2 +- .../km/biz/group/impl/GroupManagerImpl.java | 2 +- .../biz/reassign/impl/ReassignManagerImpl.java | 2 +- .../km/biz/topic/impl/TopicStateManagerImpl.java | 2 +- .../version/impl/VersionControlManagerImpl.java | 8 ++++---- .../{ => kafka}/BrokerMetricCollector.java | 3 ++- .../{ => kafka}/ClusterMetricCollector.java | 3 ++- .../metric/{ => kafka}/GroupMetricCollector.java | 3 ++- .../{ => kafka}/PartitionMetricCollector.java | 3 ++- .../{ => kafka}/ReplicaMetricCollector.java | 3 ++- .../metric/{ => kafka}/TopicMetricCollector.java | 3 ++- .../{ => kafka}/ZookeeperMetricCollector.java | 3 ++- .../broker/impl/BrokerMetricServiceImpl.java | 4 ++-- .../cluster/impl/ClusterMetricServiceImpl.java | 3 ++- .../group/impl/GroupMetricServiceImpl.java | 2 +- .../checker/broker/HealthCheckBrokerService.java | 2 +- .../cluster/HealthCheckClusterService.java | 2 +- .../checker/group/HealthCheckGroupService.java | 2 +- .../checker/topic/HealthCheckTopicService.java | 2 +- .../zookeeper/HealthCheckZookeeperService.java | 2 +- .../state/impl/HealthStateServiceImpl.java | 16 ++++++++-------- .../job/handler/AbstractReassignJobHandler.java | 2 +- .../km/core/service/job/impl/JobServiceImpl.java | 2 +- .../impl/PartitionMetricServiceImpl.java | 2 +- .../reassign/impl/ReassignJobServiceImpl.java | 2 +- .../impl/ReassignStrategyServiceImpl.java | 2 +- .../replica/impl/ReplicaMetricServiceImpl.java | 4 ++-- .../topic/impl/TopicMetricServiceImpl.java | 2 +- .../{ => kafka}/BrokerMetricVersionItems.java | 3 ++- .../{ => kafka}/ClusterMetricVersionItems.java | 3 ++- .../{ => kafka}/GroupMetricVersionItems.java | 3 ++- .../{ => kafka}/PartitionMetricVersionItems.java | 3 ++- .../{ => kafka}/ReplicaMetricVersionItems.java | 3 ++- .../{ => kafka}/TopicMetricVersionItems.java | 3 ++- .../{ => kafka}/ZookeeperMetricVersionItems.java | 3 ++- .../impl/ZookeeperMetricServiceImpl.java | 2 +- .../task/metrics/BrokerMetricCollectorTask.java | 2 +- .../task/metrics/ClusterMetricCollectorTask.java | 2 +- .../task/metrics/GroupMetricCollectorTask.java | 2 +- .../metrics/PartitionMetricCollectorTask.java | 2 +- .../task/metrics/ReplicaMetricCollectorTask.java | 2 +- .../task/metrics/TopicMetricCollectorTask.java | 2 +- .../metrics/ZookeeperMetricCollectorTask.java | 2 +- 44 files changed, 71 insertions(+), 56 deletions(-) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/BrokerMetricCollector.java (97%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/ClusterMetricCollector.java (96%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/GroupMetricCollector.java (97%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/PartitionMetricCollector.java (97%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/ReplicaMetricCollector.java (97%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/TopicMetricCollector.java (97%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/{ => kafka}/ZookeeperMetricCollector.java (97%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/BrokerMetricVersionItems.java (99%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/ClusterMetricVersionItems.java (99%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/GroupMetricVersionItems.java (96%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/PartitionMetricVersionItems.java (96%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/ReplicaMetricVersionItems.java (97%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/TopicMetricVersionItems.java (98%) rename km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/{ => kafka}/ZookeeperMetricVersionItems.java (98%) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java index 7783b40b..6441087e 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java @@ -19,7 +19,7 @@ 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.core.service.cluster.ClusterPhyService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZnodeService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperMetricService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService; diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java index 9d1f7e33..6d716983 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java @@ -25,7 +25,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; 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.kafkacontroller.KafkaControllerService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ClusterMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java index 77095cc0..a77b7c39 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java @@ -38,7 +38,7 @@ 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.partition.PartitionService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems; import com.xiaojukeji.know.streaming.km.persistence.es.dao.GroupMetricESDAO; import org.apache.kafka.clients.admin.ConsumerGroupDescription; import org.apache.kafka.clients.admin.MemberDescription; diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/reassign/impl/ReassignManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/reassign/impl/ReassignManagerImpl.java index 557974ee..4909ee60 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/reassign/impl/ReassignManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/reassign/impl/ReassignManagerImpl.java @@ -22,7 +22,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.reassign.ReassignService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java index afc907da..4c4781da 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java @@ -43,7 +43,7 @@ import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicConfigService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.admin.OffsetSpec; diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java index 501e4822..3ce527a1 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java @@ -29,10 +29,10 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum.V_MAX; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ClusterMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; @Service public class VersionControlManagerImpl implements VersionControlManager { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/BrokerMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java similarity index 97% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/BrokerMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java index e60372a4..18f36192 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/BrokerMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java @@ -1,8 +1,9 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.alibaba.fastjson.JSON; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics; diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ClusterMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java similarity index 96% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ClusterMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java index aef5d263..19ed8f0d 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ClusterMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/GroupMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java similarity index 97% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/GroupMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java index 52685b63..e41af566 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/GroupMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java @@ -1,8 +1,9 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.alibaba.fastjson.JSON; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/PartitionMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java similarity index 97% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/PartitionMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java index 89363652..0b5debfa 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/PartitionMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ReplicaMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java similarity index 97% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ReplicaMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java index 3f9e0035..390196ae 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ReplicaMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java @@ -1,8 +1,9 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.alibaba.fastjson.JSON; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ReplicationMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/TopicMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java similarity index 97% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/TopicMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java index 5d278f4d..ab09c2b8 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/TopicMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ZookeeperMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java similarity index 97% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ZookeeperMetricCollector.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java index 37f86d4e..8e055b00 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/ZookeeperMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.metric; +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; 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.kafkacontroller.KafkaController; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java index 1a08c85c..2f358d8e 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java @@ -33,8 +33,8 @@ 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; import com.xiaojukeji.know.streaming.km.core.service.version.BaseMetricService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetricVersionItems; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ReplicaMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ReplicaMetricVersionItems; import com.xiaojukeji.know.streaming.km.persistence.es.dao.BrokerMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; import org.apache.kafka.clients.admin.LogDirDescription; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java index bdd652aa..8cbfc8b8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java @@ -69,7 +69,8 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics.initWithMetrics; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; /** * @author didi diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java index 6d38f2a3..5112d57f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java @@ -33,7 +33,7 @@ import java.util.*; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; /** * @author didi diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java index 5714c844..8e0792d9 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java @@ -17,7 +17,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.Tuple; 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.checker.AbstractHealthCheckService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java index b8550de1..2be267a2 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java @@ -13,7 +13,7 @@ import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimension import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterMetricService; import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ClusterMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java index cd78b368..522d76b8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java @@ -24,7 +24,7 @@ import javax.annotation.PostConstruct; import java.util.List; import java.util.stream.Collectors; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.GROUP_METRIC_STATE; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.GROUP_METRIC_STATE; @Data @Service diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java index 3f202d3b..5c37ee2c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java @@ -27,7 +27,7 @@ import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.TOPIC_METRIC_UNDER_REPLICA_PARTITIONS; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.TOPIC_METRIC_UNDER_REPLICA_PARTITIONS; @Service public class HealthCheckTopicService extends AbstractHealthCheckService { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java index 5d3e658d..b83a4ee4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java @@ -23,7 +23,7 @@ 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.version.metrics.kafka.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; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java index 7f41b0d8..8cb44fd4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java @@ -23,14 +23,14 @@ 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.BrokerMetricVersionItems.BROKER_METRIC_HEALTH_STATE; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ClusterMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.GROUP_METRIC_HEALTH_CHECK_TOTAL; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.TOPIC_METRIC_HEALTH_CHECK_TOTAL; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.BROKER_METRIC_HEALTH_STATE; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.GROUP_METRIC_HEALTH_CHECK_TOTAL; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.TOPIC_METRIC_HEALTH_CHECK_TOTAL; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems.*; @Service diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/handler/AbstractReassignJobHandler.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/handler/AbstractReassignJobHandler.java index 715874af..9b63492d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/handler/AbstractReassignJobHandler.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/handler/AbstractReassignJobHandler.java @@ -21,7 +21,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.core.service.job.JobHandler; import com.xiaojukeji.know.streaming.km.core.service.reassign.ReassignJobService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems; import org.springframework.beans.factory.annotation.Autowired; import java.util.*; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/impl/JobServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/impl/JobServiceImpl.java index 573a3cf5..0709e314 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/impl/JobServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/job/impl/JobServiceImpl.java @@ -46,7 +46,7 @@ import java.util.*; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; @Service public class JobServiceImpl implements JobService { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java index 9104b398..90646fe4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java @@ -34,7 +34,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.PartitionMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.PartitionMetricVersionItems.*; /** * @author didi diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java index 2f70b2d9..9bcd1d29 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java @@ -38,7 +38,7 @@ import com.xiaojukeji.know.streaming.km.core.service.reassign.ReassignService; import com.xiaojukeji.know.streaming.km.core.service.replica.ReplicaMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicConfigService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ReplicaMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ReplicaMetricVersionItems; import com.xiaojukeji.know.streaming.km.persistence.mysql.reassign.ReassignJobDAO; import com.xiaojukeji.know.streaming.km.persistence.mysql.reassign.ReassignSubJobDAO; import org.apache.kafka.common.TopicPartition; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignStrategyServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignStrategyServiceImpl.java index fb9736ce..bd09a49a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignStrategyServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignStrategyServiceImpl.java @@ -11,7 +11,7 @@ import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; import com.xiaojukeji.know.streaming.km.core.service.reassign.ReassignStrategyService; import com.xiaojukeji.know.streaming.km.core.service.replica.ReplicaMetricService; -import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ReplicaMetricVersionItems; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ReplicaMetricVersionItems; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java index 848c8601..c15914a6 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java @@ -33,8 +33,8 @@ import java.util.Map; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_REPLICATION; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ReplicaMetricVersionItems.REPLICATION_METRIC_LOG_END_OFFSET; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ReplicaMetricVersionItems.REPLICATION_METRIC_LOG_START_OFFSET; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ReplicaMetricVersionItems.REPLICATION_METRIC_LOG_END_OFFSET; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ReplicaMetricVersionItems.REPLICATION_METRIC_LOG_START_OFFSET; /** * @author didi diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java index 703bf59d..cf04f2e8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java @@ -52,7 +52,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.PartitionMetricVersionItems.PARTITION_METRIC_MESSAGES; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.PartitionMetricVersionItems.PARTITION_METRIC_MESSAGES; /** */ diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BrokerMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java similarity index 99% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BrokerMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java index be68ac06..0fc46d92 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BrokerMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ClusterMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java similarity index 99% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ClusterMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java index ea81da3a..2c7408a9 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ClusterMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java @@ -1,8 +1,9 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/GroupMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/GroupMetricVersionItems.java similarity index 96% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/GroupMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/GroupMetricVersionItems.java index 3ca7b4c6..c92b17a7 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/GroupMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/GroupMetricVersionItems.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMethodInfo; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/PartitionMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/PartitionMetricVersionItems.java similarity index 96% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/PartitionMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/PartitionMetricVersionItems.java index b330c38b..8fbb520a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/PartitionMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/PartitionMetricVersionItems.java @@ -1,6 +1,7 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ReplicaMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ReplicaMetricVersionItems.java similarity index 97% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ReplicaMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ReplicaMetricVersionItems.java index cd196cc3..f10a8422 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ReplicaMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ReplicaMetricVersionItems.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/TopicMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java similarity index 98% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/TopicMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java index 017435bc..86296a5d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/TopicMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ZookeeperMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ZookeeperMetricVersionItems.java similarity index 98% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ZookeeperMetricVersionItems.java rename to km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ZookeeperMetricVersionItems.java index a037053a..2ae3b470 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/ZookeeperMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ZookeeperMetricVersionItems.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java index ee26f3cf..a9be87f6 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java @@ -44,7 +44,7 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.VC_JMX_CONNECT_ERROR; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems.*; @Service diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java index 16334246..58707aa2 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java @@ -5,7 +5,7 @@ import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.BrokerMetricCollector; +import com.xiaojukeji.know.streaming.km.collector.metric.kafka.BrokerMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java index 4abf0372..6c489c72 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java @@ -5,7 +5,7 @@ import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.ClusterMetricCollector; +import com.xiaojukeji.know.streaming.km.collector.metric.kafka.ClusterMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java index 595930b8..7571236f 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java @@ -5,7 +5,7 @@ import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.GroupMetricCollector; +import com.xiaojukeji.know.streaming.km.collector.metric.kafka.GroupMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java index 21f9133b..0bc1dfbb 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java @@ -3,7 +3,7 @@ package com.xiaojukeji.know.streaming.km.task.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -import com.xiaojukeji.know.streaming.km.collector.metric.PartitionMetricCollector; +import com.xiaojukeji.know.streaming.km.collector.metric.kafka.PartitionMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java index 7e52c2f4..e92a027f 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java @@ -3,7 +3,7 @@ //import com.didiglobal.logi.job.annotation.Task; //import com.didiglobal.logi.job.common.TaskResult; //import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -//import com.xiaojukeji.know.streaming.km.collector.metric.ReplicaMetricCollector; +//import com.xiaojukeji.know.streaming.km.collector.metric.kafka.ReplicaMetricCollector; //import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; //import lombok.extern.slf4j.Slf4j; //import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java index 9f8a5de7..18888452 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java @@ -5,7 +5,7 @@ import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.TopicMetricCollector; +import com.xiaojukeji.know.streaming.km.collector.metric.kafka.TopicMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java index f533a30a..2796f564 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java @@ -5,7 +5,7 @@ import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.ZookeeperMetricCollector; +import com.xiaojukeji.know.streaming.km.collector.metric.kafka.ZookeeperMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; From c69c289bc44aa5b10ac9eed6791111b6d21a528e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Tue, 1 Nov 2022 19:17:44 +0800 Subject: [PATCH 009/150] =?UTF-8?q?styles:=E9=83=A8=E5=88=86icon=E5=9C=A8h?= =?UTF-8?q?over=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=9C=89=E8=83=8C=E6=99=AF=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/TypicalListCard/index.less | 7 ++++++ .../components/CardBar/BrokerHealthCheck.tsx | 4 ++-- .../src/components/CardBar/index.less | 10 ++++++++ .../ChartOperateBar/style/index.less | 5 ++-- .../layout-clusters-fe/src/index.less | 15 ++++++++---- .../src/pages/SingleClusterDetail/index.less | 23 ++++++++++++++----- 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less b/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less index 9a48d134..e9766691 100644 --- a/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less +++ b/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less @@ -32,6 +32,13 @@ color: #74788d; cursor: pointer; } + .refresh-icon{ + padding: 0 6px; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } + } .right .search-input { width: 248px; margin-right: 8px; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx index 31531c15..49a718c5 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx @@ -7,7 +7,7 @@ import { Tag, Tooltip, Utils } from 'knowdesign'; import api from '@src/api'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { HealthStateEnum } from '../HealthState'; - +import './index.less'; export default () => { const routeParams = useParams<{ clusterId: string; @@ -87,7 +87,7 @@ export default () => {
Similar Config - +
); diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index 6474a929..707fb0dc 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -177,3 +177,13 @@ border-radius: 6px; } } +.rebalance-tooltip-bg { + padding: 6px; + margin-left: -6px; + font-size: 14px; + cursor: pointer; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } +} \ No newline at end of file diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less index 70a7af11..e9e6a8b2 100644 --- a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less @@ -37,8 +37,7 @@ display: flex; justify-content: center; align-items: center; - width: 22px; - height: 22px; + padding: 6px; border-radius: 50%; cursor: pointer; .icon { @@ -46,7 +45,7 @@ color: #74788d; } &:hover { - background: #21252904; + background: rgba(33, 37, 41, 0.04); .refresh-icon { color: #495057; } diff --git a/km-console/packages/layout-clusters-fe/src/index.less b/km-console/packages/layout-clusters-fe/src/index.less index f7da736e..2d59a3fe 100644 --- a/km-console/packages/layout-clusters-fe/src/index.less +++ b/km-console/packages/layout-clusters-fe/src/index.less @@ -308,10 +308,17 @@ li { align-items: center; } &-left { - &-refresh{ - font-size: 20px; - color: #74788d; - cursor: pointer; + &-refresh { + font-size: 20px; + color: #74788d; + cursor: pointer; + padding: 0px 6px; + color: #74788d; + cursor: pointer; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } } } diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less index 4b098a72..90192e4b 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less @@ -35,14 +35,19 @@ } .edit-icon-box { position: relative; - width: 20px; + width: 24px; cursor: pointer; .edit-icon { - position: absolute; - bottom: 2px; - margin-left: 4px; - font-size: 16px; - color: #74788d; + position: absolute; + bottom: -2px; + padding: 3px; + margin-left: 4px; + font-size: 16px; + color: #74788d; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } } } } @@ -205,9 +210,15 @@ margin-bottom: 10px; .icon { + padding: 3px 3px 2px 3px; margin-left: 4px; font-size: 14px; cursor: pointer; + border-radius: 50%; + line-height: 12px; + &:hover { + background: rgba(33, 37, 41, 0.04); + } } } From 805a704d3453a3743f359f30b126c661009f55f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Wed, 2 Nov 2022 10:08:29 +0800 Subject: [PATCH 010/150] =?UTF-8?q?styles:=E9=83=A8=E5=88=86icon=E5=9C=A8h?= =?UTF-8?q?over=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=9C=89=E8=83=8C=E6=99=AF=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/TypicalListCard/index.less | 10 ++++++---- .../src/components/CardBar/index.less | 4 ++-- .../src/components/ChartOperateBar/style/index.less | 2 +- .../packages/layout-clusters-fe/src/index.less | 12 ++++++------ .../src/pages/SingleClusterDetail/index.less | 11 ++++++----- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less b/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less index e9766691..a4272ad7 100644 --- a/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less +++ b/km-console/packages/config-manager-fe/src/components/TypicalListCard/index.less @@ -33,10 +33,12 @@ cursor: pointer; } .refresh-icon{ - padding: 0 6px; - border-radius: 50%; - &:hover { - background: rgba(33, 37, 41, 0.04); + .icon{ + padding: 3px; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } } } .right .search-input { diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index 707fb0dc..c3101e00 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -178,8 +178,8 @@ } } .rebalance-tooltip-bg { - padding: 6px; - margin-left: -6px; + padding: 3px; + margin-left: -3px; font-size: 14px; cursor: pointer; border-radius: 50%; diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less index e9e6a8b2..7709e5ce 100644 --- a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/style/index.less @@ -37,7 +37,7 @@ display: flex; justify-content: center; align-items: center; - padding: 6px; + padding: 3px; border-radius: 50%; cursor: pointer; .icon { diff --git a/km-console/packages/layout-clusters-fe/src/index.less b/km-console/packages/layout-clusters-fe/src/index.less index 2d59a3fe..cba2f5d2 100644 --- a/km-console/packages/layout-clusters-fe/src/index.less +++ b/km-console/packages/layout-clusters-fe/src/index.less @@ -312,14 +312,14 @@ li { font-size: 20px; color: #74788d; cursor: pointer; - padding: 0px 6px; - color: #74788d; - cursor: pointer; - border-radius: 50%; - &:hover { + &-icon { + border-radius: 50%; + padding: 3px; + } + &-icon:hover { background: rgba(33, 37, 41, 0.04); } - } + } } &-right{ diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less index 90192e4b..7cb7ded4 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/index.less @@ -210,14 +210,15 @@ margin-bottom: 10px; .icon { - padding: 3px 3px 2px 3px; margin-left: 4px; font-size: 14px; cursor: pointer; - border-radius: 50%; - line-height: 12px; - &:hover { - background: rgba(33, 37, 41, 0.04); + .anticon { + padding: 3px; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } } } } From 6918f4babef895c039e875e9cfcf62829e0d7114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Wed, 2 Nov 2022 11:42:09 +0800 Subject: [PATCH 011/150] =?UTF-8?q?styles:job=E5=88=97=E8=A1=A8=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=88=97=E6=8C=89=E9=92=AE=E6=96=B0=E5=A2=9E?= =?UTF-8?q?hover=E8=83=8C=E6=99=AF=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/components/CardBar/index.less | 2 +- .../layout-clusters-fe/src/pages/Jobs/index.less | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index c3101e00..35db33a2 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -178,7 +178,7 @@ } } .rebalance-tooltip-bg { - padding: 3px; + padding: 3px 3px 2px 3px; margin-left: -3px; font-size: 14px; cursor: pointer; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less b/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less index 085d63cf..c13bee61 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less @@ -158,3 +158,12 @@ .dcloud-checkbox-table-serch{ padding-top: 0; } +.clustom-table-content { + .anticon { + padding: 3px; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } + } +} \ No newline at end of file From 70ff20a2b0e9086e6c8e9c9f0b45bbe52011387c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B5=B7=E5=A9=B7?= Date: Fri, 4 Nov 2022 18:17:18 +0800 Subject: [PATCH 012/150] =?UTF-8?q?styles:cardBar=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E5=9B=BE=E6=A0=87hover=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CardBar/BrokerHealthCheck.tsx | 3 +- .../src/components/CardBar/index.less | 31 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx index 49a718c5..c7ac61f1 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx @@ -7,7 +7,6 @@ import { Tag, Tooltip, Utils } from 'knowdesign'; import api from '@src/api'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { HealthStateEnum } from '../HealthState'; -import './index.less'; export default () => { const routeParams = useParams<{ clusterId: string; @@ -87,7 +86,7 @@ export default () => {
Similar Config - +
); diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index 35db33a2..e18f8530 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -63,12 +63,21 @@ margin-left: 12px; padding: 12px 20px; .card-bar-colunms-header { - font-size: 14px; - color: #74788d; - letter-spacing: 0; - text-align: justify; - line-height: 20px; - } + font-size: 14px; + color: #74788d; + letter-spacing: 0; + text-align: justify; + line-height: 20px; + .anticon-question-circle { + padding: 3px 3px 2px 3px; + margin-left: -3px; + font-size: 14px; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } + } + } .card-bar-colunms-body { font-size: 40px; color: #212529; @@ -177,13 +186,3 @@ border-radius: 6px; } } -.rebalance-tooltip-bg { - padding: 3px 3px 2px 3px; - margin-left: -3px; - font-size: 14px; - cursor: pointer; - border-radius: 50%; - &:hover { - background: rgba(33, 37, 41, 0.04); - } -} \ No newline at end of file From 3c8aaf528c909f93412a4538dea1dd76ad7cc39d Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 8 Nov 2022 10:27:08 +0800 Subject: [PATCH 013/150] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E5=9B=A0?= =?UTF-8?q?=E4=B8=BA=E6=8C=87=E6=A0=87=E7=BC=BA=E5=A4=B1=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=9A=84=E9=9B=86=E7=BE=A4=E6=95=B0=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98=20(#741)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/biz/cluster/impl/MultiClusterPhyManagerImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java index 6d716983..3c8458e9 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java @@ -176,7 +176,10 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager { // 获取所有的metrics List metricsList = new ArrayList<>(); for (ClusterPhyDashboardVO vo: voList) { - metricsList.add(clusterMetricService.getLatestMetricsFromCache(vo.getId())); + ClusterMetrics clusterMetrics = clusterMetricService.getLatestMetricsFromCache(vo.getId()); + clusterMetrics.getMetrics().putIfAbsent(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_STATE, (float) HealthStateEnum.UNKNOWN.getDimension()); + + metricsList.add(clusterMetrics); } // 范围搜索 From c1031a492a26454fbbd719969990963d51ec7118 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 8 Nov 2022 10:29:54 +0800 Subject: [PATCH 014/150] =?UTF-8?q?[Optimize]=E5=A2=9E=E5=8A=A0ES=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E5=88=A0=E9=99=A4=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/persistence/es/ESOpClient.java | 67 ++++++++++++++++++- .../persistence/es/dao/BaseMetricESDAO.java | 27 ++++++++ km-rest/src/main/resources/application.yml | 2 + 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java index c70a4df6..1efa01d6 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java @@ -11,6 +11,9 @@ import com.didiglobal.logi.elasticsearch.client.request.batch.ESBatchRequest; import com.didiglobal.logi.elasticsearch.client.request.query.query.ESQueryRequest; import com.didiglobal.logi.elasticsearch.client.response.batch.ESBatchResponse; import com.didiglobal.logi.elasticsearch.client.response.batch.IndexResultItemNode; +import com.didiglobal.logi.elasticsearch.client.response.indices.catindices.CatIndexResult; +import com.didiglobal.logi.elasticsearch.client.response.indices.catindices.ESIndicesCatIndicesResponse; +import com.didiglobal.logi.elasticsearch.client.response.indices.deleteindex.ESIndicesDeleteIndexResponse; import com.didiglobal.logi.elasticsearch.client.response.indices.gettemplate.ESIndicesGetTemplateResponse; import com.didiglobal.logi.elasticsearch.client.response.indices.putindex.ESIndicesPutIndexResponse; import com.didiglobal.logi.elasticsearch.client.response.indices.puttemplate.ESIndicesPutTemplateResponse; @@ -37,6 +40,7 @@ import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.stream.Collectors; @Component public class ESOpClient { @@ -77,6 +81,8 @@ public class ESOpClient { */ private LinkedBlockingQueue esClientPool; + private static final Integer ES_OPERATE_TIMEOUT = 30; + @PostConstruct public void init(){ esClientPool = new LinkedBlockingQueue<>( clientCnt ); @@ -380,7 +386,7 @@ public class ESOpClient { if (client != null) { try { ESIndicesPutIndexResponse response = client.admin().indices().preparePutIndex(indexName).execute() - .actionGet(30, TimeUnit.SECONDS); + .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); return response.getAcknowledged(); } catch (Exception e){ LOGGER.warn( "msg=create index fail||indexName={}", indexName, e); @@ -400,7 +406,7 @@ public class ESOpClient { // 获取es中原来index template的配置 ESIndicesGetTemplateResponse getTemplateResponse = - esClient.admin().indices().prepareGetTemplate( indexTemplateName ).execute().actionGet( 30, TimeUnit.SECONDS ); + esClient.admin().indices().prepareGetTemplate( indexTemplateName ).execute().actionGet( ES_OPERATE_TIMEOUT, TimeUnit.SECONDS ); TemplateConfig templateConfig = getTemplateResponse.getMultiTemplatesConfig().getSingleConfig(); @@ -433,7 +439,7 @@ public class ESOpClient { // 创建新的模板 ESIndicesPutTemplateResponse response = esClient.admin().indices().preparePutTemplate( indexTemplateName ) - .setTemplateConfig( config ).execute().actionGet( 30, TimeUnit.SECONDS ); + .setTemplateConfig( config ).execute().actionGet( ES_OPERATE_TIMEOUT, TimeUnit.SECONDS ); return response.getAcknowledged(); } catch (Exception e) { @@ -449,6 +455,61 @@ public class ESOpClient { return false; } + /** + * 根据索引模板获取所有的索引 + * @param indexName + * @return + */ + public List listIndexByName(String indexName){ + ESClient esClient = null; + + try { + esClient = this.getESClientFromPool(); + + ESIndicesCatIndicesResponse response = esClient.admin().indices().prepareCatIndices(indexName + "*").execute() + .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); + + if(null != response){ + return response.getCatIndexResults().stream().map(CatIndexResult::getIndex).collect(Collectors.toList()); + } + } catch (Exception e) { + LOGGER.warn( "method=listIndexByTemplate||indexName={}||msg=exception!", + indexName, e); + } finally { + if (esClient != null) { + this.returnESClientToPool(esClient); + } + } + + return new ArrayList<>(); + } + + /** + * 删除索引 + * @param indexRealName + * @return + */ + public boolean delIndexByName(String indexRealName){ + ESClient esClient = null; + + try { + esClient = this.getESClientFromPool(); + + ESIndicesDeleteIndexResponse response = esClient.admin().indices().prepareDeleteIndex(indexRealName).execute() + .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); + return response.getAcknowledged(); + } catch (Exception e) { + LOGGER.warn( "method=delIndexByName||indexRealName={}||msg=exception!", + indexRealName, e); + } finally { + if (esClient != null) { + this.returnESClientToPool(esClient); + } + } + + return false; + } + /**************************************************** private method ****************************************************/ /** diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java index faeb64cb..39c9ef44 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java @@ -10,10 +10,12 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.BaseESPO; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BaseMetricESPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; +import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import com.xiaojukeji.know.streaming.km.common.utils.IndexNameUtils; import com.xiaojukeji.know.streaming.km.persistence.es.BaseESDAO; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.util.CollectionUtils; @@ -42,6 +44,12 @@ public class BaseMetricESDAO extends BaseESDAO { */ private static Map ariusStatsEsDaoMap = Maps.newConcurrentMap(); + /** + * es 地址 + */ + @Value("${es.index.expire:60}") + private int indexExpireDays; + /** * 检查 es 索引是否存在,不存在则创建索引 */ @@ -62,6 +70,25 @@ public class BaseMetricESDAO extends BaseESDAO { } } + @Scheduled(cron = "0 30/45 * * * ?") + public void delExpireIndex(){ + List indexList = esOpClient.listIndexByName(indexName); + if(CollectionUtils.isEmpty(indexList)){return;} + + indexList.sort((o1, o2) -> -o1.compareTo(o2)); + + int size = indexList.size(); + if(size > indexExpireDays){ + if(!EnvUtil.isOnline()){ + LOGGER.info("method=delExpireIndex||indexExpireDays={}||delIndex={}", + indexExpireDays, indexList.subList(indexExpireDays, size)); + } + + indexList.subList(indexExpireDays, size).stream().forEach( + s -> esOpClient.delIndexByName(s)); + } + } + public static BaseMetricESDAO getByStatsType(String statsType) { return ariusStatsEsDaoMap.get(statsType); } diff --git a/km-rest/src/main/resources/application.yml b/km-rest/src/main/resources/application.yml index 4a4b7f1c..c9cd3c5e 100644 --- a/km-rest/src/main/resources/application.yml +++ b/km-rest/src/main/resources/application.yml @@ -88,6 +88,8 @@ es: client-cnt: 10 # 创建的ES客户端数 io-thread-cnt: 2 max-retry-cnt: 5 + index: + expire: 60 # 索引过期天数,60表示超过60天的索引会被KS过期删除 # 普罗米修斯指标导出相关配置 management: From da0a97cabf8641cdffe25be6a063daaae8b207a2 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 8 Nov 2022 10:33:30 +0800 Subject: [PATCH 015/150] =?UTF-8?q?[Optimize]=20=E8=B0=83=E6=95=B4Task?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E4=B8=BAConnector=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=81=9A=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/{EntifyIdInterface.java => EntityIdInterface.java} | 2 +- .../streaming/km/common/bean/entity/cluster/ClusterPhy.java | 4 ++-- .../know/streaming/km/task/AbstractDispatchTask.java | 4 ++-- .../km/task/{ => kafka}/AbstractAsyncCommonDispatchTask.java | 2 +- .../km/task/{ => kafka}/AbstractClusterPhyDispatchTask.java | 3 ++- .../km/task/{ => kafka}/client/CheckJmxClientTask.java | 2 +- .../km/task/{ => kafka}/health/AbstractHealthCheckTask.java | 4 ++-- .../km/task/{ => kafka}/health/BrokerHealthCheckTask.java | 2 +- .../km/task/{ => kafka}/health/ClusterHealthCheckTask.java | 2 +- .../km/task/{ => kafka}/health/GroupHealthCheckTask.java | 2 +- .../km/task/{ => kafka}/health/TopicHealthCheckTask.java | 2 +- .../km/task/{ => kafka}/health/ZookeeperHealthCheckTask.java | 2 +- .../km/task/{ => kafka}/job/CommunityReassignJobTask.java | 4 ++-- .../know/streaming/km/task/{ => kafka}/job/KMJobTask.java | 4 ++-- .../metadata/AbstractAsyncMetadataDispatchTask.java | 4 ++-- .../task/{ => kafka}/metadata/SyncBrokerConfigDiffTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncBrokerTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncControllerTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncKafkaAclTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncKafkaGroupTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncKafkaUserTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncPartitionTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncTopicConfigTask.java | 2 +- .../streaming/km/task/{ => kafka}/metadata/SyncTopicTask.java | 2 +- .../km/task/{ => kafka}/metadata/SyncZookeeperTask.java | 2 +- .../{ => kafka}/metrics/AbstractAsyncMetricsDispatchTask.java | 4 ++-- .../task/{ => kafka}/metrics/BrokerMetricCollectorTask.java | 2 +- .../task/{ => kafka}/metrics/ClusterMetricCollectorTask.java | 2 +- .../km/task/{ => kafka}/metrics/GroupMetricCollectorTask.java | 2 +- .../{ => kafka}/metrics/PartitionMetricCollectorTask.java | 2 +- .../task/{ => kafka}/metrics/ReplicaMetricCollectorTask.java | 2 +- .../km/task/{ => kafka}/metrics/TopicMetricCollectorTask.java | 2 +- .../{ => kafka}/metrics/ZookeeperMetricCollectorTask.java | 2 +- .../km/task/service/listener/TaskClusterAddedListener.java | 4 ++-- 34 files changed, 43 insertions(+), 42 deletions(-) rename km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/{EntifyIdInterface.java => EntityIdInterface.java} (80%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/AbstractAsyncCommonDispatchTask.java (97%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/AbstractClusterPhyDispatchTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/client/CheckJmxClientTask.java (97%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/health/AbstractHealthCheckTask.java (97%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/health/BrokerHealthCheckTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/health/ClusterHealthCheckTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/health/GroupHealthCheckTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/health/TopicHealthCheckTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/health/ZookeeperHealthCheckTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/job/CommunityReassignJobTask.java (92%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/job/KMJobTask.java (86%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/AbstractAsyncMetadataDispatchTask.java (93%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncBrokerConfigDiffTask.java (98%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncBrokerTask.java (96%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncControllerTask.java (96%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncKafkaAclTask.java (97%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncKafkaGroupTask.java (98%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncKafkaUserTask.java (96%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncPartitionTask.java (97%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncTopicConfigTask.java (98%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncTopicTask.java (96%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metadata/SyncZookeeperTask.java (96%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/AbstractAsyncMetricsDispatchTask.java (93%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/BrokerMetricCollectorTask.java (95%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/ClusterMetricCollectorTask.java (95%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/GroupMetricCollectorTask.java (95%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/PartitionMetricCollectorTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/ReplicaMetricCollectorTask.java (94%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/TopicMetricCollectorTask.java (95%) rename km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/{ => kafka}/metrics/ZookeeperMetricCollectorTask.java (95%) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/EntifyIdInterface.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/EntityIdInterface.java similarity index 80% rename from km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/EntifyIdInterface.java rename to km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/EntityIdInterface.java index c6b42615..2f40f39c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/EntifyIdInterface.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/EntityIdInterface.java @@ -3,7 +3,7 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity; /** * @author didi */ -public interface EntifyIdInterface { +public interface EntityIdInterface { /** * 获取id * @return diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhy.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhy.java index 823ec67d..a534a015 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhy.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhy.java @@ -1,6 +1,6 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.cluster; -import com.xiaojukeji.know.streaming.km.common.bean.entity.EntifyIdInterface; +import com.xiaojukeji.know.streaming.km.common.bean.entity.EntityIdInterface; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -10,7 +10,7 @@ import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor -public class ClusterPhy implements Comparable, EntifyIdInterface { +public class ClusterPhy implements Comparable, EntityIdInterface { /** * 主键 */ diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java index 6fce1bb3..fc211c14 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java @@ -7,7 +7,7 @@ import com.didiglobal.logi.job.core.job.Job; import com.didiglobal.logi.job.core.job.JobContext; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.common.bean.entity.EntifyIdInterface; +import com.xiaojukeji.know.streaming.km.common.bean.entity.EntityIdInterface; import com.xiaojukeji.know.streaming.km.common.exception.AdminTaskCodeException; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; @@ -16,7 +16,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public abstract class AbstractDispatchTask implements Job { +public abstract class AbstractDispatchTask implements Job { private static final ILog LOGGER = LogFactory.getLog(AbstractDispatchTask.class); /** diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractAsyncCommonDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java similarity index 97% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractAsyncCommonDispatchTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java index 0f8f5797..5a41aadc 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractAsyncCommonDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task; +package com.xiaojukeji.know.streaming.km.task.kafka; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.log.ILog; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractClusterPhyDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractClusterPhyDispatchTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractClusterPhyDispatchTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractClusterPhyDispatchTask.java index 9281be8c..a9b70748 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractClusterPhyDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractClusterPhyDispatchTask.java @@ -1,10 +1,11 @@ -package com.xiaojukeji.know.streaming.km.task; +package com.xiaojukeji.know.streaming.km.task.kafka; import com.didiglobal.logi.job.common.TaskResult; 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.core.service.cluster.ClusterPhyService; +import com.xiaojukeji.know.streaming.km.task.AbstractDispatchTask; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/client/CheckJmxClientTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java similarity index 97% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/client/CheckJmxClientTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java index 4e5038a6..2e67fcaa 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/client/CheckJmxClientTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.client; +package com.xiaojukeji.know.streaming.km.task.kafka.client; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/AbstractHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java similarity index 97% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/AbstractHealthCheckTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java index 711a4ef6..3c9fdf23 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/AbstractHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.health; +package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.log.ILog; @@ -12,7 +12,7 @@ import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimension import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; -import com.xiaojukeji.know.streaming.km.task.metrics.AbstractAsyncMetricsDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.metrics.AbstractAsyncMetricsDispatchTask; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/BrokerHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/BrokerHealthCheckTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/BrokerHealthCheckTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/BrokerHealthCheckTask.java index ef02be8e..c8bdebaa 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/BrokerHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/BrokerHealthCheckTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.health; +package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/ClusterHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/ClusterHealthCheckTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/ClusterHealthCheckTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/ClusterHealthCheckTask.java index 43c16cb8..0b5a2e18 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/ClusterHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/ClusterHealthCheckTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.health; +package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/GroupHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/GroupHealthCheckTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/GroupHealthCheckTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/GroupHealthCheckTask.java index d24f981d..5136d4dd 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/GroupHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/GroupHealthCheckTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.health; +package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/TopicHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/TopicHealthCheckTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java index 25a1e531..c3226feb 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/TopicHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.health; +package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/ZookeeperHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/ZookeeperHealthCheckTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/ZookeeperHealthCheckTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/ZookeeperHealthCheckTask.java index 89a1c4f3..3eaea409 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/health/ZookeeperHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/ZookeeperHealthCheckTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.health; +package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/job/CommunityReassignJobTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java similarity index 92% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/job/CommunityReassignJobTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java index e655e57d..00dbd301 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/job/CommunityReassignJobTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.job; +package com.xiaojukeji.know.streaming.km.task.kafka.job; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; @@ -8,7 +8,7 @@ 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.result.Result; import com.xiaojukeji.know.streaming.km.core.service.reassign.ReassignJobService; -import com.xiaojukeji.know.streaming.km.task.AbstractAsyncCommonDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.AbstractAsyncCommonDispatchTask; import org.springframework.beans.factory.annotation.Autowired; @Task(name = "CommunityReassignJobTask", diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/job/KMJobTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/KMJobTask.java similarity index 86% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/job/KMJobTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/KMJobTask.java index debb020c..9cf5e089 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/job/KMJobTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/KMJobTask.java @@ -1,11 +1,11 @@ -package com.xiaojukeji.know.streaming.km.task.job; +package com.xiaojukeji.know.streaming.km.task.kafka.job; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.core.service.job.JobService; -import com.xiaojukeji.know.streaming.km.task.AbstractAsyncCommonDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.AbstractAsyncCommonDispatchTask; import org.springframework.beans.factory.annotation.Autowired; @Task(name = "kmJobTask", diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/AbstractAsyncMetadataDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java similarity index 93% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/AbstractAsyncMetadataDispatchTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java index 668208c7..c2b8516d 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/AbstractAsyncMetadataDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java @@ -1,10 +1,10 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.common.TaskResult; 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.task.AbstractClusterPhyDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.AbstractClusterPhyDispatchTask; import com.xiaojukeji.know.streaming.km.task.service.TaskThreadPoolService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncBrokerConfigDiffTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncBrokerConfigDiffTask.java similarity index 98% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncBrokerConfigDiffTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncBrokerConfigDiffTask.java index 04838b0f..42945c05 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncBrokerConfigDiffTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncBrokerConfigDiffTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncBrokerTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncBrokerTask.java similarity index 96% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncBrokerTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncBrokerTask.java index a4d1dc99..c64b776f 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncBrokerTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncBrokerTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncControllerTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java similarity index 96% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncControllerTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java index 971ef852..ea0a7696 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncControllerTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaAclTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaAclTask.java similarity index 97% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaAclTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaAclTask.java index 8b87d36e..0dd5b8f7 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaAclTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaAclTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaGroupTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java similarity index 98% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaGroupTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java index cbec5bd2..b75ab3d9 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaGroupTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaUserTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaUserTask.java similarity index 96% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaUserTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaUserTask.java index 2e2fd63e..d3651abf 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncKafkaUserTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaUserTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncPartitionTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java similarity index 97% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncPartitionTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java index e4b07ec3..c0d50e09 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncPartitionTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncTopicConfigTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java similarity index 98% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncTopicConfigTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java index 69b71d06..094f288c 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncTopicConfigTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncTopicTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicTask.java similarity index 96% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncTopicTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicTask.java index 4ed42c64..14b15a44 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncTopicTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncZookeeperTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncZookeeperTask.java similarity index 96% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncZookeeperTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncZookeeperTask.java index 5af37be2..e87f2bf1 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metadata/SyncZookeeperTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncZookeeperTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metadata; +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/AbstractAsyncMetricsDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java similarity index 93% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/AbstractAsyncMetricsDispatchTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java index 705c2301..1907e279 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/AbstractAsyncMetricsDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java @@ -1,10 +1,10 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.common.TaskResult; 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.task.AbstractClusterPhyDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.AbstractClusterPhyDispatchTask; import com.xiaojukeji.know.streaming.km.task.service.TaskThreadPoolService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java similarity index 95% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java index 58707aa2..09c004ad 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/BrokerMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java similarity index 95% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java index 6c489c72..f32f0588 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ClusterMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java similarity index 95% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java index 7571236f..3018c211 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/GroupMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/PartitionMetricCollectorTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/PartitionMetricCollectorTask.java index 0bc1dfbb..2a3c0a53 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/PartitionMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/PartitionMetricCollectorTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java similarity index 94% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java index e92a027f..80cc2644 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ReplicaMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java @@ -1,4 +1,4 @@ -//package com.xiaojukeji.know.streaming.km.task.metrics; +//package com.xiaojukeji.know.streaming.km.task.kafka.metrics; // //import com.didiglobal.logi.job.annotation.Task; //import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java similarity index 95% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java index 18888452..3c1d023c 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/TopicMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java similarity index 95% rename from km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java rename to km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java index 2796f564..59aa86da 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/metrics/ZookeeperMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.task.metrics; +package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java index b0886754..d5512843 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java @@ -8,8 +8,8 @@ import com.xiaojukeji.know.streaming.km.common.component.SpringTool; import com.xiaojukeji.know.streaming.km.common.utils.BackoffUtils; import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil; import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; -import com.xiaojukeji.know.streaming.km.task.metadata.AbstractAsyncMetadataDispatchTask; -import com.xiaojukeji.know.streaming.km.task.metrics.AbstractAsyncMetricsDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.metadata.AbstractAsyncMetadataDispatchTask; +import com.xiaojukeji.know.streaming.km.task.kafka.metrics.AbstractAsyncMetricsDispatchTask; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Service; From e456be91ef0b69e610a038ad1381710b46aee182 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 10 Nov 2022 16:04:40 +0800 Subject: [PATCH 016/150] =?UTF-8?q?[Bugfix]=E9=9B=86=E7=BE=A4JMX=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=91=E7=94=9F=E5=8F=98=E6=9B=B4=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E8=BF=9B=E8=A1=8CJMX=E7=9A=84=E9=87=8D=E6=96=B0=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../streaming/km/persistence/kafka/KafkaJMXClient.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java index 39ae1ebe..1ee0adcb 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java @@ -45,10 +45,6 @@ public class KafkaJMXClient extends AbstractClusterLoadedChangedHandler { public JmxConnectorWrap getClient(Long clusterPhyId, Integer brokerId) { Map jmxMap = JMX_MAP.getOrDefault(clusterPhyId, new ConcurrentHashMap<>()); - if (jmxMap == null) { - // 集群不存在, 直接返回null - return null; - } JmxConnectorWrap jmxConnectorWrap = jmxMap.get(brokerId); if (jmxConnectorWrap != null) { @@ -107,7 +103,8 @@ public class KafkaJMXClient extends AbstractClusterLoadedChangedHandler { protected void modify(ClusterPhy newClusterPhy, ClusterPhy oldClusterPhy) { if (newClusterPhy.getClientProperties().equals(oldClusterPhy.getClientProperties()) && newClusterPhy.getZookeeper().equals(oldClusterPhy.getZookeeper()) - && newClusterPhy.getBootstrapServers().equals(oldClusterPhy.getBootstrapServers())) { + && newClusterPhy.getBootstrapServers().equals(oldClusterPhy.getBootstrapServers()) + && newClusterPhy.getJmxProperties().equals(oldClusterPhy.getJmxProperties())) { // 集群信息虽然变化,但是相关没有变化,则直接返回 return; } From 7661826ea5e2a28152a605b1d3826784ecddc1fa Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 10 Nov 2022 16:24:39 +0800 Subject: [PATCH 017/150] =?UTF-8?q?[Optimize]=E5=81=A5=E5=BA=B7=E5=B7=A1?= =?UTF-8?q?=E6=A3=80=E5=A2=9E=E5=8A=A0ClusterParam,=20=E4=BB=8E=E8=80=8C?= =?UTF-8?q?=E6=8B=86=E5=88=86Kafka=E5=92=8CConnect=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=B7=A1=E6=A3=80=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/entity/param/cluster/ClusterParam.java | 10 ++++++++++ .../entity/param/cluster/ClusterPhyParam.java | 2 +- .../checker/AbstractHealthCheckService.java | 15 ++++++++------- .../checker/broker/HealthCheckBrokerService.java | 9 +++++---- .../cluster/HealthCheckClusterService.java | 7 ++++--- .../checker/group/HealthCheckGroupService.java | 5 +++-- .../checker/topic/HealthCheckTopicService.java | 9 +++++---- .../zookeeper/HealthCheckZookeeperService.java | 15 ++++++++------- .../kafka/health/AbstractHealthCheckTask.java | 11 ++++++----- 9 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterParam.java diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterParam.java new file mode 100644 index 00000000..95269065 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterParam.java @@ -0,0 +1,10 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; + +/** + * @author wyc + * @date 2022/11/9 + */ +public class ClusterParam extends VersionItemParam { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java index d55ceab5..9b01fa84 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java @@ -8,6 +8,6 @@ import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor -public class ClusterPhyParam extends VersionItemParam { +public class ClusterPhyParam extends ClusterParam { protected Long clusterPhyId; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java index 4b3e011c..b25a7b8c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java @@ -4,6 +4,7 @@ import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; @@ -21,15 +22,15 @@ public abstract class AbstractHealthCheckService { protected static final Map< String, - Function, HealthCheckResult> + Function, HealthCheckResult> > functionMap = new ConcurrentHashMap<>(); - public abstract List getResList(Long clusterPhyId); + public abstract List getResList(Long clusterPhyId); public abstract HealthCheckDimensionEnum getHealthCheckDimensionEnum(); - public HealthCheckResult checkAndGetResult(ClusterPhyParam clusterPhyParam, BaseClusterHealthConfig clusterHealthConfig) { - if (ValidateUtils.anyNull(clusterPhyParam.getClusterPhyId(), clusterPhyParam, clusterHealthConfig)) { + public HealthCheckResult checkAndGetResult(ClusterParam clusterParam, BaseClusterHealthConfig clusterHealthConfig) { + if (ValidateUtils.anyNull( clusterParam, clusterHealthConfig)) { return null; } @@ -39,16 +40,16 @@ public abstract class AbstractHealthCheckService { return null; } - Function, HealthCheckResult> function = functionMap.get(clusterHealthConfig.getCheckNameEnum().getConfigName()); + Function, HealthCheckResult> function = functionMap.get(clusterHealthConfig.getCheckNameEnum().getConfigName()); if (function == null) { return null; } try { - return function.apply(new Tuple<>(clusterPhyParam, clusterHealthConfig)); + return function.apply(new Tuple<>(clusterParam, clusterHealthConfig)); } catch (Exception e) { log.error("method=checkAndGetResult||clusterPhyParam={}||clusterHealthConfig={}||errMsg=exception!", - clusterPhyParam, clusterHealthConfig, e); + clusterParam, clusterHealthConfig, e); } return null; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java index 8e0792d9..c9b173af 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java @@ -8,6 +8,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.He import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.broker.BrokerParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.constant.Constant; @@ -45,8 +46,8 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { } @Override - public List getResList(Long clusterPhyId) { - List paramList = new ArrayList<>(); + public List getResList(Long clusterPhyId) { + List paramList = new ArrayList<>(); for (Broker broker: brokerService.listAliveBrokersFromDB(clusterPhyId)) { paramList.add(new BrokerParam(clusterPhyId, broker.getBrokerId())); } @@ -61,7 +62,7 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { /** * Broker网络处理线程平均值过低 */ - private HealthCheckResult checkBrokerNetworkProcessorAvgIdleTooLow(Tuple paramTuple) { + private HealthCheckResult checkBrokerNetworkProcessorAvgIdleTooLow(Tuple paramTuple) { BrokerParam param = (BrokerParam) paramTuple.getV1(); HealthCompareValueConfig singleConfig = (HealthCompareValueConfig) paramTuple.getV2(); @@ -96,7 +97,7 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { /** * Broker请求队列满 */ - private HealthCheckResult checkBrokerRequestQueueFull(Tuple paramTuple) { + private HealthCheckResult checkBrokerRequestQueueFull(Tuple paramTuple) { BrokerParam param = (BrokerParam) paramTuple.getV1(); HealthCompareValueConfig singleConfig = (HealthCompareValueConfig) paramTuple.getV2(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java index 2be267a2..a8201f45 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java @@ -6,6 +6,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.Ba 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.ClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum; @@ -34,7 +35,7 @@ public class HealthCheckClusterService extends AbstractHealthCheckService { } @Override - public List getResList(Long clusterPhyId) { + public List getResList(Long clusterPhyId) { return Arrays.asList(new ClusterPhyParam(clusterPhyId)); } @@ -46,8 +47,8 @@ public class HealthCheckClusterService extends AbstractHealthCheckService { /** * 检查NoController */ - private HealthCheckResult checkClusterNoController(Tuple singleConfigSimpleTuple) { - ClusterPhyParam param = singleConfigSimpleTuple.getV1(); + private HealthCheckResult checkClusterNoController(Tuple singleConfigSimpleTuple) { + ClusterPhyParam param =(ClusterPhyParam) singleConfigSimpleTuple.getV1(); HealthCompareValueConfig valueConfig = (HealthCompareValueConfig) singleConfigSimpleTuple.getV2(); Result clusterMetricsResult = clusterMetricService.getLatestMetricsFromES(param.getClusterPhyId(), Arrays.asList(ClusterMetricVersionItems.CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT)); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java index 522d76b8..8cc40c20 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java @@ -5,6 +5,7 @@ import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthDetectedInLatestMinutesConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.group.GroupParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -43,7 +44,7 @@ public class HealthCheckGroupService extends AbstractHealthCheckService { } @Override - public List getResList(Long clusterPhyId) { + public List getResList(Long clusterPhyId) { return groupService.getGroupsFromDB(clusterPhyId).stream().map(elem -> new GroupParam(clusterPhyId, elem)).collect(Collectors.toList()); } @@ -55,7 +56,7 @@ public class HealthCheckGroupService extends AbstractHealthCheckService { /** * 检查Group re-balance太频繁 */ - private HealthCheckResult checkReBalanceTooFrequently(Tuple paramTuple) { + private HealthCheckResult checkReBalanceTooFrequently(Tuple paramTuple) { GroupParam param = (GroupParam) paramTuple.getV1(); HealthDetectedInLatestMinutesConfig singleConfig = (HealthDetectedInLatestMinutesConfig) paramTuple.getV2(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java index 5c37ee2c..613d2902 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java @@ -6,6 +6,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.Ba 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.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; @@ -49,8 +50,8 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { } @Override - public List getResList(Long clusterPhyId) { - List paramList = new ArrayList<>(); + public List getResList(Long clusterPhyId) { + List paramList = new ArrayList<>(); for (Topic topic: topicService.listTopicsFromDB(clusterPhyId)) { paramList.add(new TopicParam(clusterPhyId, topic.getTopicName())); } @@ -65,7 +66,7 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { /** * 检查Topic长期未同步 */ - private HealthCheckResult checkTopicUnderReplicatedPartition(Tuple paramTuple) { + private HealthCheckResult checkTopicUnderReplicatedPartition(Tuple paramTuple) { TopicParam param = (TopicParam) paramTuple.getV1(); HealthDetectedInLatestMinutesConfig singleConfig = (HealthDetectedInLatestMinutesConfig) paramTuple.getV2(); @@ -97,7 +98,7 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { /** * 检查NoLeader */ - private HealthCheckResult checkTopicNoLeader(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkTopicNoLeader(Tuple singleConfigSimpleTuple) { TopicParam param = (TopicParam) singleConfigSimpleTuple.getV1(); List partitionList = partitionService.listPartitionFromCacheFirst(param.getClusterPhyId(), param.getTopicName()); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java index b83a4ee4..ef3cb554 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java @@ -9,6 +9,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.He 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.ClusterParam; 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; @@ -58,7 +59,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { } @Override - public List getResList(Long clusterPhyId) { + public List getResList(Long clusterPhyId) { ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(clusterPhyId); if (clusterPhy == null) { return new ArrayList<>(); @@ -82,7 +83,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.ZOOKEEPER; } - private HealthCheckResult checkBrainSplit(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkBrainSplit(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); HealthCompareValueConfig valueConfig = (HealthCompareValueConfig) singleConfigSimpleTuple.getV2(); @@ -100,7 +101,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return checkResult; } - private HealthCheckResult checkOutstandingRequests(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkOutstandingRequests(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2(); @@ -135,7 +136,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return checkResult; } - private HealthCheckResult checkWatchCount(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkWatchCount(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2(); @@ -171,7 +172,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return checkResult; } - private HealthCheckResult checkAliveConnections(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkAliveConnections(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2(); @@ -207,7 +208,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return checkResult; } - private HealthCheckResult checkApproximateDataSize(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkApproximateDataSize(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2(); @@ -243,7 +244,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return checkResult; } - private HealthCheckResult checkSentRate(Tuple singleConfigSimpleTuple) { + private HealthCheckResult checkSentRate(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2(); diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java index 3c9fdf23..f8c2185b 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java @@ -6,6 +6,7 @@ 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.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; @@ -41,15 +42,15 @@ public abstract class AbstractHealthCheckTask extends AbstractAsyncMetricsDispat List resultList = new ArrayList<>(); // 遍历Check-Service - List paramList = this.getCheckService().getResList(clusterPhy.getId()); + List paramList = this.getCheckService().getResList(clusterPhy.getId()); if (ValidateUtils.isEmptyList(paramList)) { // 当前无该维度的资源,则直接设置为 resultList.addAll(this.getNoResResult(clusterPhy.getId(), this.getCheckService(), healthConfigMap)); } // 遍历资源 - for (ClusterPhyParam clusterPhyParam: paramList) { - resultList.addAll(this.checkAndGetResult(clusterPhyParam, healthConfigMap)); + for (ClusterParam clusterParam: paramList) { + resultList.addAll(this.checkAndGetResult(clusterParam, healthConfigMap)); } try { @@ -93,13 +94,13 @@ public abstract class AbstractHealthCheckTask extends AbstractAsyncMetricsDispat return resultList; } - private List checkAndGetResult(ClusterPhyParam clusterPhyParam, + private List checkAndGetResult(ClusterParam clusterParam, Map healthConfigMap) { List resultList = new ArrayList<>(); // 进行检查 for (BaseClusterHealthConfig clusterHealthConfig: healthConfigMap.values()) { - HealthCheckResult healthCheckResult = this.getCheckService().checkAndGetResult(clusterPhyParam, clusterHealthConfig); + HealthCheckResult healthCheckResult = this.getCheckService().checkAndGetResult(clusterParam, clusterHealthConfig); if (healthCheckResult == null) { continue; } From 8ffe7e71018d832e6917455446c650ee7d66a9b3 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 14 Nov 2022 10:58:01 +0800 Subject: [PATCH 018/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DPrometheus?= =?UTF-8?q?=E4=B8=ADGroup=E9=83=A8=E5=88=86=E6=8C=87=E6=A0=87=E7=BC=BA?= =?UTF-8?q?=E5=B0=91=E7=9A=84=E9=97=AE=E9=A2=98(#756)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitor/component/AbstractMonitorSinkService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java index b2ca9283..47288792 100644 --- a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java +++ b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java @@ -139,11 +139,21 @@ public abstract class AbstractMonitorSinkService implements ApplicationListener< for(GroupMetrics g : groupMetrics){ if(g.isBGroupMetric()){ + // Group 指标 Map tagsMap = new HashMap<>(); tagsMap.put(CLUSTER_ID.getName(), g.getClusterPhyId()); tagsMap.put(CONSUMER_GROUP.getName(), g.getGroup()); pointList.addAll(genSinkPoint("Group", g.getMetrics(), g.getTimestamp(), tagsMap)); + } else { + // Group + Topic + Partition指标 + Map tagsMap = new HashMap<>(); + tagsMap.put(CLUSTER_ID.getName(), g.getClusterPhyId()); + tagsMap.put(CONSUMER_GROUP.getName(), g.getGroup()); + tagsMap.put(TOPIC.getName(), g.getTopic()); + tagsMap.put(PARTITION_ID.getName(), g.getPartitionId()); + + pointList.addAll(genSinkPoint("Group_Topic_Partition", g.getMetrics(), g.getTimestamp(), tagsMap)); } } From 898a55c703f2f094c8842d097757937e7daf020f Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 16 Nov 2022 10:31:29 +0800 Subject: [PATCH 019/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DBroker?= =?UTF-8?q?=E5=88=97=E8=A1=A8LogSize=E6=8C=87=E6=A0=87=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E6=97=B6=E5=90=8D=E7=A7=B0=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98(#759)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/service/broker/impl/BrokerMetricServiceImpl.java | 7 ++++--- .../version/metrics/kafka/BrokerMetricVersionItems.java | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java index 2f358d8e..50bf69d4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java @@ -66,7 +66,8 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker public static final String BROKER_METHOD_GET_HEALTH_SCORE = "getMetricHealthScore"; public static final String BROKER_METHOD_GET_PARTITIONS_SKEW = "getPartitionsSkew"; public static final String BROKER_METHOD_GET_LEADERS_SKEW = "getLeadersSkew"; - public static final String BROKER_METHOD_GET_LOG_SIZE = "getLogSize"; + public static final String BROKER_METHOD_GET_LOG_SIZE_FROM_CLIENT = "getLogSizeFromClient"; + public static final String BROKER_METHOD_GET_LOG_SIZE_FROM_JMX = "getLogSizeFromJmx"; public static final String BROKER_METHOD_IS_BROKER_ALIVE = "isBrokerAlive"; @Autowired @@ -109,8 +110,8 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker registerVCHandler( BROKER_METHOD_GET_PARTITIONS_SKEW, this::getPartitionsSkew); registerVCHandler( BROKER_METHOD_GET_LEADERS_SKEW, this::getLeadersSkew); - 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); + registerVCHandler( BROKER_METHOD_GET_LOG_SIZE_FROM_JMX, this::getLogSizeFromJmx); + registerVCHandler( BROKER_METHOD_GET_LOG_SIZE_FROM_CLIENT, this::getLogSizeFromClient); registerVCHandler( BROKER_METHOD_IS_BROKER_ALIVE, this::isBrokerAlive); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java index 0fc46d92..948d4d7f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/BrokerMetricVersionItems.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import static com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem.*; +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum.*; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_BROKER; import static com.xiaojukeji.know.streaming.km.common.jmx.JmxAttribute.*; import static com.xiaojukeji.know.streaming.km.common.jmx.JmxName.*; @@ -186,9 +187,12 @@ public class BrokerMetricVersionItems extends BaseMetricVersionMetric { .jmxObjectName( JMX_SERVER_PARTITIONS ).jmxAttribute(VALUE))); // LogSize 指标 - items.add(buildAllVersionsItem() + items.add(buildItem().minVersion(V_0_10_0_0).maxVersion(V_1_0_0) .name(BROKER_METRIC_LOG_SIZE).unit("byte").desc("Broker上的消息容量大小").category(CATEGORY_PARTITION) - .extendMethod(BROKER_METHOD_GET_LOG_SIZE)); + .extendMethod(BROKER_METHOD_GET_LOG_SIZE_FROM_JMX)); + items.add(buildItem().minVersion(V_1_0_0).maxVersion(V_MAX) + .name(BROKER_METRIC_LOG_SIZE).unit("byte").desc("Broker上的消息容量大小").category(CATEGORY_PARTITION) + .extendMethod(BROKER_METHOD_GET_LOG_SIZE_FROM_CLIENT)); // ActiveControllerCount 指标 items.add(buildAllVersionsItem(BROKER_METRIC_ACTIVE_CONTROLLER_COUNT, "个").desc("Broker是否为controller").category(CATEGORY_PERFORMANCE) From 636c2c6a83ed7063d091468d9c000064a0fc93ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=B3=E8=87=BB=E8=87=BB=E7=9A=84=E6=9D=82=E8=B4=A7?= =?UTF-8?q?=E9=93=BA?= Date: Thu, 17 Nov 2022 13:33:40 +0800 Subject: [PATCH 020/150] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cc19762..8f526268 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ PS: 提问请尽量把问题一次性描述清楚,并告知环境信息情况 **`2、微信群`** -微信加群:添加`mike_zhangliang`、`PenceXie`的微信号备注KnowStreaming加群。 +微信加群:添加`mike_zhangliang`、`PenceXie` 、`szzdzhp001`的微信号备注KnowStreaming加群。
加群之前有劳点一下 star,一个小小的 star 是对KnowStreaming作者们努力建设社区的动力。 From 2f807eec2b944cb361971d48a2aa51983f219127 Mon Sep 17 00:00:00 2001 From: duanxiaoqiu Date: Thu, 17 Nov 2022 16:45:23 +0800 Subject: [PATCH 021/150] =?UTF-8?q?[Feat]Topic=E5=88=97=E8=A1=A8=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E5=88=86=E4=BF=AE=E6=94=B9=E4=B8=BA=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E7=8A=B6=E6=80=81(#758)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/TopicList/index.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx index 60d7dd72..780161e3 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx @@ -18,6 +18,7 @@ import ReplicaMove from '@src/components/TopicJob/ReplicaMove'; import { formatAssignSize } from '../Jobs/config'; import { DownOutlined } from '@ant-design/icons'; import { tableHeaderPrefix } from '@src/constants/common'; +import {sliderValueMap} from "@src/pages/MutliClusterPage/config"; const { Option } = Select; @@ -90,8 +91,9 @@ const AutoPage = (props: any) => { // return item?.value || ''; const orgVal = record?.latestMetrics?.metrics?.[metricName]; if (orgVal !== undefined) { - if (metricName === 'HealthScore') { - return Math.round(orgVal).toLocaleString(); + if (metricName === 'HealthState') { + const val = sliderValueMap[(orgVal) as keyof typeof sliderValueMap]; + return val.name; } else if (metricName === 'LogSize') { return Number(Utils.formatAssignSize(orgVal, 'MB')).toLocaleString(); } else { @@ -157,13 +159,15 @@ const AutoPage = (props: any) => { width: 95, }, { - title: '健康分', - dataIndex: 'HealthScore', - key: 'HealthScore', + title: '健康状态', + dataIndex: 'HealthState', + key: 'HealthState', sorter: true, // 设计图上量出来的是144,但做的时候发现写144 header部分的sort箭头不出来,所以临时调大些 width: 170, - render: (value: any, record: any) => renderLine(record, 'HealthScore'), + render: (value: any, record: any) =>{ + return calcCurValue(record, "HealthState") + }, }, // { // title: '创建时间', From 6637ba4ccc06fac2903919dba967a10fb7d55da6 Mon Sep 17 00:00:00 2001 From: "night.liang" Date: Fri, 18 Nov 2022 11:18:41 +0800 Subject: [PATCH 022/150] =?UTF-8?q?[Optimize]=20optimize=20zk=20Outstandin?= =?UTF-8?q?gRequests=20checker=E2=80=99s=20exception=20log=20(#738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HealthCheckZookeeperService.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java index ef3cb554..45e6e0b8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java @@ -103,11 +103,12 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { private HealthCheckResult checkOutstandingRequests(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); + Long clusterPhyId = param.getClusterPhyId(); HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2(); Result metricsResult = zookeeperMetricService.collectMetricsFromZookeeper( new ZookeeperMetricParam( - param.getClusterPhyId(), + clusterPhyId, param.getZkAddressList(), param.getZkConfig(), ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS @@ -115,8 +116,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { ); if (metricsResult.failed() || !metricsResult.hasData()) { log.error( - "class=HealthCheckZookeeperService||method=checkOutstandingRequests||param={}||config={}||result={}||errMsg=get metrics failed", - param, valueConfig, metricsResult + "class=HealthCheckZookeeperService||method=checkOutstandingRequests||clusterPhyId={}||param={}||config={}||result={}||errMsg=get metrics failed",clusterPhyId ,param, valueConfig, metricsResult ); return null; } @@ -129,9 +129,21 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { ); Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS); + if(null == value){ + log.error("class=HealthCheckZookeeperService||method=checkOutstandingRequests||clusterPhyId={}|| errMsg=get OutstandingRequests metric failed, may be collect failed or zk mntr command not in whitelist.", clusterPhyId); + return null; + } + + Integer amount = valueConfig.getAmount(); + Double ratio = valueConfig.getRatio(); + if (null == amount || null == ratio) { + log.error("class=HealthCheckZookeeperService||method=checkOutstandingRequests||clusterPhyId={}||result={}||errMsg=get valueConfig amount/ratio config failed", clusterPhyId,valueConfig); + return null; + } + double configValue = amount.doubleValue() * ratio; - checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO); + checkResult.setPassed(value.doubleValue() <= configValue ? Constant.YES : Constant.NO); return checkResult; } From a165ecaeef9e31457be4711daaaaadf57f0a373e Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 18 Nov 2022 17:29:15 +0800 Subject: [PATCH 023/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DBroker&Topic?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=B6=EF=BC=8C=E7=89=88=E6=9C=AC=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98(#762)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kafka v2.3增加了增量修改配置的功能,但是KS中错误的将其配置为0.11.0版本就具备该能力,因此对其进行调整。 --- .../core/service/broker/impl/BrokerConfigServiceImpl.java | 4 ++-- .../km/core/service/topic/impl/TopicConfigServiceImpl.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java index e1e93c32..ecfbfcde 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java @@ -70,8 +70,8 @@ public class BrokerConfigServiceImpl extends BaseVersionControlService implement registerVCHandler(GET_BROKER_CONFIG, V_0_10_1_0, V_0_11_0_0, "getBrokerConfigByZKClient", this::getBrokerConfigByZKClient); registerVCHandler(GET_BROKER_CONFIG, V_0_11_0_0, V_MAX, "getBrokerConfigByKafkaClient", this::getBrokerConfigByKafkaClient); - registerVCHandler(MODIFY_BROKER_CONFIG, V_0_10_1_0, V_0_11_0_0, "modifyBrokerConfigByZKClient", this::modifyBrokerConfigByZKClient); - registerVCHandler(MODIFY_BROKER_CONFIG, V_0_11_0_0, V_MAX, "modifyBrokerConfigByKafkaClient", this::modifyBrokerConfigByKafkaClient); + registerVCHandler(MODIFY_BROKER_CONFIG, V_0_10_1_0, V_2_3_0, "modifyBrokerConfigByZKClient", this::modifyBrokerConfigByZKClient); + registerVCHandler(MODIFY_BROKER_CONFIG, V_2_3_0, V_MAX, "modifyBrokerConfigByKafkaClient", this::modifyBrokerConfigByKafkaClient); } @Override diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java index 09be0d43..2b089c67 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java @@ -98,9 +98,9 @@ public class TopicConfigServiceImpl extends BaseVersionControlService implements registerVCHandler(GET_TOPIC_CONFIG, V_0_10_0_0, V_0_11_0_0, "getTopicConfigByZKClient", this::getTopicConfigByZKClient); registerVCHandler(GET_TOPIC_CONFIG, V_0_11_0_0, V_MAX, "getTopicConfigByKafkaClient", this::getTopicConfigByKafkaClient); - registerVCHandler(MODIFY_TOPIC_CONFIG, V_0_10_0_0, V_0_10_2_0, "modifyTopicConfigByZKClientAndNodeVersionV1", this::modifyTopicConfigByZKClientAndNodeVersionV1); - registerVCHandler(MODIFY_TOPIC_CONFIG, V_0_10_2_0, V_0_11_0_3, "modifyTopicConfigByZKClientAndNodeVersionV2", this::modifyTopicConfigByZKClientAndNodeVersionV2); - registerVCHandler(MODIFY_TOPIC_CONFIG, V_0_11_0_3, V_MAX, "modifyTopicConfigByKafkaClient", this::modifyTopicConfigByKafkaClient); + registerVCHandler(MODIFY_TOPIC_CONFIG, V_0_10_0_0, V_0_10_2_0, "modifyTopicConfigByZKClientAndNodeVersionV1", this::modifyTopicConfigByZKClientAndNodeVersionV1); + registerVCHandler(MODIFY_TOPIC_CONFIG, V_0_10_2_0, V_2_3_0, "modifyTopicConfigByZKClientAndNodeVersionV2", this::modifyTopicConfigByZKClientAndNodeVersionV2); + registerVCHandler(MODIFY_TOPIC_CONFIG, V_2_3_0, V_MAX, "modifyTopicConfigByKafkaClient", this::modifyTopicConfigByKafkaClient); } @Override From 4b29a2fdfd3cb98cd60c22320935328f2dd2d7d4 Mon Sep 17 00:00:00 2001 From: pen4 Date: Sat, 19 Nov 2022 20:08:26 +0800 Subject: [PATCH 024/150] update org.springframework:spring-context 5.3.18 to 5.3.19 --- km-rest/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/km-rest/pom.xml b/km-rest/pom.xml index 8814a8a5..543d4483 100644 --- a/km-rest/pom.xml +++ b/km-rest/pom.xml @@ -18,7 +18,7 @@ 2.16.0 2.3.7.RELEASE - 5.3.18 + 5.3.19 diff --git a/pom.xml b/pom.xml index a168b74c..e1b7bbc3 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ 2.8.8 2.3.7.RELEASE - 5.3.18 + 5.3.19 9.0.41 1.2.83 From 38def45ad679aa7b77f96d22318b33867d5561b5 Mon Sep 17 00:00:00 2001 From: WangYaobo <1164642317@qq.com> Date: Tue, 22 Nov 2022 16:32:49 +0800 Subject: [PATCH 025/150] =?UTF-8?q?[Doc]=E5=A2=9E=E5=8A=A0=E6=97=A0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8E=92=E6=9F=A5=E6=96=87=E6=A1=A3(#773)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dev_guide/无数据排查文档.md | 286 +++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 docs/dev_guide/无数据排查文档.md diff --git a/docs/dev_guide/无数据排查文档.md b/docs/dev_guide/无数据排查文档.md new file mode 100644 index 00000000..337c5b13 --- /dev/null +++ b/docs/dev_guide/无数据排查文档.md @@ -0,0 +1,286 @@ +## 1、集群接入错误 + +### 1.1、异常现象 + +如下图所示,集群非空时,大概率为地址配置错误导致。 + + + + + +### 1.2、解决方案 + +接入集群时,依据提示的错误,进行相应的解决。例如: + + + +### 1.3、正常情况 + +接入集群时,页面信息都自动正常出现,没有提示错误。 + + + +## 2、JMX连接失败(需使用3.0.1及以上版本) + +### 2.1异常现象 + +Broker列表的JMX Port列出现红色感叹号,则该Broker的JMX连接异常。 + + + + + +#### 2.1.1、原因一:JMX未开启 + +##### 2.1.1.1、异常现象 + +broker列表的JMX Port值为-1,对应Broker的JMX未开启。 + + + +##### 2.1.1.2、解决方案 + +开启JMX,开启流程如下: + +1、修改kafka的bin目录下面的:`kafka-server-start.sh`文件 + +``` +# 在这个下面增加JMX端口的配置 +if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then + export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" + export JMX_PORT=9999 # 增加这个配置, 这里的数值并不一定是要9999 +fi +``` + + + +2、修改kafka的bin目录下面对的:`kafka-run-class.sh`文件 + +``` +# JMX settings +if [ -z "$KAFKA_JMX_OPTS" ]; then + KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false + -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=${当前机器的IP}" +fi + +# JMX port to use +if [ $JMX_PORT ]; then + KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT - Dcom.sun.management.jmxremote.rmi.port=$JMX_PORT" +fi +``` + + + +3、重启Kafka-Broker。 + + + +#### 2.1.2、原因二:JMX配置错误 + +##### 2.1.2.1、异常现象 + +错误日志: + +``` +# 错误一: 错误提示的是真实的IP,这样的话基本就是JMX配置的有问题了。 +2021-01-27 10:06:20.730 ERROR 50901 --- [ics-Thread-1-62] c.x.k.m.c.utils.jmx.JmxConnectorWrap : JMX connect exception, host:192.168.0.1 port:9999. java.rmi.ConnectException: Connection refused to host: 192.168.0.1; nested exception is: + +# 错误二:错误提示的是127.0.0.1这个IP,这个是机器的hostname配置的可能有问题。 +2021-01-27 10:06:20.730 ERROR 50901 --- [ics-Thread-1-62] c.x.k.m.c.utils.jmx.JmxConnectorWrap : JMX connect exception, host:127.0.0.1 port:9999. java.rmi.ConnectException: Connection refused to host: 127.0.0.1;; nested exception is: +``` + + + +##### 2.1.2.2、解决方案 + +开启JMX,开启流程如下: + +1、修改kafka的bin目录下面的:`kafka-server-start.sh`文件 + +``` +# 在这个下面增加JMX端口的配置 +if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then + export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" + export JMX_PORT=9999 # 增加这个配置, 这里的数值并不一定是要9999 +fi +``` + + + +2、修改kafka的bin目录下面对的:`kafka-run-class.sh`文件 + +``` +# JMX settings +if [ -z "$KAFKA_JMX_OPTS" ]; then + KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false + -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=${当前机器的IP}" +fi + +# JMX port to use +if [ $JMX_PORT ]; then + KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT - Dcom.sun.management.jmxremote.rmi.port=$JMX_PORT" +fi +``` + + + +3、重启Kafka-Broker。 + + + +#### 2.1.3、原因三:JMX开启SSL + +##### 2.1.3.1、解决方案 + + + +#### 2.1.4、原因四:连接了错误IP + +##### 2.1.4.1、异常现象 + +Broker 配置了内外网,而JMX在配置时,可能配置了内网IP或者外网IP,此时`KnowStreaming` 需要连接到特定网络的IP才可以进行访问。 + + 比如:Broker在ZK的存储结构如下所示,我们期望连接到 `endpoints` 中标记为 `INTERNAL` 的地址,但是 `KnowStreaming` 却连接了 `EXTERNAL` 的地址。 + +```json +{ + "listener_security_protocol_map": { + "EXTERNAL": "SASL_PLAINTEXT", + "INTERNAL": "SASL_PLAINTEXT" + }, + "endpoints": [ + "EXTERNAL://192.168.0.1:7092", + "INTERNAL://192.168.0.2:7093" + ], + "jmx_port": 8099, + "host": "192.168.0.1", + "timestamp": "1627289710439", + "port": -1, + "version": 4 +} +``` + +##### 2.1.4.2、解决方案 + +可以手动往`ks_km_physical_cluster`表的`jmx_properties`字段增加一个`useWhichEndpoint`字段,从而控制 `KnowStreaming` 连接到特定的JMX IP及PORT。 + +`jmx_properties`格式: + +```json +{ + "maxConn": 100, // KM对单台Broker的最大JMX连接数 + "username": "xxxxx", //用户名,可以不填写 + "password": "xxxx", // 密码,可以不填写 + "openSSL": true, //开启SSL, true表示开启ssl, false表示关闭 + "useWhichEndpoint": "EXTERNAL" //指定要连接的网络名称,填写EXTERNAL就是连接endpoints里面的EXTERNAL地址 +} +``` + + + +SQL例子: + +```sql +UPDATE ks_km_physical_cluster SET jmx_properties='{ "maxConn": 10, "username": "xxxxx", "password": "xxxx", "openSSL": false , "useWhichEndpoint": "xxx"}' where id={xxx}; +``` + +### 2.2、正常情况 + +修改完成后,如果看到 JMX PORT这一列全部为绿色,则表示JMX已正常。 + + + + + +## 3、Elasticsearch问题 + +注意:mac系统在执行curl指令时,可能报zsh错误。可参考以下操作。 + +``` +1 进入.zshrc 文件 vim ~/.zshrc +2.在.zshrc中加入 setopt no_nomatch +3.更新配置 source ~/.zshrc +``` + +### 3.1、原因一:缺少索引 + +#### 3.1.1、异常现象 + +报错信息 + +``` +com.didiglobal.logi.elasticsearch.client.model.exception.ESIndexNotFoundException: method [GET], host[http://127.0.0.1:9200], URI [/ks_kafka_broker_metric_2022-10-21,ks_kafka_broker_metric_2022-10-22/_search], status line [HTTP/1.1 404 Not Found] +``` + +curl http://{ES的IP地址}:{ES的端口号}/_cat/indices/ks_kafka* 查看KS索引列表,发现没有索引。 + +#### 3.1.2、解决方案 + +执行[/km-dist/init/template/template.sh](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/template/template.sh)脚本创建索引。 + + + +### 3.2、原因二:索引模板错误 + +#### 3.2.1、异常现象 + +多集群列表有数据,集群详情页图标无数据。查询KS索引模板列表,发现不存在。 + +``` +curl {ES的IP地址}:{ES的端口号}/_cat/templates/ks_kafka*?v&h=name +``` + +正常KS模板如下图所示。 + + + + + +#### 3.2.2、解决方案 + +删除KS索引模板和索引 + +``` +curl -XDELETE {ES的IP地址}:{ES的端口号}/ks_kafka* +curl -XDELETE {ES的IP地址}:{ES的端口号}/_template/ks_kafka* +``` + +执行[/km-dist/init/template/template.sh](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/template/template.sh)脚本初始化索引和模板。 + + + +### 3.3、原因三:集群Shard满 + +#### 3.3.1、异常现象 + +报错信息 + +``` +com.didiglobal.logi.elasticsearch.client.model.exception.ESIndexNotFoundException: method [GET], host[http://127.0.0.1:9200], URI [/ks_kafka_broker_metric_2022-10-21,ks_kafka_broker_metric_2022-10-22/_search], status line [HTTP/1.1 404 Not Found] +``` + +尝试手动创建索引失败。 + +``` +#创建ks_kafka_cluster_metric_test索引的指令 +curl -s -XPUT http://{ES的IP地址}:{ES的端口号}/ks_kafka_cluster_metric_test +``` + +#### 3.3.2、解决方案 + +ES索引的默认分片数量为1000,达到数量以后,索引创建失败。 + ++ 扩大ES索引数量上限,执行指令 + +``` +curl -XPUT -H"content-type:application/json" http://{ES的IP地址}:{ES的端口号}/_cluster/settings -d ' +{ + "persistent": { + "cluster": { + "max_shards_per_node":{索引上限,默认为1000} + } + } +}' +``` + +执行[/km-dist/init/template/template.sh](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/template/template.sh)脚本补全索引。 From daeb5c4cec8633e18662065302a6e61e2b86e255 Mon Sep 17 00:00:00 2001 From: limaiwang Date: Wed, 23 Nov 2022 20:52:08 +0800 Subject: [PATCH 026/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E9=9B=86?= =?UTF-8?q?=E7=BE=A4=E9=85=8D=E7=BD=AE=E4=B8=8D=E5=86=99=E6=97=B6,?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=8F=82=E6=95=B0=E6=8A=A5=E9=94=99=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/MutliClusterPage/AccessCluster.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx index e5c1f649..fffbe60f 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx @@ -144,7 +144,7 @@ const AccessClusters = (props: any): JSX.Element => { return Utils.post(api.kafkaValidator, { bootstrapServers: bootstrapServers || '', zookeeper: zookeeper || '', - clientProperties, + clientProperties: clientProperties || {}, }) .then( (res: { From c4a81613f4db45a91e4c5ed016acd74dc85bf476 Mon Sep 17 00:00:00 2001 From: SimonTeo58 Date: Thu, 24 Nov 2022 21:26:27 +0800 Subject: [PATCH 027/150] =?UTF-8?q?[Optimize]=E6=9B=B4=E6=96=B0Topic-Messa?= =?UTF-8?q?ges=E6=8A=BD=E5=B1=89=E6=96=87=E6=A1=88(#771)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/pages/TopicDetail/Messages.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Messages.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Messages.tsx index 450cb59b..374af706 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Messages.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Messages.tsx @@ -173,7 +173,7 @@ const TopicMessages = (props: any) => { style={{ margin: '12px 0 4px', padding: '7px 12px', background: '#FFF9E6' }} message={
- 此处展示 Topic 最近的 100 条 messages。 + 此处展示 Topic 的 100 条 messages。 {process.env.BUSINESS_VERSION ? ( 若想获取其他 messages,可前往 Produce&Consume 进行操作 From 47b6c5d86a875c802dda2bd35a67a7b391d554c6 Mon Sep 17 00:00:00 2001 From: duanxiaoqiu Date: Sat, 26 Nov 2022 18:09:14 +0800 Subject: [PATCH 028/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BAtopic=E9=80=89=E6=8B=A9=E8=BF=87=E6=9C=9F=E7=AD=96?= =?UTF-8?q?=E7=95=A5(kafka=E7=89=88=E6=9C=AC0.10.1.0=E4=B9=8B=E5=89=8D)com?= =?UTF-8?q?pact=E5=92=8Cdelete=E5=8F=AA=E8=83=BD=E4=BA=8C=E9=80=89?= =?UTF-8?q?=E4=B8=80(didi#770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/version/fe/FrontEndControlVersionItems.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java index 4daa465b..769bd225 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java @@ -31,6 +31,8 @@ public class FrontEndControlVersionItems extends BaseMetricVersionMetric { private static final String FE_SECURITY_ACL_CREATE_RESOURCE_TYPE_TRANSACTIONAL_ID = "FESecurityAclCreateResourceTypeTransactionalId"; private static final String FE_SECURITY_ACL_CREATE_RESOURCE_TYPE_DELEGATION_TOKEN = "FESecurityAclCreateResourceTypeDelegationToken"; + private static final String FE_CREATE_TOPIC_CLEANUP_POLICY = "FECreateTopicCleanupPolicy"; + public FrontEndControlVersionItems(){} @Override @@ -74,6 +76,10 @@ public class FrontEndControlVersionItems extends BaseMetricVersionMetric { itemList.add(buildItem().minVersion(VersionEnum.V_1_1_0).maxVersion(VersionEnum.V_MAX) .name(FE_SECURITY_ACL_CREATE_RESOURCE_TYPE_DELEGATION_TOKEN).desc("Security-创建ACL-ResourceType-DelegationToken")); + // topic-创建-清理策略(delete和compact)V_0_10_1_0都可以选择 + itemList.add(buildItem().minVersion(VersionEnum.V_0_10_1_0).maxVersion(VersionEnum.V_MAX) + .name(FE_CREATE_TOPIC_CLEANUP_POLICY).desc("Topic-创建Topic-Cleanup-Policy")); + return itemList; } } From feb03aede64f6ab1f33430ccf90039700d439a2e Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 28 Nov 2022 15:10:32 +0800 Subject: [PATCH 029/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E6=B1=A0=E7=9A=84=E5=90=8D=E7=A7=B0(#789)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/collector/service/CollectThreadPoolService.java | 2 +- .../know/streaming/km/common/utils/FutureNoWaitUtil.java | 6 +++--- .../know/streaming/km/common/utils/FutureUtil.java | 6 +++--- .../know/streaming/km/common/utils/FutureWaitUtil.java | 4 ++-- .../streaming/km/task/service/TaskThreadPoolService.java | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/service/CollectThreadPoolService.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/service/CollectThreadPoolService.java index 22505b35..f5d3c496 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/service/CollectThreadPoolService.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/service/CollectThreadPoolService.java @@ -237,7 +237,7 @@ public class CollectThreadPoolService { private synchronized FutureWaitUtil closeOldAndCreateNew(Long shardId) { // 新的 FutureWaitUtil newFutureUtil = FutureWaitUtil.init( - "CollectorMetricsFutureUtil-Shard-" + shardId, + "MetricCollect-Shard-" + shardId, this.futureUtilThreadNum, this.futureUtilThreadNum, this.futureUtilQueueSize diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java index 6bd7ae19..378abaf6 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java @@ -23,7 +23,7 @@ public class FutureNoWaitUtil { private FutureNoWaitUtil() { } - public static FutureNoWaitUtil init(String name, int corePoolSize, int maxPoolSize, int queueSize) { + public static FutureNoWaitUtil init(String threadPoolName, int corePoolSize, int maxPoolSize, int queueSize) { FutureNoWaitUtil futureUtil = new FutureNoWaitUtil<>(); // 创建任务线程池 @@ -33,7 +33,7 @@ public class FutureNoWaitUtil { 300, TimeUnit.SECONDS, new LinkedBlockingDeque<>(queueSize), - new NamedThreadFactory("KS-KM-FutureNoWaitUtil-" + name), + new NamedThreadFactory(threadPoolName), new ThreadPoolExecutor.DiscardOldestPolicy() //对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。 ); futureUtil.executor.allowCoreThreadTimeOut(true); @@ -41,7 +41,7 @@ public class FutureNoWaitUtil { futureUtil.delayQueueData = new DelayQueue<>(); // 创建检查延迟队列的线程并启动 - futureUtil.checkDelayQueueThread = new Thread(() -> futureUtil.runCheck(), "KS-KM-FutureNoWaitUtil-CheckDelayQueueData-" + name); + futureUtil.checkDelayQueueThread = new Thread(() -> futureUtil.runCheck(), threadPoolName + "-CheckTaskTimeout"); futureUtil.checkDelayQueueThread.setDaemon(true); futureUtil.checkDelayQueueThread.start(); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureUtil.java index 1e5cd875..ed68939f 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureUtil.java @@ -9,12 +9,12 @@ import java.util.concurrent.*; public class FutureUtil { private ThreadPoolExecutor executor; - public static final FutureUtil quickStartupFutureUtil = FutureUtil.init("QuickStartupFutureUtil", 8, 8, 10240); + public static final FutureUtil quickStartupFutureUtil = FutureUtil.init("QuickStartupTP", 8, 8, 10240); private FutureUtil() { } - public static FutureUtil init(String name, int corePoolSize, int maxPoolSize, int queueSize) { + public static FutureUtil init(String threadPoolName, int corePoolSize, int maxPoolSize, int queueSize) { FutureUtil futureUtil = new FutureUtil<>(); futureUtil.executor = new ThreadPoolExecutor( @@ -23,7 +23,7 @@ public class FutureUtil { 300, TimeUnit.SECONDS, new LinkedBlockingDeque<>(queueSize), - new NamedThreadFactory("FutureUtil-" + name), + new NamedThreadFactory(threadPoolName), new ThreadPoolExecutor.DiscardOldestPolicy() //对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。 ); futureUtil.executor.allowCoreThreadTimeOut(true); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java index cf2af736..e62efb8b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java @@ -23,7 +23,7 @@ public class FutureWaitUtil { private FutureWaitUtil() { } - public static FutureWaitUtil init(String name, int corePoolSize, int maxPoolSize, int queueSize) { + public static FutureWaitUtil init(String threadPoolName, int corePoolSize, int maxPoolSize, int queueSize) { FutureWaitUtil futureUtil = new FutureWaitUtil<>(); futureUtil.executor = new ThreadPoolExecutor( @@ -32,7 +32,7 @@ public class FutureWaitUtil { 300, TimeUnit.SECONDS, new LinkedBlockingDeque<>(queueSize), - new NamedThreadFactory("FutureWaitUtil-" + name), + new NamedThreadFactory(threadPoolName), new ThreadPoolExecutor.DiscardOldestPolicy() //对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。 ); futureUtil.executor.allowCoreThreadTimeOut(true); diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/TaskThreadPoolService.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/TaskThreadPoolService.java index f4bb6826..33545f04 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/TaskThreadPoolService.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/TaskThreadPoolService.java @@ -52,21 +52,21 @@ public class TaskThreadPoolService { @PostConstruct private void init() { metricsTaskThreadPool = FutureNoWaitUtil.init( - "metricsTaskThreadPool", + "MetricsTaskTP", metricsTaskThreadNum, metricsTaskThreadNum, metricsTaskQueueSize ); metadataTaskThreadPool = FutureNoWaitUtil.init( - "metadataTaskThreadPool", + "MetadataTaskTP", metadataTaskThreadNum, metadataTaskThreadNum, metadataTaskQueueSize ); commonTaskThreadPool = FutureNoWaitUtil.init( - "commonTaskThreadPool", + "CommonTaskTP", commonTaskThreadNum, commonTaskThreadNum, commonTaskQueueSize From 5127b600ecb5a96481c6f3a7b5346a80999e0f95 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 28 Nov 2022 14:32:40 +0800 Subject: [PATCH 030/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96ESClient?= =?UTF-8?q?=E7=9A=84=E5=B9=B6=E5=8F=91=E8=AE=BF=E9=97=AE=E6=8E=A7=E5=88=B6?= =?UTF-8?q?(#787)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/persistence/es/ESOpClient.java | 35 ++++--------------- km-rest/src/main/resources/application.yml | 2 +- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java index 1efa01d6..5e91f7b2 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java @@ -37,7 +37,6 @@ import javax.annotation.PostConstruct; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -61,7 +60,7 @@ public class ESOpClient { /** * 客户端个数 */ - @Value("${es.client.client-cnt:10}") + @Value("${es.client.client-cnt:2}") private Integer clientCnt; /** @@ -79,13 +78,13 @@ public class ESOpClient { /** * 更新es数据的客户端连接队列 */ - private LinkedBlockingQueue esClientPool; + private List esClientPool; - private static final Integer ES_OPERATE_TIMEOUT = 30; + private static final Integer ES_OPERATE_TIMEOUT = 30; @PostConstruct public void init(){ - esClientPool = new LinkedBlockingQueue<>( clientCnt ); + esClientPool = new ArrayList<>(clientCnt); for (int i = 0; i < clientCnt; ++i) { ESClient esClient = this.buildEsClient(esAddress, esPass, "", ""); @@ -102,37 +101,15 @@ public class ESOpClient { * @return */ public ESClient getESClientFromPool() { - ESClient esClient = null; - int retryCount = 0; - - // 如果esClient为空或者重试次数小于5次,循环获取 - while (esClient == null && retryCount < 5) { - try { - ++retryCount; - esClient = esClientPool.poll(3, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - if (esClient == null) { - LOGGER.error( "class=ESOpClient||method=getESClientFromPool||errMsg=fail to get es client from pool"); - } - - return esClient; + return esClientPool.get((int)(System.currentTimeMillis() % clientCnt)); } /** * 归还到es http 客户端连接池 - * * @param esClient */ public void returnESClientToPool(ESClient esClient) { - try { - this.esClientPool.put(esClient); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + // 已不需要进行归还,后续再删除该代码 } /** diff --git a/km-rest/src/main/resources/application.yml b/km-rest/src/main/resources/application.yml index c9cd3c5e..3b01022e 100644 --- a/km-rest/src/main/resources/application.yml +++ b/km-rest/src/main/resources/application.yml @@ -85,7 +85,7 @@ es: client: address: 127.0.0.1:8091,127.0.0.1:8061,127.0.0.1:8061 pass: # ES账号密码,如果有账号密码,按照 username:password 的格式填写,没有则不需要填写 - client-cnt: 10 # 创建的ES客户端数 + client-cnt: 2 # 创建的ES客户端数 io-thread-cnt: 2 max-retry-cnt: 5 index: From c2fd0a8410a0adfa5dd9430c5b656b3ba070d4d9 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 29 Nov 2022 20:54:41 +0800 Subject: [PATCH 031/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96Sonar?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=87=BA=E7=9A=84=E4=B8=8D=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cluster/impl/MultiClusterPhyManagerImpl.java | 7 +------ .../metric/kafka/GroupMetricCollector.java | 2 +- .../bean/entity/param/cluster/ClusterPhyParam.java | 1 - .../service/broker/impl/BrokerSpecServiceImpl.java | 6 +++--- .../km/core/service/cluster/ClusterPhyService.java | 1 - .../core/service/topic/impl/TopicServiceImpl.java | 13 +++++++------ 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java index 3c8458e9..e5fa31e1 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java @@ -29,10 +29,7 @@ import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.Clust import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @@ -57,7 +54,6 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager { false ); - // TODO 后续产品上,看是否需要增加一个未知的状态,否则新接入的集群,因为新接入的集群,数据存在延迟 ClusterPhysState physState = new ClusterPhysState(0, 0, clusterPhyList.size()); for (ClusterPhy clusterPhy: clusterPhyList) { KafkaController kafkaController = controllerMap.get(clusterPhy.getId()); @@ -111,7 +107,6 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager { // 转为vo格式,方便后续进行分页筛选等 List voList = ConvertUtil.list2List(clusterPhyList, ClusterPhyDashboardVO.class); - // TODO 后续产品上,看是否需要增加一个未知的状态,否则新接入的集群,因为新接入的集群,数据存在延迟 // 获取集群controller信息并补充到vo中, Map controllerMap = kafkaControllerService.getKafkaControllersFromDB(clusterPhyList.stream().map(elem -> elem.getId()).collect(Collectors.toList()), false); for (ClusterPhyDashboardVO vo: voList) { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java index e41af566..44828dae 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java @@ -61,7 +61,7 @@ public class GroupMetricCollector extends AbstractMetricCollector items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); - FutureWaitUtil future = getFutureUtilByClusterPhyId(clusterPhyId); + FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); Map> metricsMap = new ConcurrentHashMap<>(); for(String groupName : groups) { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java index 9b01fa84..203efcdb 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ClusterPhyParam.java @@ -1,6 +1,5 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java index b2c7ef7f..5cbe3ce8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java @@ -1,5 +1,7 @@ package com.xiaojukeji.know.streaming.km.core.service.broker.impl; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.BrokerSpec; import com.xiaojukeji.know.streaming.km.common.bean.po.config.PlatformClusterConfigPO; import com.xiaojukeji.know.streaming.km.common.enums.config.ConfigGroupEnum; @@ -15,11 +17,11 @@ import java.util.Map; @Service public class BrokerSpecServiceImpl implements BrokerSpecService { + private static final ILog LOGGER = LogFactory.getLog(BrokerSpecServiceImpl.class); @Autowired private PlatformClusterConfigService platformClusterConfigService; - @Override public Map getBrokerSpecMap(Long clusterPhyId) { //获取规格信息 @@ -37,6 +39,4 @@ public class BrokerSpecServiceImpl implements BrokerSpecService { } return brokerSpecMap; } - - } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyService.java index 56b6640b..4027267a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyService.java @@ -9,7 +9,6 @@ import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.exception.ParamErrorException; import java.util.List; -import java.util.Set; /** * @author didi diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java index e2870d9d..2c0309f0 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java @@ -116,15 +116,16 @@ public class TopicServiceImpl implements TopicService { @Override public List listRecentUpdateTopicNamesFromDB(Long clusterPhyId, Integer time) { - Date updateTime = DateUtils.getBeforeSeconds(new Date(), time); LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(TopicPO::getClusterPhyId, clusterPhyId); - lambdaQueryWrapper.ge(TopicPO::getUpdateTime, updateTime); - List topicPOS = topicDAO.selectList(lambdaQueryWrapper); - if (topicPOS.isEmpty()){ + lambdaQueryWrapper.ge(TopicPO::getClusterPhyId, clusterPhyId); + lambdaQueryWrapper.ge(TopicPO::getUpdateTime, DateUtils.getBeforeSeconds(new Date(), time)); + + List poList = topicDAO.selectList(lambdaQueryWrapper); + if (poList.isEmpty()){ return new ArrayList<>(); } - return topicPOS.stream().map(TopicPO::getTopicName).collect(Collectors.toList()); + + return poList.stream().map(elem -> elem.getTopicName()).collect(Collectors.toList()); } @Override From 6241eb052a893f868c29387653753b1a2747ebe0 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 30 Nov 2022 11:13:41 +0800 Subject: [PATCH 032/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DKafkaJMXClien?= =?UTF-8?q?t=E7=B1=BB=E4=B8=ADlogger=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98(#794)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../know/streaming/km/persistence/kafka/KafkaJMXClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java index 1ee0adcb..11e5ae36 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java @@ -25,7 +25,7 @@ import java.util.stream.Collectors; @Component public class KafkaJMXClient extends AbstractClusterLoadedChangedHandler { - private static final ILog log = LogFactory.getLog(KafkaAdminZKClient.class); + private static final ILog log = LogFactory.getLog(KafkaJMXClient.class); @Autowired private BrokerDAO brokerDAO; From 175b8d643ac7d4dc64a8c73987e77202bdba43e7 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 2 Dec 2022 14:39:57 +0800 Subject: [PATCH 033/150] =?UTF-8?q?[Optimize]=E7=BB=9F=E4=B8=80=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=A0=BC=E5=BC=8F-part1(#800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biz/cluster/impl/ClusterZookeepersManagerImpl.java | 2 +- .../impl/KafkaControllerServiceImpl.java | 6 +++--- .../service/partition/impl/OpPartitionServiceImpl.java | 4 ++-- .../partition/impl/PartitionMetricServiceImpl.java | 10 +++++----- .../km/core/service/topic/impl/TopicServiceImpl.java | 4 ++-- .../core/service/zookeeper/impl/ZnodeServiceImpl.java | 8 ++++---- .../zookeeper/impl/ZookeeperMetricServiceImpl.java | 2 +- .../service/zookeeper/impl/ZookeeperServiceImpl.java | 6 +++--- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java index 6441087e..aca30269 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterZookeepersManagerImpl.java @@ -94,7 +94,7 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager { ); if (metricsResult.failed()) { LOGGER.error( - "class=ClusterZookeepersManagerImpl||method=getClusterPhyZookeepersState||clusterPhyId={}||errMsg={}", + "method=getClusterPhyZookeepersState||clusterPhyId={}||errMsg={}", clusterPhyId, metricsResult.getMessage() ); return Result.buildSuc(vo); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java index 8048eabe..6047d844 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java @@ -140,7 +140,7 @@ public class KafkaControllerServiceImpl implements KafkaControllerService { try { adminClient = kafkaAdminClient.getClient(clusterPhy.getId()); } catch (Exception e) { - log.error("class=KafkaControllerServiceImpl||method=getControllerFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getControllerFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); // 集群已经加载进来,但是创建admin-client失败,则设置无controller return Result.buildSuc(); @@ -178,7 +178,7 @@ public class KafkaControllerServiceImpl implements KafkaControllerService { )); } catch (Exception e) { log.error( - "class=KafkaControllerServiceImpl||method=getControllerFromAdminClient||clusterPhyId={}||tryTime={}||errMsg=exception", + "method=getControllerFromAdminClient||clusterPhyId={}||tryTime={}||errMsg=exception", clusterPhy.getId(), i, e ); } @@ -192,7 +192,7 @@ public class KafkaControllerServiceImpl implements KafkaControllerService { try { return Result.buildSuc(kafkaZKDAO.getKafkaController(clusterPhy.getId(), false)); } catch (Exception e) { - log.error("class=KafkaControllerServiceImpl||method=getControllerFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getControllerFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java index 0f1186ef..6dbb5816 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java @@ -84,7 +84,7 @@ public class OpPartitionServiceImpl extends BaseVersionControlService implements return Result.buildSuc(); } catch (Exception e) { LOGGER.error( - "class=OpPartitionServiceImpl||method=preferredReplicaElectionByZKClient||clusterPhyId={}||errMsg=exception", + "method=preferredReplicaElectionByZKClient||clusterPhyId={}||errMsg=exception", partitionParam.getClusterPhyId(), e ); @@ -109,7 +109,7 @@ public class OpPartitionServiceImpl extends BaseVersionControlService implements return Result.buildSuc(); } catch (Exception e) { LOGGER.error( - "class=OpPartitionServiceImpl||method=preferredReplicaElectionByKafkaClient||clusterPhyId={}||errMsg=exception", + "method=preferredReplicaElectionByKafkaClient||clusterPhyId={}||errMsg=exception", partitionParam.getClusterPhyId(), e ); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java index 90646fe4..e6ce6a62 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java @@ -191,7 +191,7 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par } } else { LOGGER.warn( - "class=PartitionMetricServiceImpl||method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||resultMsg={}||msg=get begin offset failed", + "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||resultMsg={}||msg=get begin offset failed", clusterPhyId, topicName, beginOffsetMapResult.getMessage() ); } @@ -211,7 +211,7 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par } } else { LOGGER.warn( - "class=PartitionMetricServiceImpl||method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||resultMsg={}||msg=get end offset failed", + "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||resultMsg={}||msg=get end offset failed", clusterPhyId, topicName, endOffsetMapResult.getMessage() ); } @@ -235,7 +235,7 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par } } else { LOGGER.warn( - "class=PartitionMetricServiceImpl||method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||endResultMsg={}||beginResultMsg={}||msg=get messages failed", + "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||endResultMsg={}||beginResultMsg={}||msg=get messages failed", clusterPhyId, topicName, endOffsetMapResult.getMessage(), beginOffsetMapResult.getMessage() ); } @@ -286,7 +286,7 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par continue; } catch (Exception e) { LOGGER.error( - "class=PartitionMetricServiceImpl||method=getMetricFromJmx||clusterPhyId={}||topicName={}||partitionId={}||leaderBrokerId={}||metricName={}||msg={}", + "method=getMetricFromJmx||clusterPhyId={}||topicName={}||partitionId={}||leaderBrokerId={}||metricName={}||msg={}", clusterPhyId, topicName, partition.getPartitionId(), partition.getLeaderBrokerId(), metricName, e.getClass().getName() ); } @@ -341,7 +341,7 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par continue; } catch (Exception e) { LOGGER.error( - "class=PartitionMetricServiceImpl||method=getTopicAvgMetricFromJmx||clusterPhyId={}||topicName={}||partitionId={}||leaderBrokerId={}||metricName={}||msg={}", + "method=getTopicAvgMetricFromJmx||clusterPhyId={}||topicName={}||partitionId={}||leaderBrokerId={}||metricName={}||msg={}", clusterPhyId, topicName, partition.getPartitionId(), partition.getLeaderBrokerId(), metricName, e.getClass().getName() ); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java index 2c0309f0..2689373c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java @@ -260,7 +260,7 @@ public class TopicServiceImpl implements TopicService { return Result.buildSuc(topicList); } catch (Exception e) { - log.error("class=TopicServiceImpl||method=getTopicsFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getTopicsFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } @@ -278,7 +278,7 @@ public class TopicServiceImpl implements TopicService { return Result.buildSuc(topicList); } catch (Exception e) { - log.error("class=TopicServiceImpl||method=getTopicsFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getTopicsFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZnodeServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZnodeServiceImpl.java index 73ff251f..af613c03 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZnodeServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZnodeServiceImpl.java @@ -43,10 +43,10 @@ public class ZnodeServiceImpl implements ZnodeService { try { children = kafkaZKDAO.getChildren(clusterPhyId, path, false); } catch (NotExistException e) { - LOGGER.error("class=ZnodeServiceImpl||method=listZnodeChildren||clusterPhyId={}||errMsg={}", clusterPhyId, "create ZK client create failed"); + LOGGER.error("method=listZnodeChildren||clusterPhyId={}||errMsg={}", clusterPhyId, "create ZK client create failed"); return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, "ZK客户端创建失败"); } catch (Exception e) { - LOGGER.error("class=ZnodeServiceImpl||method=listZnodeChildren||clusterPhyId={}||errMsg={}", clusterPhyId, "ZK operate failed"); + LOGGER.error("method=listZnodeChildren||clusterPhyId={}||errMsg={}", clusterPhyId, "ZK operate failed"); return Result.buildFromRSAndMsg(ResultStatus.ZK_OPERATE_FAILED, "ZK操作失败"); } @@ -69,10 +69,10 @@ public class ZnodeServiceImpl implements ZnodeService { try { dataAndStat = kafkaZKDAO.getDataAndStat(clusterPhyId, path); } catch (NotExistException e) { - LOGGER.error("class=ZnodeServiceImpl||method=getZnode||clusterPhyId={}||errMsg={}", clusterPhyId, "create ZK client create failed"); + LOGGER.error("method=getZnode||clusterPhyId={}||errMsg={}", clusterPhyId, "create ZK client create failed"); return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, "ZK客户端创建失败"); } catch (Exception e) { - LOGGER.error("class=ZnodeServiceImpl||method=getZnode||clusterPhyId={}||errMsg={}", clusterPhyId, "ZK operate failed"); + LOGGER.error("method=getZnode||clusterPhyId={}||errMsg={}", clusterPhyId, "ZK operate failed"); return Result.buildFromRSAndMsg(ResultStatus.ZK_OPERATE_FAILED, "ZK操作失败"); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java index a9be87f6..d90ec378 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperMetricServiceImpl.java @@ -140,7 +140,7 @@ public class ZookeeperMetricServiceImpl extends BaseMetricService implements Zoo metrics.putMetric(ret.getData().getMetrics()); } catch (Exception e){ LOGGER.error( - "class=ZookeeperMetricServiceImpl||method=collectMetricsFromZookeeper||clusterPhyId={}||metricName={}||errMsg=exception!", + "method=collectMetricsFromZookeeper||clusterPhyId={}||metricName={}||errMsg=exception!", clusterPhyId, metricName, e ); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperServiceImpl.java index 84d7eb4c..8b0d63d1 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/zookeeper/impl/ZookeeperServiceImpl.java @@ -41,7 +41,7 @@ public class ZookeeperServiceImpl implements ZookeeperService { addressList = ZookeeperUtils.connectStringParser(zookeeperAddress); } catch (Exception e) { LOGGER.error( - "class=ZookeeperServiceImpl||method=listFromZookeeperCluster||clusterPhyId={}||zookeeperAddress={}||errMsg=exception!", + "method=listFromZookeeperCluster||clusterPhyId={}||zookeeperAddress={}||errMsg=exception!", clusterPhyId, zookeeperAddress, e ); @@ -87,7 +87,7 @@ public class ZookeeperServiceImpl implements ZookeeperService { zookeeperDAO.updateById(newInfo); } } catch (Exception e) { - LOGGER.error("class=ZookeeperServiceImpl||method=batchReplaceDataInDB||clusterPhyId={}||newInfo={}||errMsg=exception", clusterPhyId, newInfo, e); + LOGGER.error("method=batchReplaceDataInDB||clusterPhyId={}||newInfo={}||errMsg=exception", clusterPhyId, newInfo, e); } } @@ -96,7 +96,7 @@ public class ZookeeperServiceImpl implements ZookeeperService { try { zookeeperDAO.deleteById(entry.getValue().getId()); } catch (Exception e) { - LOGGER.error("class=ZookeeperServiceImpl||method=batchReplaceDataInDB||clusterPhyId={}||expiredInfo={}||errMsg=exception", clusterPhyId, entry.getValue(), e); + LOGGER.error("method=batchReplaceDataInDB||clusterPhyId={}||expiredInfo={}||errMsg=exception", clusterPhyId, entry.getValue(), e); } }); } From 7a52cf67b069843df8f29ee170a259e274602437 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 2 Dec 2022 15:01:24 +0800 Subject: [PATCH 034/150] =?UTF-8?q?[Optimize]=E6=97=A5=E5=BF=97=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=A0=BC=E5=BC=8F&=E4=BC=98=E5=8C=96=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E5=86=85=E5=AE=B9-part2(#800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metric/AbstractMetricCollector.java | 30 ++++++- .../metric/kafka/BrokerMetricCollector.java | 31 +++----- .../metric/kafka/ClusterMetricCollector.java | 38 +++++---- .../metric/kafka/GroupMetricCollector.java | 78 ++++++++----------- .../kafka/PartitionMetricCollector.java | 28 ++----- .../metric/kafka/ReplicaMetricCollector.java | 30 +++---- .../metric/kafka/TopicMetricCollector.java | 23 ++---- .../kafka/ZookeeperMetricCollector.java | 33 +++----- .../sink/AbstractMetricESSender.java | 52 ++++--------- .../collector/sink/BrokerMetricESSender.java | 4 +- .../collector/sink/ClusterMetricESSender.java | 4 +- .../collector/sink/GroupMetricESSender.java | 4 +- .../sink/PartitionMetricESSender.java | 4 +- .../collector/sink/ReplicaMetricESSender.java | 4 +- .../collector/sink/TopicMetricESSender.java | 4 +- .../sink/ZookeeperMetricESSender.java | 4 +- .../streaming/km/common/utils/LoggerUtil.java | 21 +++++ km-rest/src/main/resources/logback-spring.xml | 10 +-- 18 files changed, 180 insertions(+), 222 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/LoggerUtil.java diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java index 7d0e85f6..7b6bce9a 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java @@ -1,25 +1,53 @@ package com.xiaojukeji.know.streaming.km.collector.metric; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.service.CollectThreadPoolService; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BaseMetricEvent; import com.xiaojukeji.know.streaming.km.common.component.SpringTool; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + /** * @author didi */ public abstract class AbstractMetricCollector { - public abstract void collectMetrics(ClusterPhy clusterPhy); + protected static final ILog LOGGER = LogFactory.getLog(AbstractMetricCollector.class); + + protected static final ILog METRIC_COLLECTED_LOGGER = LoggerUtil.getMetricCollectedLogger(); + + public abstract List collectKafkaMetrics(ClusterPhy clusterPhy); public abstract VersionItemTypeEnum collectorType(); @Autowired private CollectThreadPoolService collectThreadPoolService; + public void collectMetrics(ClusterPhy clusterPhy) { + long startTime = System.currentTimeMillis(); + + // 采集指标 + List metricsList = this.collectKafkaMetrics(clusterPhy); + + // 输出耗时信息 + LOGGER.info( + "metricType={}||clusterPhyId={}||costTimeUnitMs={}", + this.collectorType().getMessage(), clusterPhy.getId(), System.currentTimeMillis() - startTime + ); + + // 输出采集到的指标信息 + METRIC_COLLECTED_LOGGER.debug("metricType={}||clusterPhyId={}||metrics={}!", + this.collectorType().getMessage(), clusterPhy.getId(), ConvertUtil.obj2Json(metricsList) + ); + } + protected FutureWaitUtil getFutureUtilByClusterPhyId(Long clusterPhyId) { return collectThreadPoolService.selectSuitableFutureUtil(clusterPhyId * 1000L + this.collectorType().getCode()); } diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java index 18f36192..1753a875 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java @@ -1,6 +1,5 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; -import com.alibaba.fastjson.JSON; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; @@ -12,7 +11,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionContro import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BrokerMetricEvent; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; -import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerMetricService; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; @@ -30,7 +28,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT */ @Component public class BrokerMetricCollector extends AbstractMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(BrokerMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -42,8 +40,7 @@ public class BrokerMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List brokers = brokerService.listAliveBrokersFromDB(clusterPhy.getId()); @@ -51,23 +48,23 @@ public class BrokerMetricCollector extends AbstractMetricCollector future = this.getFutureUtilByClusterPhyId(clusterPhyId); - List brokerMetrics = new ArrayList<>(); + List metricsList = new ArrayList<>(); for(Broker broker : brokers) { BrokerMetrics metrics = new BrokerMetrics(clusterPhyId, broker.getBrokerId(), broker.getHost(), broker.getPort()); - brokerMetrics.add(metrics); + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); + metricsList.add(metrics); future.runnableTask( - String.format("method=BrokerMetricCollector||clusterPhyId=%d||brokerId=%d", clusterPhyId, broker.getBrokerId()), + String.format("class=BrokerMetricCollector||clusterPhyId=%d||brokerId=%d", clusterPhyId, broker.getBrokerId()), 30000, () -> collectMetrics(clusterPhyId, metrics, items) ); } future.waitExecute(30000); - this.publishMetric(new BrokerMetricEvent(this, brokerMetrics)); + this.publishMetric(new BrokerMetricEvent(this, metricsList)); - LOGGER.info("method=BrokerMetricCollector||clusterPhyId={}||startTime={}||costTime={}||msg=collect finished.", - clusterPhyId, startTime, System.currentTimeMillis() - startTime); + return metricsList; } @Override @@ -79,7 +76,6 @@ public class BrokerMetricCollector extends AbstractMetricCollector items) { long startTime = System.currentTimeMillis(); - metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); for(VersionControlItem v : items) { try { @@ -93,14 +89,11 @@ public class BrokerMetricCollector extends AbstractMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); +public class ClusterMetricCollector extends AbstractMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(ClusterMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -38,35 +35,37 @@ public class ClusterMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long startTime = System.currentTimeMillis(); Long clusterPhyId = clusterPhy.getId(); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); ClusterMetrics metrics = new ClusterMetrics(clusterPhyId, clusterPhy.getKafkaVersion()); + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); for(VersionControlItem v : items) { future.runnableTask( - String.format("method=ClusterMetricCollector||clusterPhyId=%d||metricName=%s", clusterPhyId, v.getName()), + String.format("class=ClusterMetricCollector||clusterPhyId=%d", clusterPhyId), 30000, () -> { try { - if(null != metrics.getMetrics().get(v.getName())){return null;} + if(null != metrics.getMetrics().get(v.getName())){ + return null; + } Result ret = clusterMetricService.collectClusterMetricsFromKafka(clusterPhyId, v.getName()); - if(null == ret || ret.failed() || null == ret.getData()){return null;} + if(null == ret || ret.failed() || null == ret.getData()){ + return null; + } metrics.putMetric(ret.getData().getMetrics()); - - if(!EnvUtil.isOnline()){ - LOGGER.info("method=ClusterMetricCollector||clusterPhyId={}||metricName={}||metricValue={}", - clusterPhyId, v.getName(), ConvertUtil.obj2Json(ret.getData().getMetrics())); - } } catch (Exception e){ - LOGGER.error("method=ClusterMetricCollector||clusterPhyId={}||metricName={}||errMsg=exception!", - clusterPhyId, v.getName(), e); + LOGGER.error( + "method=collectKafkaMetrics||clusterPhyId={}||metricName={}||errMsg=exception!", + clusterPhyId, v.getName(), e + ); } return null; @@ -77,10 +76,9 @@ public class ClusterMetricCollector extends AbstractMetricCollector> { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); +public class GroupMetricCollector extends AbstractMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(GroupMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -46,40 +41,38 @@ public class GroupMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); - List groups = new ArrayList<>(); + List groupNameList = new ArrayList<>(); try { - groups = groupService.listGroupsFromKafka(clusterPhyId); + groupNameList = groupService.listGroupsFromKafka(clusterPhyId); } catch (Exception e) { - LOGGER.error("method=GroupMetricCollector||clusterPhyId={}||msg=exception!", clusterPhyId, e); + LOGGER.error("method=collectKafkaMetrics||clusterPhyId={}||msg=exception!", clusterPhyId, e); } - if(CollectionUtils.isEmpty(groups)){return;} + if(ValidateUtils.isEmptyList(groupNameList)) { + return Collections.emptyList(); + } List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); Map> metricsMap = new ConcurrentHashMap<>(); - for(String groupName : groups) { + for(String groupName : groupNameList) { future.runnableTask( - String.format("method=GroupMetricCollector||clusterPhyId=%d||groupName=%s", clusterPhyId, groupName), + String.format("class=GroupMetricCollector||clusterPhyId=%d||groupName=%s", clusterPhyId, groupName), 30000, () -> collectMetrics(clusterPhyId, groupName, metricsMap, items)); } future.waitResult(30000); - List metricsList = new ArrayList<>(); - metricsMap.values().forEach(elem -> metricsList.addAll(elem)); + List metricsList = metricsMap.values().stream().collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll); publishMetric(new GroupMetricEvent(this, metricsList)); - - LOGGER.info("method=GroupMetricCollector||clusterPhyId={}||startTime={}||cost={}||msg=collect finished.", - clusterPhyId, startTime, System.currentTimeMillis() - startTime); + return metricsList; } @Override @@ -92,9 +85,7 @@ public class GroupMetricCollector extends AbstractMetricCollector> metricsMap, List items) { long startTime = System.currentTimeMillis(); - List groupMetricsList = new ArrayList<>(); - - Map tpGroupPOMap = new HashMap<>(); + Map subMetricMap = new HashMap<>(); GroupMetrics groupMetrics = new GroupMetrics(clusterPhyId, groupName, true); groupMetrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); @@ -108,38 +99,31 @@ public class GroupMetricCollector extends AbstractMetricCollector { + ret.getData().forEach(metrics -> { if (metrics.isBGroupMetric()) { groupMetrics.putMetric(metrics.getMetrics()); - } else { - String topicName = metrics.getTopic(); - Integer partitionId = metrics.getPartitionId(); - String tpGroupKey = genTopicPartitionGroupKey(topicName, partitionId); - - tpGroupPOMap.putIfAbsent(tpGroupKey, new GroupMetrics(clusterPhyId, partitionId, topicName, groupName, false)); - tpGroupPOMap.get(tpGroupKey).putMetric(metrics.getMetrics()); + return; } - }); - if(!EnvUtil.isOnline()){ - LOGGER.info("method=GroupMetricCollector||clusterPhyId={}||groupName={}||metricName={}||metricValue={}", - clusterPhyId, groupName, metricName, JSON.toJSONString(ret.getData())); - } - }catch (Exception e){ - LOGGER.error("method=GroupMetricCollector||clusterPhyId={}||groupName={}||errMsg=exception!", clusterPhyId, groupName, e); + TopicPartition tp = new TopicPartition(metrics.getTopic(), metrics.getPartitionId()); + subMetricMap.putIfAbsent(tp, new GroupMetrics(clusterPhyId, metrics.getPartitionId(), metrics.getTopic(), groupName, false)); + subMetricMap.get(tp).putMetric(metrics.getMetrics()); + }); + } catch (Exception e) { + LOGGER.error( + "method=collectMetrics||clusterPhyId={}||groupName={}||errMsg=exception!", + clusterPhyId, groupName, e + ); } } - groupMetricsList.add(groupMetrics); - groupMetricsList.addAll(tpGroupPOMap.values()); + List metricsList = new ArrayList<>(); + metricsList.add(groupMetrics); + metricsList.addAll(subMetricMap.values()); // 记录采集性能 groupMetrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, (System.currentTimeMillis() - startTime) / 1000.0f); - metricsMap.put(groupName, groupMetricsList); - } - - private String genTopicPartitionGroupKey(String topic, Integer partitionId){ - return topic + "@" + partitionId; + metricsMap.put(groupName, metricsList); } } diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java index 0b5debfa..fbb710b9 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java @@ -10,8 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.Topic; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionControlItem; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.PartitionMetricEvent; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; -import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; -import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; @@ -29,7 +27,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT */ @Component public class PartitionMetricCollector extends AbstractMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + protected static final ILog LOGGER = LogFactory.getLog(PartitionMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -41,14 +39,11 @@ public class PartitionMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List topicList = topicService.listTopicsFromCacheFirst(clusterPhyId); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); - // 获取集群所有分区 - FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); Map> metricsMap = new ConcurrentHashMap<>(); @@ -56,9 +51,9 @@ public class PartitionMetricCollector extends AbstractMetricCollector()); future.runnableTask( - String.format("method=PartitionMetricCollector||clusterPhyId=%d||topicName=%s", clusterPhyId, topic.getTopicName()), + String.format("class=PartitionMetricCollector||clusterPhyId=%d||topicName=%s", clusterPhyId, topic.getTopicName()), 30000, - () -> collectMetrics(clusterPhyId, topic.getTopicName(), metricsMap.get(topic.getTopicName()), items) + () -> this.collectMetrics(clusterPhyId, topic.getTopicName(), metricsMap.get(topic.getTopicName()), items) ); } @@ -69,10 +64,7 @@ public class PartitionMetricCollector extends AbstractMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + protected static final ILog LOGGER = LogFactory.getLog(ReplicaMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -42,12 +40,10 @@ public class ReplicaMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); - - List partitions = partitionService.listPartitionByCluster(clusterPhyId); + List partitions = partitionService.listPartitionFromCacheFirst(clusterPhyId); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); @@ -55,10 +51,11 @@ public class ReplicaMetricCollector extends AbstractMetricCollector collectMetrics(clusterPhyId, metrics, items) @@ -70,8 +67,7 @@ public class ReplicaMetricCollector extends AbstractMetricCollector items) { long startTime = System.currentTimeMillis(); - metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); - for(VersionControlItem v : items) { try { if (metrics.getMetrics().containsKey(v.getName())) { @@ -105,15 +99,11 @@ public class ReplicaMetricCollector extends AbstractMetricCollector> { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); +public class TopicMetricCollector extends AbstractMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(TopicMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -47,8 +45,7 @@ public class TopicMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List topics = topicService.listTopicsFromCacheFirst(clusterPhyId); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); @@ -65,7 +62,7 @@ public class TopicMetricCollector extends AbstractMetricCollector collectMetrics(clusterPhyId, topic.getTopicName(), metricsMap, items) ); @@ -78,8 +75,7 @@ public class TopicMetricCollector extends AbstractMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); +public class ZookeeperMetricCollector extends AbstractMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(ZookeeperMetricCollector.class); @Autowired private VersionControlService versionControlService; @@ -52,7 +50,7 @@ public class ZookeeperMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long startTime = System.currentTimeMillis(); Long clusterPhyId = clusterPhy.getId(); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); @@ -62,11 +60,11 @@ public class ZookeeperMetricCollector extends AbstractMetricCollector ret = zookeeperMetricService.collectMetricsFromZookeeper(param); @@ -91,16 +90,9 @@ public class ZookeeperMetricCollector extends AbstractMetricCollector esExecutor = FutureUtil.init( + "MetricsESSender", 10, 20, - 6000, - TimeUnit.MILLISECONDS, - new LinkedBlockingDeque<>(1000), - new NamedThreadFactory("KM-Collect-MetricESSender-ES"), - (r, e) -> LOGGER.warn("class=MetricESSender||msg=KM-Collect-MetricESSender-ES Deque is blocked, taskCount:{}" + e.getTaskCount()) + 10000 ); /** * 根据不同监控维度来发送 */ - protected boolean send2es(String index, List statsList){ + protected boolean send2es(String index, List statsList) { + LOGGER.info("method=send2es||indexName={}||metricsSize={}||msg=send metrics to es", index, statsList.size()); + if (CollectionUtils.isEmpty(statsList)) { return true; } - if (!EnvUtil.isOnline()) { - LOGGER.info("class=MetricESSender||method=send2es||ariusStats={}||size={}", - index, statsList.size()); - } - BaseMetricESDAO baseMetricESDao = BaseMetricESDAO.getByStatsType(index); - if (Objects.isNull( baseMetricESDao )) { - LOGGER.error("class=MetricESSender||method=send2es||errMsg=fail to find {}", index); + if (Objects.isNull(baseMetricESDao)) { + LOGGER.error("method=send2es||indexName={}||errMsg=find dao failed", index); return false; } - int size = statsList.size(); - int num = (size) % THRESHOLD == 0 ? (size / THRESHOLD) : (size / THRESHOLD + 1); + for (int i = 0; i < statsList.size(); i += THRESHOLD) { + final int idxStart = i; - if (size < THRESHOLD) { - esExecutor.execute( - () -> baseMetricESDao.batchInsertStats(statsList) - ); - return true; - } - - for (int i = 1; i < num + 1; i++) { - int end = (i * THRESHOLD) > size ? size : (i * THRESHOLD); - int start = (i - 1) * THRESHOLD; - - esExecutor.execute( - () -> baseMetricESDao.batchInsertStats(statsList.subList(start, end)) + // 异步发送 + esExecutor.submitTask( + () -> baseMetricESDao.batchInsertStats(statsList.subList(idxStart, Math.min(idxStart + THRESHOLD, statsList.size()))) ); } diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java index 6708ba38..57d43ad8 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java @@ -14,11 +14,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.B @Component public class BrokerMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(BrokerMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=BrokerMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java index 94091748..478a27bd 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java @@ -15,11 +15,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.C @Component public class ClusterMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(ClusterMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=ClusterMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java index cd7a2242..e7e622c5 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java @@ -15,11 +15,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.G @Component public class GroupMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(GroupMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=GroupMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java index ce108835..460d5e92 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java @@ -14,11 +14,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.P @Component public class PartitionMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(PartitionMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=PartitionMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java index 76b2aa2a..9b39f3af 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java @@ -14,11 +14,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.R @Component public class ReplicaMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(ReplicaMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=GroupMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java index eebd82aa..311a26fa 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java @@ -15,11 +15,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.T @Component public class TopicMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(TopicMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=TopicMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java index 4f9dad53..f2f254d6 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java @@ -14,11 +14,11 @@ import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.Z @Component public class ZookeeperMetricESSender extends AbstractMetricESSender implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + private static final ILog LOGGER = LogFactory.getLog(ZookeeperMetricESSender.class); @PostConstruct public void init(){ - LOGGER.info("class=ZookeeperMetricESSender||method=init||msg=init finished"); + LOGGER.info("method=init||msg=init finished"); } @Override diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/LoggerUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/LoggerUtil.java new file mode 100644 index 00000000..d8de462e --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/LoggerUtil.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.utils; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; + +public class LoggerUtil { + private static final ILog MetricCollectedLogger = LogFactory.getLog("METRIC_COLLECTED_LOGGER"); + + private static final ILog ESLogger = LogFactory.getLog("ES_LOGGER"); + + public static ILog getMetricCollectedLogger() { + return MetricCollectedLogger; + } + + public static ILog getESLogger() { + return ESLogger; + } + + private LoggerUtil() { + } +} diff --git a/km-rest/src/main/resources/logback-spring.xml b/km-rest/src/main/resources/logback-spring.xml index 91c3af7f..09073029 100644 --- a/km-rest/src/main/resources/logback-spring.xml +++ b/km-rest/src/main/resources/logback-spring.xml @@ -149,14 +149,14 @@ - - ${log.path}/metric/metrics.log + + ${log.path}/metric/metric_collected.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 - ${log.path}/metric/metrics_%d{yyyy-MM-dd}.%i.log + ${log.path}/metric/metric_collected_%d{yyyy-MM-dd}.%i.log 100MB @@ -197,8 +197,8 @@ - - + + From fab41e892fc3ec70ecbcd562d510969b47d9c1eb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 2 Dec 2022 15:14:21 +0800 Subject: [PATCH 035/150] =?UTF-8?q?[Optimize]=E6=97=A5=E5=BF=97=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=A0=BC=E5=BC=8F&=E4=BC=98=E5=8C=96=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E5=86=85=E5=AE=B9-part3(#800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biz/topic/impl/TopicStateManagerImpl.java | 2 +- .../parser/ConfigCmdDataParser.java | 4 +-- .../parser/MonitorCmdDataParser.java | 4 +-- .../parser/ServerCmdDataParser.java | 4 +-- .../common/component/BaseExtendFactory.java | 4 +-- .../common/component/RestTemplateConfig.java | 16 ++++++------ .../km/common/component/RestTool.java | 4 +-- .../enums/version/VersionItemTypeEnum.java | 14 +++++----- .../km/common/utils/CommonUtils.java | 2 +- .../km/common/utils/ConvertUtil.java | 6 ++--- .../km/common/utils/FutureNoWaitUtil.java | 4 +-- .../km/common/utils/FutureWaitUtil.java | 6 ++--- .../common/utils/PaginationMetricsUtil.java | 6 ++--- .../km/common/utils/RetryExecutor.java | 4 +-- .../utils/zookeeper/FourLetterWordUtil.java | 8 +++--- .../km/core/flusher/ZKWatcherManager.java | 4 +-- .../broker/impl/BrokerMetricServiceImpl.java | 2 +- .../group/impl/GroupMetricServiceImpl.java | 2 +- .../version/BaseVersionControlService.java | 12 ++++----- .../km/persistence/es/BaseESDAO.java | 4 +-- .../persistence/es/dao/BaseMetricESDAO.java | 2 +- .../es/dao/ZookeeperMetricESDAO.java | 2 +- .../km/persistence/es/dsls/DslLoaderUtil.java | 26 +++++++++---------- .../km/persistence/jmx/impl/JmxDAOImpl.java | 2 +- .../interceptor/PermissionInterceptor.java | 2 +- .../AbstractAsyncCommonDispatchTask.java | 6 ++--- .../kafka/job/CommunityReassignJobTask.java | 2 +- .../AbstractAsyncMetadataDispatchTask.java | 6 ++--- .../kafka/metadata/SyncPartitionTask.java | 2 +- .../AbstractAsyncMetricsDispatchTask.java | 6 ++--- 30 files changed, 84 insertions(+), 84 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java index 4c4781da..e4b8a147 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java @@ -307,7 +307,7 @@ public class TopicStateManagerImpl implements TopicStateManager { if (metricsResult.failed()) { // 仅打印错误日志,但是不直接返回错误 log.error( - "class=TopicStateManagerImpl||method=getTopicPartitions||clusterPhyId={}||topicName={}||result={}||msg=get metrics from es failed", + "method=getTopicPartitions||clusterPhyId={}||topicName={}||result={}||msg=get metrics from es failed", clusterPhyId, topicName, metricsResult ); } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/ConfigCmdDataParser.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/ConfigCmdDataParser.java index 35ec153b..7f27073c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/ConfigCmdDataParser.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/ConfigCmdDataParser.java @@ -99,13 +99,13 @@ public class ConfigCmdDataParser implements FourLetterWordDataParser(null, getJsonContentHeaders(headers)), String.class); return JSON.parseObject(result.getBody(), resultType); } catch (Exception e){ - LOGGER.error("class=RestTool||method=getForObject||url={}||msg=exception!", url, e); + LOGGER.error("method=getForObject||url={}||msg=exception!", url, e); } return null; @@ -151,7 +151,7 @@ public class RestTool { new HttpEntity<>(null, headers), String.class); return JSON.parseObject(result.getBody(), resultType); } catch (Exception e){ - LOGGER.error("class=RestTool||method=getForObject||url={}||msg=exception!", url, e); + LOGGER.error("method=getForObject||url={}||msg=exception!", url, e); } return null; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java index 004dad6d..7136e114 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java @@ -4,14 +4,14 @@ public enum VersionItemTypeEnum { /** * 指标 */ - METRIC_TOPIC(100, "topic_metric"), - METRIC_CLUSTER(101, "cluster_metric"), - METRIC_GROUP(102, "group_metric"), - METRIC_BROKER(103, "broker_metric"), - METRIC_PARTITION(104, "partition_metric"), - METRIC_REPLICATION(105, "replication_metric"), + METRIC_TOPIC(100, "TopicMetric"), + METRIC_CLUSTER(101, "ClusterMetric"), + METRIC_GROUP(102, "GroupMetric"), + METRIC_BROKER(103, "BrokerMetric"), + METRIC_PARTITION(104, "PartitionMetric"), + METRIC_REPLICATION(105, "ReplicaMetric"), - METRIC_ZOOKEEPER(110, "zookeeper_metric"), + METRIC_ZOOKEEPER(110, "ZookeeperMetric"), /** * 服务端查询 diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java index f0a42192..1c451609 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java @@ -61,7 +61,7 @@ public class CommonUtils { //转换为16进制 return new BigInteger(1, digest).toString(16); } catch (Exception e) { - LOGGER.error("class=CommonUtils||method=getMD5||msg=获取文件的md5失败:{}", e.getMessage()); + LOGGER.error("method=getMD5||msg=获取文件的md5失败:{}", e.getMessage()); } return null; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/ConvertUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/ConvertUtil.java index 71b611fd..10e87ca8 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/ConvertUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/ConvertUtil.java @@ -210,7 +210,7 @@ public class ConvertUtil { BeanUtils.copyProperties(srcObj, tgt); consumer.accept(tgt); } catch (Exception e) { - LOGGER.warn("class=ConvertUtil||method=obj2Obj||msg={}", e.getMessage()); + LOGGER.warn("method=obj2Obj||msg={}", e.getMessage()); } return tgt; @@ -236,7 +236,7 @@ public class ConvertUtil { try { map.put(field.getName(), field.get(obj)); } catch (IllegalAccessException e) { - LOGGER.warn("class=ConvertUtil||method=Obj2Map||msg={}", e.getMessage(), e); + LOGGER.warn("method=Obj2Map||msg={}", e.getMessage(), e); } } return map; @@ -256,7 +256,7 @@ public class ConvertUtil { field.set(obj, map.get(field.getName())); } } catch (Exception e) { - LOGGER.warn("class=ConvertUtil||method=map2Obj||msg={}", e.getMessage(), e); + LOGGER.warn("method=map2Obj||msg={}", e.getMessage(), e); } return obj; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java index 378abaf6..5e7ca714 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureNoWaitUtil.java @@ -64,7 +64,7 @@ public class FutureNoWaitUtil { while (true) { FutureTaskDelayQueueData data = null; try { - LOGGER.debug("class=FutureNoWaitUtil||method=runCheck||delayQueueSize={}", delayQueueData.size()); + LOGGER.debug("method=runCheck||delayQueueSize={}", delayQueueData.size()); while (true) { data = delayQueueData.take(); @@ -81,7 +81,7 @@ public class FutureNoWaitUtil { // 停1000ms Thread.sleep(1000); } catch (Exception e) { - LOGGER.error("class=FutureNoWaitUtil||method=runCheck||taskName={}||errMsg=exception!", data == null? "": data.getTaskName(), e); + LOGGER.error("method=runCheck||taskName={}||errMsg=exception!", data == null? "": data.getTaskName(), e); } } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java index e62efb8b..28388358 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/FutureWaitUtil.java @@ -123,11 +123,11 @@ public class FutureWaitUtil { } // 达到超时时间,但是任务未完成,则打印日志并强制取消 - LOGGER.error("class=FutureUtil||method=waitExecute||taskName={}||msg=cancel task", queueData.getTaskName()); + LOGGER.error("method=waitExecute||taskName={}||msg=cancel task", queueData.getTaskName()); queueData.getFutureTask().cancel(true); } catch (Exception e) { - LOGGER.error("class=FutureUtil||method=waitExecute||msg=exception", e); + LOGGER.error("method=waitExecute||msg=exception", e); } } @@ -155,7 +155,7 @@ public class FutureWaitUtil { return queueData.getFutureTask().get(stepWaitTimeUnitMs, TimeUnit.MILLISECONDS); } catch (Exception e) { // 达到超时时间,但是任务未完成,则打印日志并强制取消 - LOGGER.error("class=FutureUtil||method=stepWaitResult||taskName={}||errMsg=exception", queueData.getTaskName(), e); + LOGGER.error("method=stepWaitResult||taskName={}||errMsg=exception", queueData.getTaskName(), e); } return null; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java index 9d732917..c7fcad77 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java @@ -163,7 +163,7 @@ public class PaginationMetricsUtil { try { Field defaultField = FieldUtils.getField(allDataList.get(0).getClass(), defaultSortField, true); if(ValidateUtils.anyNull(defaultField)) { - log.debug("class=PaginationMetricsUtil||method=sortMetrics||className={}||metricName={}||defaultFieldName={}||metricSortType={}||msg=default field not exist.", + log.debug("method=sortMetrics||className={}||metricName={}||defaultFieldName={}||metricSortType={}||msg=default field not exist.", allDataList.get(0).getClass().getSimpleName(), metricName, defaultSortField, sortType); // 字段不存在,则排序失效,直接返回 @@ -172,7 +172,7 @@ public class PaginationMetricsUtil { Collections.sort(allDataList, (a1, a2) -> sortMetricsObject(a1, a2, metricName, defaultField)); } catch (Exception e) { - log.debug("class=PaginationMetricsUtil||method=sortMetrics||className={}||metricName={}||defaultFieldName={}||metricSortType={}||errMsg=exception.", + log.debug("method=sortMetrics||className={}||metricName={}||defaultFieldName={}||metricSortType={}||errMsg=exception.", allDataList.get(0).getClass().getSimpleName(), metricName, defaultSortField, sortType, e); } @@ -214,7 +214,7 @@ public class PaginationMetricsUtil { return 0; } catch (Exception e) { - log.debug("class=PaginationMetricsUtil||method=sortMetricsObject||metricsA={}||metricsB={}||metricName={}||defaultFieldName={}||errMsg=exception.", + log.debug("method=sortMetricsObject||metricsA={}||metricsB={}||metricName={}||defaultFieldName={}||errMsg=exception.", a1, a2, metricName, defaultField.getName(), e); } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/RetryExecutor.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/RetryExecutor.java index fcf675e4..8b68a460 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/RetryExecutor.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/RetryExecutor.java @@ -94,12 +94,12 @@ public class RetryExecutor { } } catch (Exception e) { if (!handler.needRetry(e) || tryCount == retryCount) { - LOGGER.warn("class=RetryExecutor||method=execute||errMsg={}||handlerName={}||tryCount={}", + LOGGER.warn("method=execute||errMsg={}||handlerName={}||tryCount={}", e.getMessage(), name, tryCount, e); throw e; } - LOGGER.warn("class=RetryExecutor||method=execute||errMsg={}||handlerName={}||tryCount={}||maxTryCount={}", + LOGGER.warn("method=execute||errMsg={}||handlerName={}||tryCount={}||maxTryCount={}", e.getMessage(), name, tryCount,retryCount); } } while (tryCount++ < retryCount); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/zookeeper/FourLetterWordUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/zookeeper/FourLetterWordUtil.java index a3ae31af..5c798b7c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/zookeeper/FourLetterWordUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/zookeeper/FourLetterWordUtil.java @@ -56,7 +56,7 @@ public class FourLetterWordUtil { return Result.buildSuc(dataParser.parseAndInitData(clusterPhyId, host, port, cmdData)); } catch (Exception e) { LOGGER.error( - "class=FourLetterWordUtil||method=executeFourLetterCmd||clusterPhyId={}||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", + "method=executeFourLetterCmd||clusterPhyId={}||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", clusterPhyId, host, port, dataParser.getCmd(), secure, timeout, e ); @@ -124,7 +124,7 @@ public class FourLetterWordUtil { outputStream.close(); } catch (IOException e) { LOGGER.error( - "class=FourLetterWordUtil||method=send4LetterWord||clusterPhyId={}||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", + "method=send4LetterWord||clusterPhyId={}||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", host, port, cmd, secure, timeout, e ); } @@ -135,7 +135,7 @@ public class FourLetterWordUtil { bufferedReader.close(); } catch (IOException e) { LOGGER.error( - "class=FourLetterWordUtil||method=send4LetterWord||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", + "method=send4LetterWord||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", host, port, cmd, secure, timeout, e ); } @@ -146,7 +146,7 @@ public class FourLetterWordUtil { socket.close(); } catch (IOException e) { LOGGER.error( - "class=FourLetterWordUtil||method=send4LetterWord||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", + "method=send4LetterWord||host={}||port={}||cmd={}||secure={}||timeout={}||errMsg=exception!", host, port, cmd, secure, timeout, e ); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/ZKWatcherManager.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/ZKWatcherManager.java index e2bd1e2c..0a8a865d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/ZKWatcherManager.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/ZKWatcherManager.java @@ -43,14 +43,14 @@ public class ZKWatcherManager extends AbstractClusterLoadedChangedHandler { try { FutureUtil.quickStartupFutureUtil.submitTask( () -> { - log.debug("class={}||method=scheduledTriggerFlush||clusterPhyId={}||msg=flush task start" + log.debug("runClass={}||method=scheduledTriggerFlush||clusterPhyId={}||msg=flush task start" , abstractZKWatcher.getClass().getSimpleName(), clusterPhy.getId()); long startTime = System.currentTimeMillis(); abstractZKWatcher.flush(clusterPhy); - log.info("class={}||method=scheduledTriggerFlush||clusterPhyId={}||costTime={}ms||msg=flush task finished" + log.info("runClass={}||method=scheduledTriggerFlush||clusterPhyId={}||costTime={}ms||msg=flush task finished" , abstractZKWatcher.getClass().getSimpleName(), clusterPhy.getId(), System.currentTimeMillis() - startTime); }); } catch (Exception e) { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java index 50bf69d4..52a90ad2 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java @@ -58,7 +58,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. */ @Service public class BrokerMetricServiceImpl extends BaseMetricService implements BrokerMetricService { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + protected static final ILog LOGGER = LogFactory.getLog(BrokerMetricServiceImpl.class); public static final String BROKER_METHOD_DO_NOTHING = "doNothing"; public static final String BROKER_METHOD_GET_METRIC_FROM_KAFKA_BY_JMX = "getMetricFromKafkaByJMX"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java index 5112d57f..0299a67f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java @@ -221,7 +221,7 @@ public class GroupMetricServiceImpl extends BaseMetricService implements GroupMe return Result.buildSuc(metricsList); } catch (Exception e) { - LOGGER.error("class=GroupMetricServiceImpl||method=getLagFromAdminClient||clusterPhyId={}||groupName={}||metrics={}||msg=exception", clusterId, groupName, metric, e); + LOGGER.error("method=getLagFromAdminClient||clusterPhyId={}||groupName={}||metrics={}||msg=exception", clusterId, groupName, metric, e); return Result.buildFailure(VC_KAFKA_CLIENT_ERROR); } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java index 08628498..cb35befd 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java @@ -10,7 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMethod import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; -import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.util.CollectionUtils; @@ -25,7 +24,7 @@ import java.util.function.Function; */ @DependsOn("versionControlService") public abstract class BaseVersionControlService { - protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER"); + protected static final ILog LOGGER = LogFactory.getLog(BaseVersionControlService.class); @Autowired protected VersionControlService versionControlService; @@ -61,10 +60,11 @@ public abstract class BaseVersionControlService { String methodName = getMethodName(clusterPhyId, action); Object ret = versionControlService.doHandler(getVersionItemType(), methodName, param); - if(!EnvUtil.isOnline()){ - LOGGER.info("method=doVCHandler||clusterId={}||action={}||methodName={}||type={}param={}||ret={}}!", - clusterPhyId, action, methodName, getVersionItemType().getMessage(), JSON.toJSONString(param), JSON.toJSONString(ret)); - } + LOGGER.debug( + "method=doVCHandler||clusterId={}||action={}||methodName={}||type={}param={}||ret={}!", + clusterPhyId, action, methodName, getVersionItemType().getMessage(), JSON.toJSONString(param), JSON.toJSONString(ret) + ); + return ret; } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/BaseESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/BaseESDAO.java index 62bc6a57..48949f65 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/BaseESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/BaseESDAO.java @@ -1,7 +1,7 @@ package com.xiaojukeji.know.streaming.km.persistence.es; import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslLoaderUtil; import org.springframework.beans.factory.annotation.Autowired; @@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired; * 直接操作es集群的dao */ public abstract class BaseESDAO { - protected static final ILog LOGGER = LogFactory.getLog("ES_LOGGER"); + protected static final ILog LOGGER = LoggerUtil.getESLogger(); /** * 加载查询语句工具类 diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java index 39c9ef44..568ac34e 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java @@ -65,7 +65,7 @@ public class BaseMetricESDAO extends BaseESDAO { esOpClient.createIndex(realIndex); } - }catch (Exception e){ + } catch (Exception e) { LOGGER.error("method=checkCurrentDayIndexExist||errMsg=exception!", e); } } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java index 8b391a3a..dc19d2b8 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java @@ -59,7 +59,7 @@ public class ZookeeperMetricESDAO extends BaseMetricESDAO { ESConstant.DEFAULT_RETRY_TIME ); } catch (Exception e){ - LOGGER.error("class=ZookeeperMetricESDAO||method=listMetricsByClusterPhyId||clusterPhyId={}||errMsg=exception!", + LOGGER.error("method=listMetricsByClusterPhyId||clusterPhyId={}||errMsg=exception!", clusterPhyId, e ); } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java index fda0238f..6bdea995 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java @@ -7,10 +7,10 @@ import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -34,7 +34,7 @@ import java.util.Map; */ @Component public class DslLoaderUtil { - private static final ILog LOGGER = LogFactory.getLog("ES_LOGGER"); + private static final ILog LOGGER = LoggerUtil.getESLogger(); /** * 查询语句容器 */ @@ -42,7 +42,7 @@ public class DslLoaderUtil { @PostConstruct public void init() { - LOGGER.info("class=DslLoaderUtil||method=init||DslLoaderUtil init start."); + LOGGER.info("method=init||DslLoaderUtil init start."); List dslFileNames = Lists.newLinkedList(); // 反射获取接口中定义的变量中的值 @@ -52,7 +52,7 @@ public class DslLoaderUtil { try { dslFileNames.add(fields[i].get(null).toString()); } catch (IllegalAccessException e) { - LOGGER.error("class=DslLoaderUtil||method=init||errMsg=fail to read {} error. ", fields[i].getName(), + LOGGER.error("method=init||errMsg=fail to read {} error. ", fields[i].getName(), e); } } @@ -63,13 +63,13 @@ public class DslLoaderUtil { } // 输出加载的查询语句 - LOGGER.info("class=DslLoaderUtil||method=init||msg=dsl files count {}", dslsMap.size()); + LOGGER.info("method=init||msg=dsl files count {}", dslsMap.size()); for (Map.Entry entry : dslsMap.entrySet()) { - LOGGER.info("class=DslLoaderUtil||method=init||msg=file name {}, dsl content {}", entry.getKey(), + LOGGER.info("method=init||msg=file name {}, dsl content {}", entry.getKey(), entry.getValue()); } - LOGGER.info("class=DslLoaderUtil||method=init||DslLoaderUtil init finished."); + LOGGER.info("method=init||DslLoaderUtil init finished."); } /** @@ -93,7 +93,7 @@ public class DslLoaderUtil { String loadDslContent = getDslByFileName(fileName); if (StringUtils.isBlank(loadDslContent)) { - LOGGER.error("class=DslLoaderUtil||method=getFormatDslByFileName||errMsg=dsl file {} content is empty", + LOGGER.error("method=getFormatDslByFileName||errMsg=dsl file {} content is empty", fileName); return ""; } @@ -102,7 +102,7 @@ public class DslLoaderUtil { String dsl = trimJsonBank( String.format(loadDslContent, args)); // 如果不是线上环境,则输出dsl语句 if (!EnvUtil.isOnline()) { - LOGGER.info("class=DslLoaderUtil||method=getFormatDslByFileName||dsl={}", dsl); + LOGGER.info("method=getFormatDslByFileName||dsl={}", dsl); } return dsl; @@ -164,7 +164,7 @@ public class DslLoaderUtil { JSON.DEFAULT_PARSER_FEATURE | Feature.OrderedField.getMask()); obj = parser.parse(); } catch (Exception t) { - LOGGER.error("class=DslLoaderUtil||method=trimJsonBank||errMsg=parse json {} error. ", dsl, t); + LOGGER.error("method=trimJsonBank||errMsg=parse json {} error. ", dsl, t); } if (obj == null) { break; @@ -212,7 +212,7 @@ public class DslLoaderUtil { return StringUtils.join(lines, ""); } catch (IOException e) { - LOGGER.error("class=DslLoaderUtil||method=readDslFileInJarFile||errMsg=read file {} error. ", fileName, + LOGGER.error("method=readDslFileInJarFile||errMsg=read file {} error. ", fileName, e); return ""; @@ -221,12 +221,12 @@ public class DslLoaderUtil { inputStream.close(); } catch (IOException e) { LOGGER.error( - "class=DslLoaderUtil||method=readDslFileInJarFile||errMsg=fail to close file {} error. ", + "method=readDslFileInJarFile||errMsg=fail to close file {} error. ", fileName, e); } } } else { - LOGGER.error("class=DslLoaderUtil||method=readDslFileInJarFile||errMsg=fail to read file {} content", + LOGGER.error("method=readDslFileInJarFile||errMsg=fail to read file {} content", fileName); return ""; } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java index 77eb3252..e95d3c43 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java @@ -65,7 +65,7 @@ public class JmxDAOImpl implements JmxDAO { return object == null? null: (Long) object; } catch (Exception e) { log.error( - "class=JmxDAOImpl||method=getServerStartTime||clusterPhyId={}||jmxHost={}||jmxPort={}||jmxConfig={}||errMsg=exception!", + "method=getServerStartTime||clusterPhyId={}||jmxHost={}||jmxPort={}||jmxConfig={}||errMsg=exception!", clusterPhyId, jmxHost, jmxPort, jmxConfig, e ); } diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/interceptor/PermissionInterceptor.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/interceptor/PermissionInterceptor.java index e522d062..034eaa58 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/interceptor/PermissionInterceptor.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/interceptor/PermissionInterceptor.java @@ -63,7 +63,7 @@ public class PermissionInterceptor implements HandlerInterceptor { classRequestMappingValue = getClassRequestMappingValue(handler); } catch (Exception e) { LOGGER.error( - "class=PermissionInterceptor||method=preHandle||uri={}||msg=parse class request-mapping failed", + "method=preHandle||uri={}||msg=parse class request-mapping failed", request.getRequestURI(), e); } diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java index 5a41aadc..6f4b461a 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/AbstractAsyncCommonDispatchTask.java @@ -31,12 +31,12 @@ public abstract class AbstractAsyncCommonDispatchTask extends AbstractClusterPhy try { TaskResult tr = this.processClusterTask(clusterPhy, triggerTimeUnitMs); if (TaskResult.SUCCESS_CODE != tr.getCode()) { - log.error("class=AbstractAsyncCommonDispatchTask||taskName={}||clusterPhyId={}||taskResult={}||msg=failed", this.taskName, clusterPhy.getId(), tr); + log.error("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||taskResult={}||msg=failed", this.taskName, clusterPhy.getId(), tr); } else { - log.debug("class=AbstractAsyncCommonDispatchTask||taskName={}||clusterPhyId={}||msg=success", this.taskName, clusterPhy.getId()); + log.debug("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||msg=success", this.taskName, clusterPhy.getId()); } } catch (Exception e) { - log.error("class=AbstractAsyncCommonDispatchTask||taskName={}||clusterPhyId={}||errMsg=exception", this.taskName, clusterPhy.getId(), e); + log.error("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||errMsg=exception", this.taskName, clusterPhy.getId(), e); } } ); diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java index 00dbd301..563aa73c 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/job/CommunityReassignJobTask.java @@ -35,7 +35,7 @@ public class CommunityReassignJobTask extends AbstractAsyncCommonDispatchTask { // 更新任务的状态 Result rv = reassignJobService.verifyAndUpdateStatue(jobId); if (rv != null && rv.failed()) { - log.error("class=CommunityReassignJobTask||method=processSubTask||jobId={}||result={}||msg=verify and update task status failed", jobId, rv); + log.error("method=processSubTask||jobId={}||result={}||msg=verify and update task status failed", jobId, rv); } // 更新同步进度信息 diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java index c2b8516d..4a2a9d37 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/AbstractAsyncMetadataDispatchTask.java @@ -32,12 +32,12 @@ public abstract class AbstractAsyncMetadataDispatchTask extends AbstractClusterP try { TaskResult tr = this.processClusterTask(clusterPhy, triggerTimeUnitMs); if (TaskResult.SUCCESS_CODE != tr.getCode()) { - log.error("class=AbstractAsyncMetadataDispatchTask||taskName={}||clusterPhyId={}||taskResult={}||msg=failed", this.taskName, clusterPhy.getId(), tr); + log.error("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||taskResult={}||msg=failed", this.taskName, clusterPhy.getId(), tr); } else { - log.debug("class=AbstractAsyncMetadataDispatchTask||taskName={}||clusterPhyId={}||msg=success", this.taskName, clusterPhy.getId()); + log.debug("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||msg=success", this.taskName, clusterPhy.getId()); } } catch (Exception e) { - log.error("class=AbstractAsyncMetadataDispatchTask||taskName={}||clusterPhyId={}||errMsg=exception", this.taskName, clusterPhy.getId(), e); + log.error("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||errMsg=exception", this.taskName, clusterPhy.getId(), e); } } ); diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java index c0d50e09..b1b6858b 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncPartitionTask.java @@ -49,7 +49,7 @@ public class SyncPartitionTask extends AbstractAsyncMetadataDispatchTask { try { partitionService.updatePartitions(clusterPhy.getId(), entry.getKey(), entry.getValue(), dbPartitionMap.getOrDefault(entry.getKey(), new ArrayList<>())); } catch (Exception e) { - log.error("class=SyncPartitionTask||method=processSubTask||clusterPhyId={}||topicName={}||errMsg=exception", clusterPhy.getId(), entry.getKey(), e); + log.error("method=processSubTask||clusterPhyId={}||topicName={}||errMsg=exception", clusterPhy.getId(), entry.getKey(), e); } } diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java index 1907e279..a97822ab 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/AbstractAsyncMetricsDispatchTask.java @@ -32,12 +32,12 @@ public abstract class AbstractAsyncMetricsDispatchTask extends AbstractClusterPh try { TaskResult tr = this.processClusterTask(clusterPhy, triggerTimeUnitMs); if (TaskResult.SUCCESS_CODE != tr.getCode()) { - log.error("class=AbstractAsyncMetricsDispatchTask||taskName={}||clusterPhyId={}||taskResult={}||msg=failed", this.taskName, clusterPhy.getId(), tr); + log.error("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||taskResult={}||msg=failed", this.taskName, clusterPhy.getId(), tr); } else { - log.debug("class=AbstractAsyncMetricsDispatchTask||taskName={}||clusterPhyId={}||msg=success", this.taskName, clusterPhy.getId()); + log.debug("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||msg=success", this.taskName, clusterPhy.getId()); } } catch (Exception e) { - log.error("class=AbstractAsyncMetricsDispatchTask||taskName={}||clusterPhyId={}||errMsg=exception", this.taskName, clusterPhy.getId(), e); + log.error("method=asyncProcessSubTask||taskName={}||clusterPhyId={}||errMsg=exception", this.taskName, clusterPhy.getId(), e); } } ); From 5833a8644cfe6b67d141fedcfa884692e2eb818a Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 2 Dec 2022 15:29:17 +0800 Subject: [PATCH 036/150] =?UTF-8?q?[Optimize]=E5=85=B3=E9=97=ADerrorLogger?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E9=99=A4=E6=97=A0=E7=94=A8=E8=BE=93=E5=87=BA?= =?UTF-8?q?(#801)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- km-rest/src/main/resources/logback-spring.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/km-rest/src/main/resources/logback-spring.xml b/km-rest/src/main/resources/logback-spring.xml index 09073029..477c60ef 100644 --- a/km-rest/src/main/resources/logback-spring.xml +++ b/km-rest/src/main/resources/logback-spring.xml @@ -209,6 +209,9 @@ + + + From e632c6c13f241c0646322cd5584cff0a8f5a9e04 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 2 Dec 2022 15:34:28 +0800 Subject: [PATCH 037/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96Sonar?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../streaming/km/common/bean/entity/metrics/BaseMetrics.java | 2 +- .../km/common/bean/event/metric/BrokerMetricEvent.java | 2 +- .../km/common/bean/event/metric/ClusterMetricEvent.java | 2 +- .../streaming/km/common/bean/event/metric/GroupMetricEvent.java | 2 +- .../km/common/bean/event/metric/PartitionMetricEvent.java | 2 +- .../km/common/bean/event/metric/ReplicaMetricEvent.java | 2 +- .../streaming/km/common/bean/event/metric/TopicMetricEvent.java | 2 +- .../km/common/bean/event/metric/ZookeeperMetricEvent.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/BaseMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/BaseMetrics.java index 2ab9af97..c6ce6351 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/BaseMetrics.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/BaseMetrics.java @@ -36,7 +36,7 @@ public abstract class BaseMetrics implements Serializable { return metrics.get(key); } - public BaseMetrics(Long clusterPhyId){ + protected BaseMetrics(Long clusterPhyId) { this.clusterPhyId = clusterPhyId; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/BrokerMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/BrokerMetricEvent.java index 150e7b7a..afba5938 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/BrokerMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/BrokerMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class BrokerMetricEvent extends BaseMetricEvent{ - private List brokerMetrics; + private final List brokerMetrics; public BrokerMetricEvent(Object source, List brokerMetrics) { super( source ); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ClusterMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ClusterMetricEvent.java index e4a16c99..aaf38896 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ClusterMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ClusterMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class ClusterMetricEvent extends BaseMetricEvent{ - private List clusterMetrics; + private final List clusterMetrics; public ClusterMetricEvent(Object source, List clusterMetrics) { super( source ); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/GroupMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/GroupMetricEvent.java index 600daa46..24ce93ca 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/GroupMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/GroupMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class GroupMetricEvent extends BaseMetricEvent{ - private List groupMetrics; + private final List groupMetrics; public GroupMetricEvent(Object source, List groupMetrics) { super( source ); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/PartitionMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/PartitionMetricEvent.java index a2654ea5..0bc09b38 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/PartitionMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/PartitionMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class PartitionMetricEvent extends BaseMetricEvent{ - private List partitionMetrics; + private final List partitionMetrics; public PartitionMetricEvent(Object source, List partitionMetrics) { super( source ); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java index bc4f0c10..9b71a69b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class ReplicaMetricEvent extends BaseMetricEvent{ - private List replicationMetrics; + private final List replicationMetrics; public ReplicaMetricEvent(Object source, List replicationMetrics) { super( source ); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/TopicMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/TopicMetricEvent.java index 299afe01..359ef2a0 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/TopicMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/TopicMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class TopicMetricEvent extends BaseMetricEvent{ - private List topicMetrics; + private final List topicMetrics; public TopicMetricEvent(Object source, List topicMetrics) { super( source ); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ZookeeperMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ZookeeperMetricEvent.java index 19279d53..261440d6 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ZookeeperMetricEvent.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ZookeeperMetricEvent.java @@ -11,7 +11,7 @@ import java.util.List; @Getter public class ZookeeperMetricEvent extends BaseMetricEvent { - private List zookeeperMetrics; + private final List zookeeperMetrics; public ZookeeperMetricEvent(Object source, List zookeeperMetrics) { super( source ); From 921161d6d0c92d08d54ecd81f8b7a258bb1dcfbf Mon Sep 17 00:00:00 2001 From: zengqiao Date: Sat, 3 Dec 2022 14:33:28 +0800 Subject: [PATCH 038/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DReplicaMetric?= =?UTF-8?q?Collector=E7=BC=96=E8=AF=91=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98?= =?UTF-8?q?(#802)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/collector/metric/kafka/ReplicaMetricCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java index c042ae1d..4e79d98e 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java @@ -43,7 +43,7 @@ public class ReplicaMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); - List partitions = partitionService.listPartitionFromCacheFirst(clusterPhyId); + List partitions = partitionService.listPartitionByCluster(clusterPhyId); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); From 2c82baf9fcf5d654441162e1f9439cdb17d0ae9c Mon Sep 17 00:00:00 2001 From: zengqiao Date: Sun, 4 Dec 2022 15:31:46 +0800 Subject: [PATCH 039/150] =?UTF-8?q?[Optimize]=E6=8C=87=E6=A0=87=E9=87=87?= =?UTF-8?q?=E9=9B=86=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96-part1(#726)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/biz/group/impl/GroupManagerImpl.java | 12 +- .../biz/topic/impl/TopicStateManagerImpl.java | 6 +- .../metric/kafka/ClusterMetricCollector.java | 2 +- .../metric/kafka/ReplicaMetricCollector.java | 2 +- .../bean/entity/offset/KSOffsetSpec.java | 50 +++ .../param/partition/PartitionOffsetParam.java | 40 +- .../common/bean/po/partition/PartitionPO.java | 29 ++ .../km/common/constant/KafkaConstant.java | 2 +- .../km/core/cache/DataBaseDataLocalCache.java | 43 ++ .../km/core/flusher/DatabaseDataFlusher.java | 84 ++++ .../broker/impl/BrokerMetricServiceImpl.java | 10 +- .../impl/ClusterMetricServiceImpl.java | 86 ++-- .../group/impl/GroupMetricServiceImpl.java | 43 +- .../partition/PartitionMetricService.java | 1 - .../service/partition/PartitionService.java | 54 ++- .../impl/PartitionMetricServiceImpl.java | 76 ++-- .../partition/impl/PartitionServiceImpl.java | 370 ++++++++++++------ .../metrics/ReplicaMetricCollectorTask.java | 32 -- 18 files changed, 616 insertions(+), 326 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/offset/KSOffsetSpec.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java delete mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java index a77b7c39..7d8c81ff 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java @@ -12,6 +12,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.group.Group; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.GroupTopic; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.GroupTopicMember; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; 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.topic.Topic; @@ -42,7 +43,6 @@ import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.Group import com.xiaojukeji.know.streaming.km.persistence.es.dao.GroupMetricESDAO; import org.apache.kafka.clients.admin.ConsumerGroupDescription; import org.apache.kafka.clients.admin.MemberDescription; -import org.apache.kafka.clients.admin.OffsetSpec; import org.apache.kafka.common.ConsumerGroupState; import org.apache.kafka.common.TopicPartition; import org.springframework.beans.factory.annotation.Autowired; @@ -274,16 +274,16 @@ public class GroupManagerImpl implements GroupManager { ))); } - OffsetSpec offsetSpec = null; + KSOffsetSpec offsetSpec = null; if (OffsetTypeEnum.PRECISE_TIMESTAMP.getResetType() == dto.getResetType()) { - offsetSpec = OffsetSpec.forTimestamp(dto.getTimestamp()); + offsetSpec = KSOffsetSpec.forTimestamp(dto.getTimestamp()); } else if (OffsetTypeEnum.EARLIEST.getResetType() == dto.getResetType()) { - offsetSpec = OffsetSpec.earliest(); + offsetSpec = KSOffsetSpec.earliest(); } else { - offsetSpec = OffsetSpec.latest(); + offsetSpec = KSOffsetSpec.latest(); } - return partitionService.getPartitionOffsetFromKafka(dto.getClusterId(), dto.getTopicName(), offsetSpec, dto.getTimestamp()); + return partitionService.getPartitionOffsetFromKafka(dto.getClusterId(), dto.getTopicName(), offsetSpec); } private List convert2GroupTopicOverviewVOList(List poList, List metricsList) { diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java index e4b8a147..cd970528 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicStateManagerImpl.java @@ -10,6 +10,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -46,7 +47,6 @@ import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.kafka.clients.admin.OffsetSpec; import org.apache.kafka.clients.consumer.*; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.config.TopicConfig; @@ -143,12 +143,12 @@ public class TopicStateManagerImpl implements TopicStateManager { } // 获取分区beginOffset - Result> beginOffsetsMapResult = partitionService.getPartitionOffsetFromKafka(clusterPhyId, topicName, dto.getFilterPartitionId(), OffsetSpec.earliest(), null); + Result> beginOffsetsMapResult = partitionService.getPartitionOffsetFromKafka(clusterPhyId, topicName, dto.getFilterPartitionId(), KSOffsetSpec.earliest()); if (beginOffsetsMapResult.failed()) { return Result.buildFromIgnoreData(beginOffsetsMapResult); } // 获取分区endOffset - Result> endOffsetsMapResult = partitionService.getPartitionOffsetFromKafka(clusterPhyId, topicName, dto.getFilterPartitionId(), OffsetSpec.latest(), null); + Result> endOffsetsMapResult = partitionService.getPartitionOffsetFromKafka(clusterPhyId, topicName, dto.getFilterPartitionId(), KSOffsetSpec.latest()); if (endOffsetsMapResult.failed()) { return Result.buildFromIgnoreData(endOffsetsMapResult); } diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java index 75ea6406..e70bf6f9 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java @@ -47,7 +47,7 @@ public class ClusterMetricCollector extends AbstractMetricCollector { try { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java index 4e79d98e..c042ae1d 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java @@ -43,7 +43,7 @@ public class ReplicaMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); - List partitions = partitionService.listPartitionByCluster(clusterPhyId); + List partitions = partitionService.listPartitionFromCacheFirst(clusterPhyId); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/offset/KSOffsetSpec.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/offset/KSOffsetSpec.java new file mode 100644 index 00000000..580e2f6f --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/offset/KSOffsetSpec.java @@ -0,0 +1,50 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.offset; + +import org.apache.kafka.clients.admin.OffsetSpec; + +/** + * @see OffsetSpec + */ +public class KSOffsetSpec { + public static class KSEarliestSpec extends KSOffsetSpec { } + + public static class KSLatestSpec extends KSOffsetSpec { } + + public static class KSTimestampSpec extends KSOffsetSpec { + private final long timestamp; + + public KSTimestampSpec(long timestamp) { + this.timestamp = timestamp; + } + + public long timestamp() { + return timestamp; + } + } + + /** + * Used to retrieve the latest offset of a partition + */ + public static KSOffsetSpec latest() { + return new KSOffsetSpec.KSLatestSpec(); + } + + /** + * Used to retrieve the earliest offset of a partition + */ + public static KSOffsetSpec earliest() { + return new KSOffsetSpec.KSEarliestSpec(); + } + + /** + * Used to retrieve the earliest offset whose timestamp is greater than + * or equal to the given timestamp in the corresponding partition + * @param timestamp in milliseconds + */ + public static KSOffsetSpec forTimestamp(long timestamp) { + return new KSOffsetSpec.KSTimestampSpec(timestamp); + } + + private KSOffsetSpec() { + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/partition/PartitionOffsetParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/partition/PartitionOffsetParam.java index 02907a6c..09f81812 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/partition/PartitionOffsetParam.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/partition/PartitionOffsetParam.java @@ -1,23 +1,39 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.param.partition; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam; -import lombok.Data; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; +import com.xiaojukeji.know.streaming.km.common.utils.Triple; +import lombok.Getter; import lombok.NoArgsConstructor; -import org.apache.kafka.clients.admin.OffsetSpec; import org.apache.kafka.common.TopicPartition; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; -@Data +@Getter @NoArgsConstructor -public class PartitionOffsetParam extends TopicParam { - private Map topicPartitionOffsets; +public class PartitionOffsetParam extends ClusterPhyParam { + private List>> offsetSpecList; - private Long timestamp; + public PartitionOffsetParam(Long clusterPhyId, String topicName, KSOffsetSpec ksOffsetSpec, List partitionList) { + super(clusterPhyId); + this.offsetSpecList = Collections.singletonList(new Triple<>(topicName, ksOffsetSpec, partitionList)); + } - public PartitionOffsetParam(Long clusterPhyId, String topicName, Map topicPartitionOffsets, Long timestamp) { - super(clusterPhyId, topicName); - this.topicPartitionOffsets = topicPartitionOffsets; - this.timestamp = timestamp; + public PartitionOffsetParam(Long clusterPhyId, String topicName, List specList, List partitionList) { + super(clusterPhyId); + this.offsetSpecList = new ArrayList<>(); + specList.forEach(elem -> offsetSpecList.add(new Triple<>(topicName, elem, partitionList))); + } + + public PartitionOffsetParam(Long clusterPhyId, KSOffsetSpec offsetSpec, List partitionList) { + super(clusterPhyId); + Map> tpMap = new HashMap<>(); + partitionList.forEach(elem -> { + tpMap.putIfAbsent(elem.topic(), new ArrayList<>()); + tpMap.get(elem.topic()).add(elem); + }); + + this.offsetSpecList = tpMap.entrySet().stream().map(elem -> new Triple<>(elem.getKey(), offsetSpec, elem.getValue())).collect(Collectors.toList()); } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/partition/PartitionPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/partition/PartitionPO.java index c54ea851..8740668c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/partition/PartitionPO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/partition/PartitionPO.java @@ -5,6 +5,8 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import lombok.Data; +import java.util.Objects; + @Data @TableName(Constant.MYSQL_TABLE_NAME_PREFIX + "partition") public class PartitionPO extends BasePO { @@ -37,4 +39,31 @@ public class PartitionPO extends BasePO { * AR */ private String assignReplicas; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + PartitionPO po = (PartitionPO) o; + return Objects.equals(clusterPhyId, po.clusterPhyId) + && Objects.equals(topicName, po.topicName) + && Objects.equals(partitionId, po.partitionId) + && Objects.equals(leaderBrokerId, po.leaderBrokerId) + && Objects.equals(inSyncReplicas, po.inSyncReplicas) + && Objects.equals(assignReplicas, po.assignReplicas); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), clusterPhyId, topicName, partitionId, leaderBrokerId, inSyncReplicas, assignReplicas); + } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java index 16fd7921..9ab0bad1 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java @@ -33,7 +33,7 @@ public class KafkaConstant { public static final Integer DATA_VERSION_ONE = 1; - public static final Integer ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS = 5000; + public static final Integer ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS = 10000; public static final Integer KAFKA_SASL_SCRAM_ITERATIONS = 8192; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java new file mode 100644 index 00000000..84e2363b --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java @@ -0,0 +1,43 @@ +package com.xiaojukeji.know.streaming.km.core.cache; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class DataBaseDataLocalCache { + private static final Cache clusterLatestMetricsCache = Caffeine.newBuilder() + .expireAfterWrite(180, TimeUnit.SECONDS) + .maximumSize(500) + .build(); + + private static final Cache>> partitionsCache = Caffeine.newBuilder() + .expireAfterWrite(60, TimeUnit.SECONDS) + .maximumSize(500) + .build(); + + public static ClusterMetrics getClusterLatestMetrics(Long clusterPhyId) { + return clusterLatestMetricsCache.getIfPresent(clusterPhyId); + } + + public static void putClusterLatestMetrics(Long clusterPhyId, ClusterMetrics metrics) { + clusterLatestMetricsCache.put(clusterPhyId, metrics); + } + + public static Map> getPartitions(Long clusterPhyId) { + return partitionsCache.getIfPresent(clusterPhyId); + } + + public static void putPartitions(Long clusterPhyId, Map> partitionMap) { + partitionsCache.put(clusterPhyId, partitionMap); + } + + /**************************************************** private method ****************************************************/ + + private DataBaseDataLocalCache() { + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java new file mode 100644 index 00000000..95519768 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java @@ -0,0 +1,84 @@ +package com.xiaojukeji.know.streaming.km.core.flusher; + +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.metrics.ClusterMetrics; +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.utils.FutureUtil; +import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +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.partition.PartitionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class DatabaseDataFlusher { + private static final ILog LOGGER = LogFactory.getLog(DatabaseDataFlusher.class); + + @Autowired + private ClusterPhyService clusterPhyService; + + @Autowired + private ClusterMetricService clusterMetricService; + + @Autowired + private PartitionService partitionService; + + @PostConstruct + public void init() { + this.flushPartitionsCache(); + + this.flushClusterLatestMetricsCache(); + } + + @Scheduled(cron="0 0/1 * * * ?") + public void flushPartitionsCache() { + for (ClusterPhy clusterPhy: clusterPhyService.listAllClusters()) { + FutureUtil.quickStartupFutureUtil.submitTask(() -> { + try { + // 更新缓存 + Map> newPartitionMap = new ConcurrentHashMap<>(); + + List partitionList = partitionService.listPartitionByCluster(clusterPhy.getId()); + partitionList.forEach(partition -> { + newPartitionMap.putIfAbsent(partition.getTopicName(), new ArrayList<>()); + newPartitionMap.get(partition.getTopicName()).add(partition); + }); + + DataBaseDataLocalCache.putPartitions(clusterPhy.getId(), newPartitionMap); + } catch (Exception e) { + LOGGER.error("method=flushPartitionsCache||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); + } + }); + } + } + + @Scheduled(cron = "0 0/1 * * * ?") + private void flushClusterLatestMetricsCache() { + for (ClusterPhy clusterPhy: clusterPhyService.listAllClusters()) { + FutureUtil.quickStartupFutureUtil.submitTask(() -> { + try { + Result metricsResult = clusterMetricService.getLatestMetricsFromES(clusterPhy.getId(), Collections.emptyList()); + if (metricsResult.hasData()) { + DataBaseDataLocalCache.putClusterLatestMetrics(clusterPhy.getId(), metricsResult.getData()); + return; + } + + LOGGER.error("method=flushClusterLatestMetricsCache||clusterPhyId={}||result={}||msg=failed", clusterPhy.getId(), metricsResult); + } catch (Exception e) { + LOGGER.error("method=flushClusterLatestMetricsCache||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); + } + + DataBaseDataLocalCache.putClusterLatestMetrics(clusterPhy.getId(), new ClusterMetrics(clusterPhy.getId())); + }); + } + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java index 52a90ad2..a1320b90 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java @@ -18,6 +18,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BrokerMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; @@ -51,7 +52,6 @@ import java.util.*; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum.*; /** * @author didi @@ -365,7 +365,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker Long clusterId = param.getClusterId(); Integer brokerId = param.getBrokerId(); - List partitions = partitionService.listPartitionByBroker(clusterId, brokerId); + List partitions = partitionService.listPartitionFromCacheFirst(clusterId, brokerId); Float logSizeSum = 0f; for(Partition p : partitions) { @@ -387,7 +387,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker logSizeSum += (replicaLogSize == null? 0.0f: replicaLogSize); } catch (Exception e) { LOGGER.error( - "class=BrokerMetricServiceImpl||method=getLogSize||clusterPhyId={}||brokerId={}||topicName={}||partitionId={}||metricName={}||errMsg=exception", + "method=getLogSize||clusterPhyId={}||brokerId={}||topicName={}||partitionId={}||metricName={}||errMsg=exception", clusterId, brokerId, p.getTopicName(), p.getPartitionId(), metric, e.getClass().getName() ); } @@ -432,7 +432,9 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker Float brokerLeaderCount = metricsResult.getData().getMetric( BrokerMetricVersionItems.BROKER_METRIC_LEADERS); - Integer globalLeaderCount = partitionService.getLeaderPartitionSizeByClusterId(clusterId); + Integer globalLeaderCount = (int) partitionService.listPartitionFromCacheFirst(clusterId) + .stream() + .filter(partition -> !partition.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)).count(); Integer globalBrokerCount = brokerService.listAllBrokersFromDB(clusterId).size(); if (globalLeaderCount <= 0 || globalBrokerCount <= 0) { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java index 8cbfc8b8..9d2a7f70 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java @@ -2,8 +2,6 @@ package com.xiaojukeji.know.streaming.km.core.service.cluster.impl; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.collect.Table; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricsClusterPhyDTO; @@ -13,6 +11,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.Kafka 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.TopicMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.ClusterMetricParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; @@ -24,6 +23,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ClusterMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; import com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterAuthTypeEnum; import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; @@ -33,11 +33,11 @@ import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; import com.xiaojukeji.know.streaming.km.common.utils.*; +import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.core.service.acl.KafkaAclService; 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.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.job.JobService; @@ -50,32 +50,28 @@ import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; import com.xiaojukeji.know.streaming.km.persistence.es.dao.ClusterMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; +import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.resource.ResourceType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import javax.annotation.PostConstruct; import javax.management.InstanceNotFoundException; import javax.management.ObjectName; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics.initWithMetrics; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; /** * @author didi */ -@Service("clusterMetricService") +@Service public class ClusterMetricServiceImpl extends BaseMetricService implements ClusterMetricService { private static final ILog LOGGER = LogFactory.getLog(ClusterMetricServiceImpl.class); @@ -153,22 +149,6 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust @Autowired private JobService jobService; - @Autowired - private ClusterPhyService clusterPhyService; - - private final Cache clusterLatestMetricsCache = Caffeine.newBuilder() - .expireAfterWrite(180, TimeUnit.SECONDS) - .maximumSize(1000) - .build(); - - @PostConstruct - @Scheduled(cron = "0 0/1 * * * ?") - private void flushClusterLatestMetricsCache() { - for (ClusterPhy clusterPhy: clusterPhyService.listAllClusters()) { - FutureUtil.quickStartupFutureUtil.submitTask(() -> this.updateCacheAndGetMetrics(clusterPhy.getId())); - } - } - @Override protected VersionItemTypeEnum getVersionItemType() { return VersionItemTypeEnum.METRIC_CLUSTER; @@ -283,7 +263,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust @Override public ClusterMetrics getLatestMetricsFromCache(Long clusterPhyId) { - ClusterMetrics metrics = clusterLatestMetricsCache.getIfPresent(clusterPhyId); + ClusterMetrics metrics = DataBaseDataLocalCache.getClusterLatestMetrics(clusterPhyId); if (metrics != null) { return metrics; } @@ -335,24 +315,6 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust /**************************************************** private method ****************************************************/ - private ClusterMetrics updateCacheAndGetMetrics(Long clusterPhyId) { - try { - Result metricsResult = this.getLatestMetricsFromES(clusterPhyId, Arrays.asList()); - if (metricsResult.hasData()) { - LOGGER.info("method=updateCacheAndGetMetrics||clusterPhyId={}||msg=success", clusterPhyId); - clusterLatestMetricsCache.put(clusterPhyId, metricsResult.getData()); - - return metricsResult.getData(); - } - } catch (Exception e) { - LOGGER.error("method=updateCacheAndGetMetrics||clusterPhyId={}||errMsg=exception!", clusterPhyId, e); - } - - ClusterMetrics clusterMetrics = new ClusterMetrics(clusterPhyId); - clusterLatestMetricsCache.put(clusterPhyId, clusterMetrics); - return clusterMetrics; - } - /** * doNothing */ @@ -382,9 +344,28 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust /** * 获取集群的 messageSize */ - private Result getMessageSize(VersionItemParam metricParam){ + private Result getMessageSize(VersionItemParam metricParam) { ClusterMetricParam param = (ClusterMetricParam)metricParam; - return getMetricFromKafkaByTotalTopics(param.getClusterId(), param.getMetric(), TOPIC_METRIC_MESSAGES); + + Result> beginOffsetMapResult = partitionService.getAllPartitionOffsetFromKafka(param.getClusterId(), KSOffsetSpec.earliest()); + + Result> endOffsetMapResult = partitionService.getAllPartitionOffsetFromKafka(param.getClusterId(), KSOffsetSpec.latest()); + if (endOffsetMapResult.failed() || beginOffsetMapResult.failed()) { + // 有一个失败,直接返回失败 + return Result.buildFromIgnoreData(endOffsetMapResult); + } + + long msgCount = 0; + for (Map.Entry entry: endOffsetMapResult.getData().entrySet()) { + Long beginOffset = beginOffsetMapResult.getData().get(entry.getKey()); + if (beginOffset == null) { + continue; + } + + msgCount += Math.max(0, entry.getValue() - beginOffset); + } + + return Result.buildSuc(initWithMetrics(param.getClusterId(), param.getMetric(), (float)msgCount)); } /** @@ -406,9 +387,9 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust private Result getPartitionSize(VersionItemParam metricParam){ ClusterMetricParam param = (ClusterMetricParam)metricParam; - String metric = param.getMetric(); - Long clusterId = param.getClusterId(); - Integer partitionNu = partitionService.getPartitionSizeByClusterId(clusterId); + String metric = param.getMetric(); + Long clusterId = param.getClusterId(); + Integer partitionNu = partitionService.listPartitionFromCacheFirst(clusterId).size(); return Result.buildSuc(initWithMetrics(clusterId, metric, partitionNu.floatValue())); } @@ -421,7 +402,10 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust String metric = param.getMetric(); Long clusterId = param.getClusterId(); - Integer noLeaders = partitionService.getNoLeaderPartitionSizeByClusterId(clusterId); + Integer noLeaders = (int) partitionService.listPartitionFromCacheFirst(clusterId) + .stream() + .filter(partition -> partition.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) + .count(); return Result.buildSuc(initWithMetrics(clusterId, metric, noLeaders.floatValue())); } @@ -747,7 +731,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust /** * 从所有的 Topic 的指标中加总聚合得到集群的指标 */ - private Result getMetricFromKafkaByTotalTopics(Long clusterId, String metric, String topicMetric){ + private Result getMetricFromKafkaByTotalTopics(Long clusterId, String metric, String topicMetric) { List topics = topicService.listTopicsFromCacheFirst(clusterId); float sumMetricValue = 0f; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java index 0299a67f..c9d65468 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupMetricServiceImpl.java @@ -6,6 +6,7 @@ import com.google.common.collect.Table; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricGroupPartitionDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.GroupTopic; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.GroupMetricParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -24,13 +25,11 @@ import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateSer 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; -import org.apache.kafka.clients.admin.OffsetSpec; import org.apache.kafka.common.TopicPartition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; -import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; @@ -192,31 +191,29 @@ public class GroupMetricServiceImpl extends BaseMetricService implements GroupMe metricsList.add(metrics); } - for (String topicName: groupOffsetMap.keySet().stream().map(elem -> elem.topic()).collect(Collectors.toSet())) { - Result> offsetMapResult = partitionService.getPartitionOffsetFromKafka(clusterId, topicName, OffsetSpec.latest(), null); - if (!offsetMapResult.hasData()) { - // 这个分区获取失败 + Result> offsetMapResult = partitionService.getPartitionOffsetFromKafka(clusterId, new ArrayList<>(groupOffsetMap.keySet()), KSOffsetSpec.latest()); + if (!offsetMapResult.hasData()) { + // 获取失败 + return Result.buildSuc(metricsList); + } + + for (Map.Entry entry: offsetMapResult.getData().entrySet()) { + // 组织 GROUP_METRIC_LOG_END_OFFSET 指标 + GroupMetrics metrics = new GroupMetrics(clusterId, entry.getKey().partition(), entry.getKey().topic(), groupName, false); + metrics.putMetric(GROUP_METRIC_LOG_END_OFFSET, entry.getValue().floatValue()); + metricsList.add(metrics); + + Long groupOffset = groupOffsetMap.get(entry.getKey()); + if (groupOffset == null) { + // 不存在,则直接跳过 continue; } - for (Map.Entry entry: offsetMapResult.getData().entrySet()) { - // 组织 GROUP_METRIC_LOG_END_OFFSET 指标 - GroupMetrics metrics = new GroupMetrics(clusterId, entry.getKey().partition(), entry.getKey().topic(), groupName, false); - metrics.putMetric(GROUP_METRIC_LOG_END_OFFSET, entry.getValue().floatValue()); - metricsList.add(metrics); + // 组织 GROUP_METRIC_LAG 指标 + GroupMetrics groupMetrics = new GroupMetrics(clusterId, entry.getKey().partition(), entry.getKey().topic(), groupName, false); + groupMetrics.putMetric(GROUP_METRIC_LAG, Math.max(0L, entry.getValue() - groupOffset) * 1.0f); - Long groupOffset = groupOffsetMap.get(entry.getKey()); - if (groupOffset == null) { - // 不存在,则直接跳过 - continue; - } - - // 组织 GROUP_METRIC_LAG 指标 - GroupMetrics groupMetrics = new GroupMetrics(clusterId, entry.getKey().partition(), entry.getKey().topic(), groupName, false); - groupMetrics.putMetric(GROUP_METRIC_LAG, Math.max(0L, entry.getValue() - groupOffset) * 1.0f); - - metricsList.add(groupMetrics); - } + metricsList.add(groupMetrics); } return Result.buildSuc(metricsList); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionMetricService.java index 6d4bd777..e48f3131 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionMetricService.java @@ -26,6 +26,5 @@ public interface PartitionMetricService { * 从ES获取指标 */ PartitionMetrics getLatestMetricsFromES(Long clusterPhyId, String topic, Integer brokerId, Integer partitionId, List metricNameList); - Result> getLatestMetricsFromES(Long clusterPhyId, String topicName, List metricNameList); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionService.java index ae68dccf..9b5b1e5a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/PartitionService.java @@ -1,10 +1,11 @@ package com.xiaojukeji.know.streaming.km.core.service.partition; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; 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.po.partition.PartitionPO; -import org.apache.kafka.clients.admin.OffsetSpec; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import org.apache.kafka.common.TopicPartition; import java.util.List; @@ -12,49 +13,40 @@ import java.util.Map; import java.util.Set; public interface PartitionService { + /** + * 从Kafka获取分区信息 + */ Result>> listPartitionsFromKafka(ClusterPhy clusterPhy); - Result> listPartitionsFromKafka(ClusterPhy clusterPhy, String topicName); + /** + * 从DB获取分区信息 + */ List listPartitionByCluster(Long clusterPhyId); List listPartitionPOByCluster(Long clusterPhyId); - - /** - * Topic下的分区列表 - */ List listPartitionByTopic(Long clusterPhyId, String topicName); - - - /** - * Broker下的分区列表 - */ - List listPartitionByBroker(Long clusterPhyId, Integer brokerId); - - /** - * 获取具体分区信息 - */ Partition getPartitionByTopicAndPartitionId(Long clusterPhyId, String topicName, Integer partitionId); - - - /**************************************************** 优先从缓存获取分区信息 ****************************************************/ - + + /** + * 优先从缓存获取分区信息,缓存中没有时,从DB获取分区信息 + */ + List listPartitionFromCacheFirst(Long clusterPhyId); + List listPartitionFromCacheFirst(Long clusterPhyId, Integer brokerId); List listPartitionFromCacheFirst(Long clusterPhyId, String topicName); Partition getPartitionFromCacheFirst(Long clusterPhyId, String topicName, Integer partitionId); /** - * 获取集群下分区数 + * 获取分区Offset信息 */ - Integer getPartitionSizeByClusterId(Long clusterPhyId); - - Integer getLeaderPartitionSizeByClusterId(Long clusterPhyId); - - Integer getNoLeaderPartitionSizeByClusterId(Long clusterPhyId); - - Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, OffsetSpec offsetSpec, Long timestamp); - - Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, Integer partitionId, OffsetSpec offsetSpec, Long timestamp); + Result> getAllPartitionOffsetFromKafka(Long clusterPhyId, KSOffsetSpec offsetSpec); + Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, KSOffsetSpec offsetSpec); + Result/*begin offset*/, Map/*end offset*/>> getPartitionBeginAndEndOffsetFromKafka(Long clusterPhyId, String topicName); + Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, Integer partitionId, KSOffsetSpec offsetSpec); + Result> getPartitionOffsetFromKafka(Long clusterPhyId, List tpList, KSOffsetSpec offsetSpec); + /** + * 修改分区信息 + */ int updatePartitions(Long clusterPhyId, String topicName, List kafkaPartitionList, List dbPartitionList); - void deletePartitionsIfNotIn(Long clusterPhyId, Set topicNameSet); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java index e6ce6a62..63e9bc36 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java @@ -3,6 +3,7 @@ package com.xiaojukeji.know.streaming.km.core.service.partition.impl; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.TopicMetricParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; @@ -15,6 +16,7 @@ import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistExcept import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil; 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.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.cache.CollectedMetricsLocalCache; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionMetricService; @@ -22,7 +24,6 @@ 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.PartitionMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; -import org.apache.kafka.clients.admin.OffsetSpec; import org.apache.kafka.common.TopicPartition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -176,50 +177,45 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par Map metricsMap = new HashMap<>(); - // begin offset 指标 - Result> beginOffsetMapResult = partitionService.getPartitionOffsetFromKafka(clusterPhyId, topicName, OffsetSpec.earliest(), null); - if (beginOffsetMapResult.hasData()) { - for (Map.Entry entry: beginOffsetMapResult.getData().entrySet()) { - Partition partition = partitionMap.get(entry.getKey().partition()); - PartitionMetrics metrics = metricsMap.getOrDefault( - entry.getKey().partition(), - new PartitionMetrics(clusterPhyId, topicName, partition != null? partition.getLeaderBrokerId(): KafkaConstant.NO_LEADER, entry.getKey().partition()) - ); - - metrics.putMetric(PARTITION_METRIC_LOG_START_OFFSET, entry.getValue().floatValue()); - metricsMap.put(entry.getKey().partition(), metrics); - } - } else { + // offset 指标 + Result, Map>> offsetResult = partitionService.getPartitionBeginAndEndOffsetFromKafka(clusterPhyId, topicName); + if (offsetResult.failed()) { LOGGER.warn( - "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||resultMsg={}||msg=get begin offset failed", - clusterPhyId, topicName, beginOffsetMapResult.getMessage() + "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||result={}||msg=get offset failed", + clusterPhyId, topicName, offsetResult ); + + return Result.buildFromIgnoreData(offsetResult); + } + + // begin offset 指标 + for (Map.Entry entry: offsetResult.getData().v1().entrySet()) { + Partition partition = partitionMap.get(entry.getKey().partition()); + PartitionMetrics metrics = metricsMap.getOrDefault( + entry.getKey().partition(), + new PartitionMetrics(clusterPhyId, topicName, partition != null? partition.getLeaderBrokerId(): KafkaConstant.NO_LEADER, entry.getKey().partition()) + ); + + metrics.putMetric(PARTITION_METRIC_LOG_START_OFFSET, entry.getValue().floatValue()); + metricsMap.put(entry.getKey().partition(), metrics); } // end offset 指标 - Result> endOffsetMapResult = partitionService.getPartitionOffsetFromKafka(clusterPhyId, topicName, OffsetSpec.latest(), null); - if (endOffsetMapResult.hasData()) { - for (Map.Entry entry: endOffsetMapResult.getData().entrySet()) { - Partition partition = partitionMap.get(entry.getKey().partition()); - PartitionMetrics metrics = metricsMap.getOrDefault( - entry.getKey().partition(), - new PartitionMetrics(clusterPhyId, topicName, partition != null? partition.getLeaderBrokerId(): KafkaConstant.NO_LEADER, entry.getKey().partition()) - ); - - metrics.putMetric(PARTITION_METRIC_LOG_END_OFFSET, entry.getValue().floatValue()); - metricsMap.put(entry.getKey().partition(), metrics); - } - } else { - LOGGER.warn( - "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||resultMsg={}||msg=get end offset failed", - clusterPhyId, topicName, endOffsetMapResult.getMessage() + for (Map.Entry entry: offsetResult.getData().v2().entrySet()) { + Partition partition = partitionMap.get(entry.getKey().partition()); + PartitionMetrics metrics = metricsMap.getOrDefault( + entry.getKey().partition(), + new PartitionMetrics(clusterPhyId, topicName, partition != null? partition.getLeaderBrokerId(): KafkaConstant.NO_LEADER, entry.getKey().partition()) ); + + metrics.putMetric(PARTITION_METRIC_LOG_END_OFFSET, entry.getValue().floatValue()); + metricsMap.put(entry.getKey().partition(), metrics); } // messages 指标 - if (endOffsetMapResult.hasData() && beginOffsetMapResult.hasData()) { - for (Map.Entry entry: endOffsetMapResult.getData().entrySet()) { - Long beginOffset = beginOffsetMapResult.getData().get(entry.getKey()); + if (!ValidateUtils.isEmptyMap(offsetResult.getData().v1()) && !ValidateUtils.isEmptyMap(offsetResult.getData().v2())) { + for (Map.Entry entry: offsetResult.getData().v2().entrySet()) { + Long beginOffset = offsetResult.getData().v1().get(entry.getKey()); if (beginOffset == null) { continue; } @@ -235,8 +231,8 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par } } else { LOGGER.warn( - "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||endResultMsg={}||beginResultMsg={}||msg=get messages failed", - clusterPhyId, topicName, endOffsetMapResult.getMessage(), beginOffsetMapResult.getMessage() + "method=getOffsetRelevantMetrics||clusterPhyId={}||topicName={}||offsetData={}||msg=get messages failed", + clusterPhyId, topicName, ConvertUtil.obj2Json(offsetResult.getData()) ); } @@ -283,7 +279,6 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par } catch (InstanceNotFoundException e) { // ignore - continue; } catch (Exception e) { LOGGER.error( "method=getMetricFromJmx||clusterPhyId={}||topicName={}||partitionId={}||leaderBrokerId={}||metricName={}||msg={}", @@ -326,7 +321,7 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par // 4、获取jmx指标 String value = jmxConnectorWrap.getAttribute(new ObjectName(jmxInfo.getJmxObjectName() + ",topic=" + topicName), jmxInfo.getJmxAttribute()).toString(); - Long leaderCount = partitionList.stream().filter(elem -> elem.getLeaderBrokerId().equals(partition.getLeaderBrokerId())).count(); + long leaderCount = partitionList.stream().filter(elem -> elem.getLeaderBrokerId().equals(partition.getLeaderBrokerId())).count(); if (leaderCount <= 0) { // leader已经切换走了 continue; @@ -338,7 +333,6 @@ public class PartitionMetricServiceImpl extends BaseMetricService implements Par } catch (InstanceNotFoundException e) { // ignore - continue; } catch (Exception e) { LOGGER.error( "method=getTopicAvgMetricFromJmx||clusterPhyId={}||topicName={}||partitionId={}||leaderBrokerId={}||metricName={}||msg={}", diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java index 62d24feb..a2094028 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java @@ -3,9 +3,8 @@ package com.xiaojukeji.know.streaming.km.core.service.partition.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.partition.PartitionOffsetParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; @@ -20,7 +19,10 @@ import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; +import com.xiaojukeji.know.streaming.km.common.utils.Triple; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.znode.brokers.PartitionMap; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.znode.brokers.PartitionState; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService; @@ -44,7 +46,6 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.time.Duration; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -55,7 +56,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT /** * @author didi */ -@Service("partitionService") +@Service public class PartitionServiceImpl extends BaseVersionControlService implements PartitionService { private static final ILog log = LogFactory.getLog(PartitionServiceImpl.class); @@ -78,15 +79,10 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P return SERVICE_OP_PARTITION; } - private final Cache> partitionsCache = Caffeine.newBuilder() - .expireAfterWrite(90, TimeUnit.SECONDS) - .maximumSize(1000) - .build(); - @PostConstruct private void init() { - registerVCHandler(PARTITION_OFFSET_GET, V_0_10_0_0, V_0_11_0_0, "getPartitionOffsetFromKafkaConsumerClient", this::getPartitionOffsetFromKafkaConsumerClient); - registerVCHandler(PARTITION_OFFSET_GET, V_0_11_0_0, V_MAX, "getPartitionOffsetFromKafkaAdminClient", this::getPartitionOffsetFromKafkaAdminClient); + registerVCHandler(PARTITION_OFFSET_GET, V_0_10_0_0, V_0_11_0_0, "batchGetPartitionOffsetFromKafkaConsumerClient", this::batchGetPartitionOffsetFromKafkaConsumerClient); + registerVCHandler(PARTITION_OFFSET_GET, V_0_11_0_0, V_MAX, "batchGetPartitionOffsetFromKafkaAdminClient", this::batchGetPartitionOffsetFromKafkaAdminClient); } @Override @@ -133,17 +129,32 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P } @Override - public List listPartitionFromCacheFirst(Long clusterPhyId, String topicName) { - String clusterPhyIdAndTopicKey = MsgConstant.getClusterTopicKey(clusterPhyId, topicName); - List partitionList = partitionsCache.getIfPresent(clusterPhyIdAndTopicKey); + public List listPartitionFromCacheFirst(Long clusterPhyId) { + Map> partitionMap = DataBaseDataLocalCache.getPartitions(clusterPhyId); - if (!ValidateUtils.isNull(partitionList)) { - return partitionList; + if (partitionMap != null) { + return partitionMap.values().stream().collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll); } - partitionList = this.listPartitionByTopic(clusterPhyId, topicName); - partitionsCache.put(clusterPhyIdAndTopicKey, partitionList); - return partitionList; + return this.listPartitionByCluster(clusterPhyId); + } + + @Override + public List listPartitionFromCacheFirst(Long clusterPhyId, Integer brokerId) { + List partitionList = this.listPartitionFromCacheFirst(clusterPhyId); + + return partitionList.stream().filter(elem -> elem.getAssignReplicaList().contains(brokerId)).collect(Collectors.toList()); + } + + @Override + public List listPartitionFromCacheFirst(Long clusterPhyId, String topicName) { + Map> partitionMap = DataBaseDataLocalCache.getPartitions(clusterPhyId); + + if (partitionMap != null) { + return partitionMap.getOrDefault(topicName, new ArrayList<>()); + } + + return this.listPartitionByTopic(clusterPhyId, topicName); } @Override @@ -162,16 +173,6 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P return null; } - @Override - public List listPartitionByBroker(Long clusterPhyId, Integer brokerId) { - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(PartitionPO::getClusterPhyId, clusterPhyId); - - List partitionList = this.convert2PartitionList(partitionDAO.selectList(lambdaQueryWrapper)); - - return partitionList.stream().filter(elem -> elem.getAssignReplicaList().contains(brokerId)).collect(Collectors.toList()); - } - @Override public Partition getPartitionByTopicAndPartitionId(Long clusterPhyId, String topicName, Integer partitionId) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); @@ -183,71 +184,122 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P } @Override - public Integer getPartitionSizeByClusterId(Long clusterPhyId) { - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(PartitionPO::getClusterPhyId, clusterPhyId); - - return partitionDAO.selectCount(lambdaQueryWrapper); - } - - @Override - public Integer getLeaderPartitionSizeByClusterId(Long clusterPhyId) { - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(PartitionPO::getClusterPhyId, clusterPhyId); - lambdaQueryWrapper.ne(PartitionPO::getLeaderBrokerId, -1); - - return partitionDAO.selectCount(lambdaQueryWrapper); - } - - @Override - public Integer getNoLeaderPartitionSizeByClusterId(Long clusterPhyId) { - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(PartitionPO::getClusterPhyId, clusterPhyId); - lambdaQueryWrapper.eq(PartitionPO::getLeaderBrokerId, -1); - - return partitionDAO.selectCount(lambdaQueryWrapper); - } - - @Override - public Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, OffsetSpec offsetSpec, Long timestamp) { - Map topicPartitionOffsets = new HashMap<>(); - - List partitionList = this.listPartitionByTopic(clusterPhyId, topicName); - if (partitionList == null || partitionList.isEmpty()) { - // Topic不存在 - return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getTopicNotExist(clusterPhyId, topicName)); - } - - partitionList.stream() + public Result> getAllPartitionOffsetFromKafka(Long clusterPhyId, KSOffsetSpec offsetSpec) { + List tpList = this.listPartitionFromCacheFirst(clusterPhyId).stream() .filter(item -> !item.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) - .forEach(elem -> topicPartitionOffsets.put(new TopicPartition(topicName, elem.getPartitionId()), offsetSpec)); - - if (topicPartitionOffsets.isEmpty()) { - // 所有分区no-leader - return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FAILED, MsgConstant.getPartitionNoLeader(clusterPhyId, topicName)); - } + .map(elem -> new TopicPartition(elem.getTopicName(), elem.getPartitionId())) + .collect(Collectors.toList()); try { - return (Result>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, topicName, topicPartitionOffsets, timestamp)); + Result>>> listResult = + (Result>>>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, offsetSpec, tpList)); + + return this.convert2OffsetMapResult(listResult); } catch (VCHandlerNotExistException e) { return Result.buildFailure(VC_HANDLE_NOT_EXIST); } } @Override - public Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, Integer partitionId, OffsetSpec offsetSpec, Long timestamp) { - if (partitionId == null) { - return this.getPartitionOffsetFromKafka(clusterPhyId, topicName, offsetSpec, timestamp); + public Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, KSOffsetSpec offsetSpec) { + List tpList = this.listPartitionFromCacheFirst(clusterPhyId, topicName).stream() + .filter(item -> !item.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) + .map(elem -> new TopicPartition(topicName, elem.getPartitionId())) + .collect(Collectors.toList()); + + if (tpList.isEmpty()) { + // 所有分区no-leader + return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FAILED, MsgConstant.getPartitionNoLeader(clusterPhyId, topicName)); } - Map topicPartitionOffsets = new HashMap<>(); - this.listPartitionByTopic(clusterPhyId, topicName) - .stream() - .filter(elem -> elem.getPartitionId().equals(partitionId)) - .forEach(elem -> topicPartitionOffsets.put(new TopicPartition(topicName, elem.getPartitionId()), offsetSpec)); + try { + Result>>> listResult = + (Result>>>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, topicName, offsetSpec, tpList)); + + return this.convert2OffsetMapResult(listResult); + } catch (VCHandlerNotExistException e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result, Map>> getPartitionBeginAndEndOffsetFromKafka(Long clusterPhyId, String topicName) { + List tpList = this.listPartitionFromCacheFirst(clusterPhyId, topicName).stream() + .filter(item -> !item.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) + .map(elem -> new TopicPartition(topicName, elem.getPartitionId())) + .collect(Collectors.toList()); + + if (tpList.isEmpty()) { + // 所有分区no-leader + return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FAILED, MsgConstant.getPartitionNoLeader(clusterPhyId, topicName)); + } try { - return (Result>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, topicName, topicPartitionOffsets, timestamp)); + Result>>> listResult = + (Result>>>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, topicName, Arrays.asList(KSOffsetSpec.earliest(), KSOffsetSpec.latest()), tpList)); + if (listResult.failed()) { + return Result.buildFromIgnoreData(listResult); + } else if (ValidateUtils.isEmptyList(listResult.getData())) { + return Result.buildSuc(new Tuple, Map>(new HashMap<>(0), new HashMap<>(0))); + } + + Tuple, Map> tuple = new Tuple<>(new HashMap<>(0), new HashMap<>(0)); + listResult.getData().forEach(elem -> { + if (elem.getV1() instanceof KSOffsetSpec.KSEarliestSpec) { + tuple.setV1(elem.v2()); + } else if (elem.v1() instanceof KSOffsetSpec.KSLatestSpec) { + tuple.setV2(elem.v2()); + } + }); + + return Result.buildSuc(tuple); + } catch (VCHandlerNotExistException e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result> getPartitionOffsetFromKafka(Long clusterPhyId, String topicName, Integer partitionId, KSOffsetSpec offsetSpec) { + if (partitionId == null) { + return this.getPartitionOffsetFromKafka(clusterPhyId, topicName, offsetSpec); + } + + List tpList = this.listPartitionFromCacheFirst(clusterPhyId, topicName).stream() + .filter(item -> !item.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) + .map(elem -> new TopicPartition(topicName, elem.getPartitionId())) + .collect(Collectors.toList()); + + try { + Result>>> listResult = + (Result>>>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, topicName, offsetSpec, tpList)); + + return this.convert2OffsetMapResult(listResult); + } catch (VCHandlerNotExistException e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result> getPartitionOffsetFromKafka(Long clusterPhyId, List tpList, KSOffsetSpec offsetSpec) { + // 集群具有leader的分区列表 + Set existLeaderTPSet = this.listPartitionFromCacheFirst(clusterPhyId).stream() + .filter(item -> !item.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) + .map(elem -> new TopicPartition(elem.getTopicName(), elem.getPartitionId())) + .collect(Collectors.toSet()); + + List existLeaderTPList = tpList.stream().filter(elem -> existLeaderTPSet.contains(elem)).collect(Collectors.toList()); + if (existLeaderTPList.isEmpty()) { + return Result.buildSuc(new HashMap<>(0)); + } + + try { + Result>>> listResult = (Result>>>) doVCHandler( + clusterPhyId, + PARTITION_OFFSET_GET, + new PartitionOffsetParam(clusterPhyId, offsetSpec, existLeaderTPList) + ); + + return this.convert2OffsetMapResult(listResult); } catch (VCHandlerNotExistException e) { return Result.buildFailure(VC_HANDLE_NOT_EXIST); } @@ -267,6 +319,10 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P } PartitionPO presentPartitionPO = this.convert2PartitionPO(partition); + if (presentPartitionPO.equals(dbPartitionPO)) { + // 数据一样,不进行DB操作 + continue; + } presentPartitionPO.setId(dbPartitionPO.getId()); partitionDAO.updateById(presentPartitionPO); } @@ -306,64 +362,137 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P /**************************************************** private method ****************************************************/ - - private Result> getPartitionOffsetFromKafkaAdminClient(VersionItemParam itemParam) { + private Result>>> batchGetPartitionOffsetFromKafkaAdminClient(VersionItemParam itemParam) { PartitionOffsetParam offsetParam = (PartitionOffsetParam) itemParam; + if (offsetParam.getOffsetSpecList().isEmpty()) { + return Result.buildSuc(Collections.emptyList()); + } + + List> resultList = new ArrayList<>(); + for (Triple> elem: offsetParam.getOffsetSpecList()) { + Result offsetsResult = this.getPartitionOffsetFromKafkaAdminClient( + offsetParam.getClusterPhyId(), + elem.v1(), + elem.v2(), + elem.v3() + ); + + if (offsetsResult.failed() && offsetParam.getOffsetSpecList().size() == 1) { + return Result.buildFromIgnoreData(offsetsResult); + } + + if (offsetsResult.hasData()) { + resultList.add(new Triple<>(elem.v1(), elem.v2(), offsetsResult.getData())); + } + } + + List>> offsetMapList = new ArrayList<>(); + for (Triple triple: resultList) { + try { + Map offsetMap = new HashMap<>(); + triple.v3().all().get().entrySet().stream().forEach(elem -> offsetMap.put(elem.getKey(), elem.getValue().offset())); + + offsetMapList.add(new Tuple<>(triple.v2(), offsetMap)); + } catch (Exception e) { + log.error( + "method=batchGetPartitionOffsetFromKafkaAdminClient||clusterPhyId={}||topicName={}||offsetSpec={}||errMsg=exception!", + offsetParam.getClusterPhyId(), triple.v1(), triple.v2(), e + ); + } + } + + return Result.buildSuc(offsetMapList); + } + + private Result getPartitionOffsetFromKafkaAdminClient(Long clusterPhyId, String topicName, KSOffsetSpec offsetSpec, List tpList) { try { - AdminClient adminClient = kafkaAdminClient.getClient(offsetParam.getClusterPhyId()); + AdminClient adminClient = kafkaAdminClient.getClient(clusterPhyId); - ListOffsetsResult listOffsetsResult = adminClient.listOffsets(offsetParam.getTopicPartitionOffsets(), new ListOffsetsOptions().timeoutMs(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS)); + Map kafkaOffsetSpecMap = new HashMap<>(tpList.size()); + tpList.forEach(elem -> { + if (offsetSpec instanceof KSOffsetSpec.KSEarliestSpec) { + kafkaOffsetSpecMap.put(elem, OffsetSpec.earliest()); + } else if (offsetSpec instanceof KSOffsetSpec.KSLatestSpec) { + kafkaOffsetSpecMap.put(elem, OffsetSpec.latest()); + } else if (offsetSpec instanceof KSOffsetSpec.KSTimestampSpec) { + kafkaOffsetSpecMap.put(elem, OffsetSpec.forTimestamp(((KSOffsetSpec.KSTimestampSpec) offsetSpec).timestamp())); + } + }); - Map offsetMap = new HashMap<>(); - listOffsetsResult.all().get().entrySet().stream().forEach(elem -> offsetMap.put(elem.getKey(), elem.getValue().offset())); + ListOffsetsResult listOffsetsResult = adminClient.listOffsets(kafkaOffsetSpecMap, new ListOffsetsOptions().timeoutMs(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS)); - return Result.buildSuc(offsetMap); + return Result.buildSuc(listOffsetsResult); } catch (NotExistException nee) { - return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getClusterPhyNotExist(offsetParam.getClusterPhyId())); + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getClusterPhyNotExist(clusterPhyId)); } catch (Exception e) { log.error( - "class=PartitionServiceImpl||method=getPartitionOffsetFromKafkaAdminClient||clusterPhyId={}||topicName={}||errMsg=exception!", - offsetParam.getClusterPhyId(), offsetParam.getTopicName(), e + "method=getPartitionOffsetFromKafkaAdminClient||clusterPhyId={}||topicName={}||errMsg=exception!", + clusterPhyId, topicName, e ); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } } - private Result> getPartitionOffsetFromKafkaConsumerClient(VersionItemParam itemParam) { + private Result>>> batchGetPartitionOffsetFromKafkaConsumerClient(VersionItemParam itemParam) { + PartitionOffsetParam offsetParam = (PartitionOffsetParam) itemParam; + if (offsetParam.getOffsetSpecList().isEmpty()) { + return Result.buildSuc(Collections.emptyList()); + } + + List>> offsetMapList = new ArrayList<>(); + for (Triple> triple: offsetParam.getOffsetSpecList()) { + Result> subOffsetMapResult = this.getPartitionOffsetFromKafkaConsumerClient( + offsetParam.getClusterPhyId(), + triple.v1(), + triple.v2(), + triple.v3() + ); + + if (subOffsetMapResult.failed() && offsetParam.getOffsetSpecList().size() == 1) { + return Result.buildFromIgnoreData(subOffsetMapResult); + } + + if (subOffsetMapResult.hasData()) { + offsetMapList.add(new Tuple<>(triple.v2(), subOffsetMapResult.getData())); + } + } + + return Result.buildSuc(offsetMapList); + } + + private Result> getPartitionOffsetFromKafkaConsumerClient(Long clusterPhyId, String topicName, KSOffsetSpec offsetSpec, List tpList) { KafkaConsumer kafkaConsumer = null; - PartitionOffsetParam offsetParam = (PartitionOffsetParam) itemParam; try { - if (ValidateUtils.isEmptyMap(offsetParam.getTopicPartitionOffsets())) { + if (ValidateUtils.isEmptyList(tpList)) { return Result.buildSuc(new HashMap<>()); } - kafkaConsumer = kafkaConsumerClient.getClient(offsetParam.getClusterPhyId()); + kafkaConsumer = kafkaConsumerClient.getClient(clusterPhyId); - OffsetSpec offsetSpec = new ArrayList<>(offsetParam.getTopicPartitionOffsets().values()).get(0); - if (offsetSpec instanceof OffsetSpec.LatestSpec) { + if (offsetSpec instanceof KSOffsetSpec.KSLatestSpec) { return Result.buildSuc( kafkaConsumer.endOffsets( - offsetParam.getTopicPartitionOffsets().keySet(), + tpList, Duration.ofMillis(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS) ) ); } - if (offsetSpec instanceof OffsetSpec.EarliestSpec) { + if (offsetSpec instanceof KSOffsetSpec.KSEarliestSpec) { return Result.buildSuc( kafkaConsumer.beginningOffsets( - offsetParam.getTopicPartitionOffsets().keySet(), + tpList, Duration.ofMillis(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS) ) ); } - if (offsetSpec instanceof OffsetSpec.TimestampSpec) { + if (offsetSpec instanceof KSOffsetSpec.KSTimestampSpec) { // 按照时间进行查找 Map timestampMap = new HashMap<>(); - offsetParam.getTopicPartitionOffsets().entrySet().stream().forEach(elem -> timestampMap.put(elem.getKey(), offsetParam.getTimestamp())); + tpList.forEach(elem -> timestampMap.put(elem, ((KSOffsetSpec.KSTimestampSpec) offsetSpec).timestamp())); Map offsetMetadataMap = kafkaConsumer.offsetsForTimes( timestampMap, @@ -377,17 +506,17 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "OffsetSpec type illegal"); } catch (NotExistException nee) { - return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getClusterPhyNotExist(offsetParam.getClusterPhyId())); + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getClusterPhyNotExist(clusterPhyId)); } catch (Exception e) { log.error( - "class=PartitionServiceImpl||method=getPartitionOffsetFromKafkaConsumerClient||clusterPhyId={}||topicName={}||errMsg=exception!", - offsetParam.getClusterPhyId(), offsetParam.getTopicName(), e + "method=getPartitionOffsetFromKafkaConsumerClient||clusterPhyId={}||topicName={}||errMsg=exception!", + clusterPhyId, topicName, e ); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } finally { if (kafkaConsumer != null) { - kafkaConsumerClient.returnClient(offsetParam.getClusterPhyId(), kafkaConsumer); + kafkaConsumerClient.returnClient(clusterPhyId, kafkaConsumer); } } } @@ -411,7 +540,7 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P return Result.buildSuc(partitionMap); } catch (Exception e) { - log.error("class=PartitionServiceImpl||method=getPartitionsFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getPartitionsFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } @@ -430,7 +559,7 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P } return Result.buildSuc(partitionMap); } catch (Exception e) { - log.error("class=PartitionServiceImpl||method=getPartitionsFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getPartitionsFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } @@ -447,7 +576,7 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P TopicDescription description = describeTopicsResult.all().get().get(topicName); return Result.buildSuc(PartitionConverter.convert2PartitionList(clusterPhy.getId(), description)); }catch (Exception e) { - log.error("class=PartitionServiceImpl||method=getPartitionsFromAdminClientByClusterTopicName||clusterPhyId={}||topicName={}||errMsg=exception", clusterPhy.getId(),topicName, e); + log.error("method=getPartitionsFromAdminClientByClusterTopicName||clusterPhyId={}||topicName={}||errMsg=exception", clusterPhy.getId(),topicName, e); return Result.buildFailure(ResultStatus.KAFKA_OPERATE_FAILED); } } @@ -470,7 +599,7 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P } return Result.buildSuc(partitionList); } catch (Exception e) { - log.error("class=PartitionServiceImpl||method=getPartitionsFromZKClientByClusterTopicName||clusterPhyId={}||topicName={}||errMsg=exception", clusterPhy.getId(),topicName, e); + log.error("method=getPartitionsFromZKClientByClusterTopicName||clusterPhyId={}||topicName={}||errMsg=exception", clusterPhy.getId(),topicName, e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } } @@ -482,21 +611,24 @@ public class PartitionServiceImpl extends BaseVersionControlService implements P List partitionList = new ArrayList<>(); for (PartitionPO po: poList) { - if(null != po){partitionList.add(convert2Partition(po));} + if(null != po) { + partitionList.add(this.convert2Partition(po)); + } } return partitionList; } - private List convert2PartitionPOList(List partitionList) { - if (partitionList == null) { - return new ArrayList<>(); + private Result> convert2OffsetMapResult(Result>>> listResult) { + if (listResult.failed()) { + return Result.buildFromIgnoreData(listResult); + } else if (ValidateUtils.isEmptyList(listResult.getData())) { + return Result.buildSuc(new HashMap<>(0)); } - List poList = new ArrayList<>(); - for (Partition partition: partitionList) { - poList.add(this.convert2PartitionPO(partition)); - } - return poList; + Map offsetMap = new HashMap<>(); + listResult.getData().forEach(elem -> offsetMap.putAll(elem.v2())); + + return Result.buildSuc(offsetMap); } private PartitionPO convert2PartitionPO(Partition partition) { diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java deleted file mode 100644 index 80cc2644..00000000 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ReplicaMetricCollectorTask.java +++ /dev/null @@ -1,32 +0,0 @@ -//package com.xiaojukeji.know.streaming.km.task.kafka.metrics; -// -//import com.didiglobal.logi.job.annotation.Task; -//import com.didiglobal.logi.job.common.TaskResult; -//import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -//import com.xiaojukeji.know.streaming.km.collector.metric.kafka.ReplicaMetricCollector; -//import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.beans.factory.annotation.Autowired; -// -///** -// * @author didi -// */ -//@Slf4j -//@Task(name = "ReplicaMetricCollectorTask", -// description = "Replica指标采集任务", -// cron = "0 0/1 * * * ? *", -// autoRegister = true, -// consensual = ConsensualEnum.BROADCAST, -// timeout = 2 * 60) -//public class ReplicaMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { -// -// @Autowired -// private ReplicaMetricCollector replicaMetricCollector; -// -// @Override -// public TaskResult processClusterTask(ClusterPhy clusterPhy, long triggerTimeUnitMs) throws Exception { -// replicaMetricCollector.collectMetrics(clusterPhy); -// -// return TaskResult.SUCCESS; -// } -//} From 4293d05fcacbf3bf3d2bb8c8626fa363e032195d Mon Sep 17 00:00:00 2001 From: zengqiao Date: Sun, 4 Dec 2022 17:53:31 +0800 Subject: [PATCH 040/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96Topic?= =?UTF-8?q?=E5=85=83=E4=BF=A1=E6=81=AF=E6=9B=B4=E6=96=B0=E7=AD=96=E7=95=A5?= =?UTF-8?q?(#806)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/bean/entity/topic/TopicConfig.java | 5 ++ .../km/common/bean/po/topic/TopicPO.java | 33 ++++++++++ .../km/common/converter/TopicConverter.java | 9 +-- .../km/core/service/topic/TopicService.java | 3 +- .../topic/impl/TopicConfigServiceImpl.java | 13 ++-- .../service/topic/impl/TopicServiceImpl.java | 64 +++++++++++-------- .../km/persistence/mysql/topic/TopicDAO.java | 2 +- .../main/resources/mybatis/TopicMapper.xml | 4 +- .../kafka/metadata/SyncTopicConfigTask.java | 19 ++++-- 9 files changed, 99 insertions(+), 53 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/topic/TopicConfig.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/topic/TopicConfig.java index 162f5bcf..5fde198a 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/topic/TopicConfig.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/topic/TopicConfig.java @@ -14,6 +14,11 @@ import java.io.Serializable; @NoArgsConstructor @AllArgsConstructor public class TopicConfig implements Serializable { + /** + * 表主键ID + */ + private Long id; + /** * 物理集群ID */ diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/topic/TopicPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/topic/TopicPO.java index 09ccbc16..8e14a313 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/topic/TopicPO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/topic/TopicPO.java @@ -5,6 +5,8 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import lombok.Data; +import java.util.Objects; + @Data @TableName(Constant.MYSQL_TABLE_NAME_PREFIX + "topic") public class TopicPO extends BasePO { @@ -52,4 +54,35 @@ public class TopicPO extends BasePO { * 备注信息 */ private String description; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + if (!super.equals(o)) { + return false; + } + + TopicPO topicPO = (TopicPO) o; + return Objects.equals(clusterPhyId, topicPO.clusterPhyId) + && Objects.equals(topicName, topicPO.topicName) + && Objects.equals(replicaNum, topicPO.replicaNum) + && Objects.equals(partitionNum, topicPO.partitionNum) + && Objects.equals(brokerIds, topicPO.brokerIds) + && Objects.equals(partitionMap, topicPO.partitionMap) + && Objects.equals(retentionMs, topicPO.retentionMs) + && Objects.equals(type, topicPO.type) + && Objects.equals(description, topicPO.description); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), clusterPhyId, topicName, replicaNum, partitionNum, brokerIds, partitionMap, retentionMs, type, description); + } } \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicConverter.java index c935c7f4..9ad4ed46 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicConverter.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicConverter.java @@ -55,10 +55,6 @@ public class TopicConverter { * 仅合并Topic的元信息部分,业务信息和配置信息部分不合并 */ public static TopicPO mergeAndOnlyMetadata2NewTopicPO(Topic newTopicData, TopicPO oldDBTopicPO) { - if (newTopicData == null) { - return null; - } - TopicPO newTopicPO = new TopicPO(); newTopicPO.setId(oldDBTopicPO != null? oldDBTopicPO.getId(): null); @@ -68,6 +64,7 @@ public class TopicConverter { newTopicPO.setReplicaNum(newTopicData.getReplicaNum()); newTopicPO.setBrokerIds(CommonUtils.intList2String(new ArrayList<>(newTopicData.getBrokerIdSet()))); newTopicPO.setType(newTopicData.getType()); + newTopicPO.setPartitionMap(ConvertUtil.obj2Json(newTopicData.getPartitionMap())); if (newTopicData.getCreateTime() != null) { newTopicPO.setCreateTime(new Date(newTopicData.getCreateTime())); @@ -77,8 +74,8 @@ public class TopicConverter { newTopicPO.setUpdateTime(oldDBTopicPO != null? oldDBTopicPO.getUpdateTime(): new Date()); } - newTopicPO.setPartitionMap(ConvertUtil.obj2Json(newTopicData.getPartitionMap())); - + newTopicPO.setDescription(oldDBTopicPO != null? oldDBTopicPO.getDescription(): null); + newTopicPO.setRetentionMs(oldDBTopicPO != null? oldDBTopicPO.getRetentionMs(): null); return newTopicPO; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicService.java index 14aa4a93..cd5d5ded 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicService.java @@ -22,6 +22,7 @@ public interface TopicService { * 从DB获取数据 */ List listTopicsFromDB(Long clusterPhyId); + List listTopicPOsFromDB(Long clusterPhyId); Topic getTopic(Long clusterPhyId, String topicName); List listRecentUpdateTopicNamesFromDB(Long clusterPhyId, Integer time); // 获取集群最近新增Topic的topic名称:time单位为秒 @@ -39,6 +40,6 @@ public interface TopicService { int addNewTopic2DB(TopicPO po); int deleteTopicInDB(Long clusterPhyId, String topicName); void batchReplaceMetadata(Long clusterPhyId, List presentTopicList); - int batchReplaceConfig(Long clusterPhyId, List topicConfigList); + int batchReplaceChangedConfig(Long clusterPhyId, List topicConfigList); Result updatePartitionNum(Long clusterPhyId, String topicName, Integer partitionNum); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java index 2b089c67..0149b5d4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java @@ -10,7 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.param.config.KafkaTop import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam; 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.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.constant.kafka.*; @@ -185,11 +184,9 @@ public class TopicConfigServiceImpl extends BaseVersionControlService implements private Result getTopicConfigByZKClient(Long clusterPhyId, String topicName) { try { - Topic topic = topicService.getTopic(clusterPhyId, topicName); - KafkaZkClient kafkaZkClient = kafkaAdminZKClient.getClient(clusterPhyId); - Properties properties = kafkaZkClient.getEntityConfigs("topics", topic.getTopicName()); + Properties properties = kafkaZkClient.getEntityConfigs("topics", topicName); for (Object key: properties.keySet()) { properties.getProperty((String) key); } @@ -209,12 +206,10 @@ public class TopicConfigServiceImpl extends BaseVersionControlService implements try { AdminClient adminClient = kafkaAdminClient.getClient(param.getClusterPhyId()); - Topic metadata = topicService.getTopic(param.getClusterPhyId(), param.getTopicName()); - - ConfigResource configResource = new ConfigResource(ConfigResource.Type.TOPIC, metadata.getTopicName()); + ConfigResource configResource = new ConfigResource(ConfigResource.Type.TOPIC, param.getTopicName()); DescribeConfigsResult describeConfigsResult = adminClient.describeConfigs( - Arrays.asList(configResource), - buildDescribeConfigsOptions() + Collections.singletonList(configResource), + buildDescribeConfigsOptions().timeoutMs(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS) ); Map configMap = describeConfigsResult.all().get(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java index 2689373c..ac101c61 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicServiceImpl.java @@ -101,7 +101,15 @@ public class TopicServiceImpl implements TopicService { @Override public List listTopicsFromDB(Long clusterPhyId) { - return TopicConverter.convert2TopicList(this.getTopicsFromDB(clusterPhyId)); + return TopicConverter.convert2TopicList(this.listTopicPOsFromDB(clusterPhyId)); + } + + @Override + public List listTopicPOsFromDB(Long clusterPhyId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(TopicPO::getClusterPhyId, clusterPhyId); + + return topicDAO.selectList(lambdaQueryWrapper); } @Override @@ -182,39 +190,46 @@ public class TopicServiceImpl implements TopicService { @Override public void batchReplaceMetadata(Long clusterPhyId, List presentTopicList) { - Map presentTopicMap = presentTopicList.stream().collect(Collectors.toMap(Topic::getTopicName, Function.identity())); - - List dbTopicPOList = this.getTopicsFromDB(clusterPhyId); + Map inDBMap = this.listTopicPOsFromDB(clusterPhyId).stream().collect(Collectors.toMap(TopicPO::getTopicName, Function.identity())); // 新旧合并 - for (TopicPO dbTopicPO: dbTopicPOList) { - Topic topic = presentTopicMap.remove(dbTopicPO.getTopicName()); - if (topic == null) { - topicDAO.deleteById(dbTopicPO.getId()); - continue; - } - - topicDAO.updateById(TopicConverter.mergeAndOnlyMetadata2NewTopicPO(topic, dbTopicPO)); - } - - // DB中没有的则插入DB - for (Topic topic: presentTopicMap.values()) { + for (Topic presentTopic: presentTopicList) { try { - topicDAO.insert(TopicConverter.mergeAndOnlyMetadata2NewTopicPO(topic, null)); + TopicPO inDBTopicPO = inDBMap.remove(presentTopic.getTopicName()); + + TopicPO newTopicPO = TopicConverter.mergeAndOnlyMetadata2NewTopicPO(presentTopic, inDBTopicPO); + if (inDBTopicPO == null) { + topicDAO.insert(newTopicPO); + } else if (!newTopicPO.equals(inDBTopicPO)) { + // 有变化时,则进行更新 + if (presentTopic.getUpdateTime() == null) { + // 如果原数据的更新时间为null,则修改为当前时间 + newTopicPO.setUpdateTime(new Date()); + } + topicDAO.updateById(newTopicPO); + } + + // 无变化时,直接忽略更新 } catch (DuplicateKeyException dke) { // 忽略key冲突错误,多台KM可能同时做insert,所以可能出现key冲突 } } + + // DB中没有的则进行删除 + inDBMap.values().forEach(elem -> topicDAO.deleteById(elem.getId())); } @Override - public int batchReplaceConfig(Long clusterPhyId, List topicConfigList) { + public int batchReplaceChangedConfig(Long clusterPhyId, List changedConfigList) { int effectRow = 0; - for (TopicConfig config: topicConfigList) { + for (TopicConfig config: changedConfigList) { try { - effectRow += topicDAO.updateConfig(ConvertUtil.obj2Obj(config, TopicPO.class)); + effectRow += topicDAO.updateConfigById(ConvertUtil.obj2Obj(config, TopicPO.class)); } catch (Exception e) { - log.error("method=batchReplaceConfig||config={}||errMsg=exception!", config, e); + log.error( + "method=batchReplaceConfig||clusterPhyId={}||topicName={}||retentionMs={}||errMsg=exception!", + config.getClusterPhyId(), config.getTopicName(), config.getRetentionMs(), e + ); } } @@ -299,11 +314,4 @@ public class TopicServiceImpl implements TopicService { return topicDAO.selectOne(lambdaQueryWrapper); } - - private List getTopicsFromDB(Long clusterPhyId) { - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(TopicPO::getClusterPhyId, clusterPhyId); - - return topicDAO.selectList(lambdaQueryWrapper); - } } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/topic/TopicDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/topic/TopicDAO.java index 79437029..7a2dc15f 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/topic/TopicDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/topic/TopicDAO.java @@ -8,5 +8,5 @@ import org.springframework.stereotype.Repository; public interface TopicDAO extends BaseMapper { int replaceAll(TopicPO topicPO); - int updateConfig(TopicPO topicPO); + int updateConfigById(TopicPO topicPO); } diff --git a/km-persistence/src/main/resources/mybatis/TopicMapper.xml b/km-persistence/src/main/resources/mybatis/TopicMapper.xml index 66e5b629..d13ecd52 100644 --- a/km-persistence/src/main/resources/mybatis/TopicMapper.xml +++ b/km-persistence/src/main/resources/mybatis/TopicMapper.xml @@ -25,8 +25,8 @@ (#{clusterPhyId}, #{topicName}, #{replicaNum}, #{partitionNum}, #{brokerIds}, #{partitionMap}, #{retentionMs}, #{type}, #{description}) - - UPDATE ks_km_topic SET retention_ms = #{retentionMs} WHERE cluster_phy_id = #{clusterPhyId} AND topic_name = #{topicName} + + UPDATE ks_km_topic SET retention_ms = #{retentionMs} WHERE id=#{id} \ No newline at end of file diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java index 094f288c..60b6fbe8 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncTopicConfigTask.java @@ -7,8 +7,8 @@ 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.result.Result; -import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.Topic; import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.TopicConfig; +import com.xiaojukeji.know.streaming.km.common.bean.po.topic.TopicPO; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicConfigService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; @@ -44,18 +44,25 @@ public class SyncTopicConfigTask extends AbstractAsyncMetadataDispatchTask { public TaskResult processClusterTask(ClusterPhy clusterPhy, long triggerTimeUnitMs) { boolean success = true; - List topicConfigList = new ArrayList<>(); - for (Topic topic: topicService.listTopicsFromDB(clusterPhy.getId())) { - Result configResult = this.getTopicConfig(clusterPhy.getId(), topic.getTopicName()); + List changedConfigList = new ArrayList<>(); + for (TopicPO topicPO: topicService.listTopicPOsFromDB(clusterPhy.getId())) { + Result configResult = this.getTopicConfig(clusterPhy.getId(), topicPO.getTopicName()); if (configResult.failed()) { success = false; continue; } - topicConfigList.add(configResult.getData()); + TopicConfig config = configResult.getData(); + if (topicPO.getRetentionMs().equals(config.getRetentionMs())) { + // 数据无变化,不需要加入待更新列表中 + continue; + } + + config.setId(topicPO.getId()); + changedConfigList.add(configResult.getData()); } - topicService.batchReplaceConfig(clusterPhy.getId(), topicConfigList); + topicService.batchReplaceChangedConfig(clusterPhy.getId(), changedConfigList); return success? TaskResult.SUCCESS: TaskResult.FAIL; } From dd0d5196774d82ec3c5e9081350d7f910e2f01bd Mon Sep 17 00:00:00 2001 From: limaiwang Date: Fri, 2 Dec 2022 12:22:48 +0800 Subject: [PATCH 041/150] =?UTF-8?q?[Optimize]=E6=9B=B4=E6=96=B0Zookeeper?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=96=87=E6=A1=88(#793)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/layout-clusters-fe/src/pages/Zookeeper/Sider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/Sider.tsx b/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/Sider.tsx index 40f4bbc5..4b5befc2 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/Sider.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/Sider.tsx @@ -147,7 +147,7 @@ const ZKDetailMenu = (props: DetailMenuType) => { Date: Mon, 5 Dec 2022 13:38:10 +0800 Subject: [PATCH 042/150] =?UTF-8?q?[Optimize]ZK=E5=9B=9B=E5=AD=97=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=A7=A3=E6=9E=90=E6=97=A5=E5=BF=97=E4=BC=98=E5=8C=96?= =?UTF-8?q?(#805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加遗漏的指标名的处理,减少warn日志该部分的信息 --- .../zookeeper/fourletterword/parser/MonitorCmdDataParser.java | 4 ++++ .../zookeeper/fourletterword/parser/ServerCmdDataParser.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java index 97791a56..ff23afab 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java @@ -98,6 +98,10 @@ public class MonitorCmdDataParser implements FourLetterWordDataParser Date: Mon, 5 Dec 2022 13:49:35 +0800 Subject: [PATCH 043/150] =?UTF-8?q?[Optimize]=E9=94=99=E5=BC=80=E9=87=87?= =?UTF-8?q?=E9=9B=86=E4=BB=BB=E5=8A=A1=E8=A7=A6=E5=8F=91=E6=97=B6=E9=97=B4?= =?UTF-8?q?=EF=BC=8C=E9=99=8D=E4=BD=8EOffset=E4=BF=A1=E6=81=AF=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=97=B6=E8=B6=85=E6=97=B6=E6=83=85=E5=86=B5=E7=9A=84?= =?UTF-8?q?=E5=8F=91=E7=94=9F(#726)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当前指标采集任务都是整分钟触发执行的,导致会同时向Kafka请求分区Offset信息,会导致: 1、请求过多,从而出现超时; 2、同时进行,可能会导致分区重复获取Offset信息; 因此将其错开。 --- .../km/task/kafka/metrics/BrokerMetricCollectorTask.java | 6 +----- .../km/task/kafka/metrics/ClusterMetricCollectorTask.java | 6 +----- .../km/task/kafka/metrics/GroupMetricCollectorTask.java | 2 +- .../km/task/kafka/metrics/TopicMetricCollectorTask.java | 6 +----- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java index 09c004ad..8519b8a6 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/BrokerMetricCollectorTask.java @@ -3,8 +3,6 @@ package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.metric.kafka.BrokerMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; @@ -14,13 +12,11 @@ import org.springframework.beans.factory.annotation.Autowired; */ @Task(name = "BrokerMetricCollectorTask", description = "Broker指标采集任务", - cron = "0 0/1 * * * ? *", + cron = "20 0/1 * * * ? *", autoRegister = true, consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) public class BrokerMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { - private static final ILog log = LogFactory.getLog(BrokerMetricCollectorTask.class); - @Autowired private BrokerMetricCollector brokerMetricCollector; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java index f32f0588..51596084 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ClusterMetricCollectorTask.java @@ -3,8 +3,6 @@ package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.metric.kafka.ClusterMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; @@ -14,13 +12,11 @@ import org.springframework.beans.factory.annotation.Autowired; */ @Task(name = "ClusterMetricCollectorTask", description = "Cluster指标采集任务", - cron = "0 0/1 * * * ? *", + cron = "30 0/1 * * * ? *", autoRegister = true, consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) public class ClusterMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { - private static final ILog log = LogFactory.getLog(ClusterMetricCollectorTask.class); - @Autowired private ClusterMetricCollector clusterMetricCollector; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java index 3018c211..c1a1ff46 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/GroupMetricCollectorTask.java @@ -14,7 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ @Task(name = "GroupMetricCollectorTask", description = "Group指标采集任务", - cron = "0 0/1 * * * ? *", + cron = "40 0/1 * * * ? *", autoRegister = true, consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java index 3c1d023c..a4245071 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/TopicMetricCollectorTask.java @@ -3,8 +3,6 @@ package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.metric.kafka.TopicMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; @@ -14,13 +12,11 @@ import org.springframework.beans.factory.annotation.Autowired; */ @Task(name = "TopicMetricCollectorTask", description = "Topic指标采集任务", - cron = "0 0/1 * * * ? *", + cron = "10 0/1 * * * ? *", autoRegister = true, consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) public class TopicMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { - private static final ILog log = LogFactory.getLog(TopicMetricCollectorTask.class); - @Autowired private TopicMetricCollector topicMetricCollector; From 0f8be4fadc3e4737a2ca253943dd57308f844337 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 5 Dec 2022 14:04:19 +0800 Subject: [PATCH 044/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=BE=93=E5=87=BA=20&=20=E6=9C=AC=E5=9C=B0=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E7=BB=9F=E4=B8=80=E7=AE=A1=E7=90=86(#800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/ClusterTopicsManagerImpl.java | 2 +- .../km/common/constant/MsgConstant.java | 4 + .../km/core/cache/DataBaseDataLocalCache.java | 14 + .../km/core/flusher/DatabaseDataFlusher.java | 38 +++ .../config/PlatformClusterConfigService.java | 1 + .../PlatformClusterConfigServiceImpl.java | 17 + .../service/topic/TopicMetricService.java | 2 +- .../topic/impl/TopicMetricServiceImpl.java | 46 +-- .../km/persistence/es/ESOpClient.java | 293 +++++++----------- .../persistence/es/dao/TopicMetricESDAO.java | 33 +- .../listener/TaskClusterAddedListener.java | 2 +- 11 files changed, 221 insertions(+), 231 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java index c68f9b9a..17a6c63c 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java @@ -44,7 +44,7 @@ public class ClusterTopicsManagerImpl implements ClusterTopicsManager { List topicList = topicService.listTopicsFromDB(clusterPhyId); // 获取集群所有Topic的指标 - Map metricsMap = topicMetricService.getLatestMetricsFromCacheFirst(clusterPhyId); + Map metricsMap = topicMetricService.getLatestMetricsFromCache(clusterPhyId); // 转换成vo List voList = TopicVOConverter.convert2ClusterPhyTopicsOverviewVOList(topicList, metricsMap); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java index 1be8dadf..9072810d 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java @@ -52,6 +52,10 @@ public class MsgConstant { /**************************************************** Partition ****************************************************/ + public static String getPartitionNoLeader(Long clusterPhyId) { + return String.format("集群ID:[%d] 所有分区NoLeader", clusterPhyId); + } + public static String getPartitionNoLeader(Long clusterPhyId, String topicName) { return String.format("集群ID:[%d] Topic名称:[%s] 所有分区NoLeader", clusterPhyId, topicName); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java index 84e2363b..0cb6f832 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java @@ -3,6 +3,7 @@ package com.xiaojukeji.know.streaming.km.core.cache; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; import java.util.List; @@ -10,6 +11,11 @@ import java.util.Map; import java.util.concurrent.TimeUnit; public class DataBaseDataLocalCache { + private static final Cache> topicLatestMetricsCache = Caffeine.newBuilder() + .expireAfterWrite(360, TimeUnit.SECONDS) + .maximumSize(500) + .build(); + private static final Cache clusterLatestMetricsCache = Caffeine.newBuilder() .expireAfterWrite(180, TimeUnit.SECONDS) .maximumSize(500) @@ -20,6 +26,14 @@ public class DataBaseDataLocalCache { .maximumSize(500) .build(); + public static Map getTopicMetrics(Long clusterPhyId) { + return topicLatestMetricsCache.getIfPresent(clusterPhyId); + } + + public static void putTopicMetrics(Long clusterPhyId, Map metricsMap) { + topicLatestMetricsCache.put(clusterPhyId, metricsMap); + } + public static ClusterMetrics getClusterLatestMetrics(Long clusterPhyId) { return clusterLatestMetricsCache.getIfPresent(clusterPhyId); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java index 95519768..1ac1e86a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java @@ -4,13 +4,18 @@ 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.metrics.ClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; 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.topic.Topic; import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil; import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; 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.partition.PartitionService; +import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; +import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -18,11 +23,19 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; @Service public class DatabaseDataFlusher { private static final ILog LOGGER = LogFactory.getLog(DatabaseDataFlusher.class); + @Autowired + private TopicService topicService; + + @Autowired + private TopicMetricService topicMetricService; + @Autowired private ClusterPhyService clusterPhyService; @@ -37,6 +50,8 @@ public class DatabaseDataFlusher { this.flushPartitionsCache(); this.flushClusterLatestMetricsCache(); + + this.flushTopicLatestMetricsCache(); } @Scheduled(cron="0 0/1 * * * ?") @@ -81,4 +96,27 @@ public class DatabaseDataFlusher { }); } } + + @Scheduled(cron = "0 0/1 * * * ?") + private void flushTopicLatestMetricsCache() { + for (ClusterPhy clusterPhy: LoadedClusterPhyCache.listAll().values()) { + FutureUtil.quickStartupFutureUtil.submitTask(() -> { + try { + + List topicNameList = topicService.listTopicsFromCacheFirst(clusterPhy.getId()).stream().map(Topic::getTopicName).collect(Collectors.toList()); + + List metricsList = topicMetricService.listTopicLatestMetricsFromES(clusterPhy.getId(), topicNameList, Collections.emptyList()); + + Map metricsMap = metricsList + .stream() + .collect(Collectors.toMap(TopicMetrics::getTopic, Function.identity())); + + DataBaseDataLocalCache.putTopicMetrics(clusterPhy.getId(), metricsMap); + + } catch (Exception e) { + LOGGER.error("method=flushTopicLatestMetricsCache||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); + } + }); + } + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/PlatformClusterConfigService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/PlatformClusterConfigService.java index 1d9e571d..148cec1e 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/PlatformClusterConfigService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/PlatformClusterConfigService.java @@ -17,4 +17,5 @@ public interface PlatformClusterConfigService { Map getByClusterAndGroupWithoutDefault(Long clusterPhyId, String group); + Map> listByGroup(String groupName); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/impl/PlatformClusterConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/impl/PlatformClusterConfigServiceImpl.java index c6b3bd5d..2c65e9fa 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/impl/PlatformClusterConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/config/impl/PlatformClusterConfigServiceImpl.java @@ -12,6 +12,7 @@ import com.xiaojukeji.know.streaming.km.persistence.mysql.config.PlatformCluster import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -68,4 +69,20 @@ public class PlatformClusterConfigServiceImpl implements PlatformClusterConfigSe return configPOMap; } + + @Override + public Map> listByGroup(String groupName) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(PlatformClusterConfigPO::getValueGroup, groupName); + + List poList = platformClusterConfigDAO.selectList(lambdaQueryWrapper); + + Map> poMap = new HashMap<>(); + poList.forEach(elem -> { + poMap.putIfAbsent(elem.getClusterId(), new HashMap<>()); + poMap.get(elem.getClusterId()).put(elem.getValueName(), elem); + }); + + return poMap; + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java index d2428aa3..014b460a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java @@ -23,7 +23,7 @@ public interface TopicMetricService { /** * 优先从本地缓存获取metrics信息 */ - Map getLatestMetricsFromCacheFirst(Long clusterPhyId); + Map getLatestMetricsFromCache(Long clusterPhyId); /** * 获取Topic在具体Broker上最新的一个指标 diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java index cf04f2e8..fe680901 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java @@ -2,13 +2,10 @@ package com.xiaojukeji.know.streaming.km.core.service.topic.impl; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.collect.Table; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricsTopicDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; -import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; @@ -30,25 +27,22 @@ import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; 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.cache.DataBaseDataLocalCache; 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.partition.PartitionMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseMetricService; -import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; import com.xiaojukeji.know.streaming.km.persistence.es.dao.TopicMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.management.InstanceNotFoundException; import javax.management.ObjectName; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; @@ -58,8 +52,7 @@ import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafk */ @Service public class TopicMetricServiceImpl extends BaseMetricService implements TopicMetricService { - - private static final ILog LOGGER = LogFactory.getLog( TopicMetricServiceImpl.class); + private static final ILog LOGGER = LogFactory.getLog(TopicMetricServiceImpl.class); public static final String TOPIC_METHOD_DO_NOTHING = "doNothing"; public static final String TOPIC_METHOD_GET_HEALTH_SCORE = "getMetricHealthScore"; @@ -86,18 +79,6 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe @Autowired private TopicMetricESDAO topicMetricESDAO; - private final Cache> topicLatestMetricsCache = Caffeine.newBuilder() - .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(200) - .build(); - - @Scheduled(cron = "0 0/2 * * * ?") - private void flushClusterLatestMetricsCache() { - for (ClusterPhy clusterPhy: LoadedClusterPhyCache.listAll().values()) { - this.updateCacheAndGetMetrics(clusterPhy.getId()); - } - } - @Override protected VersionItemTypeEnum getVersionItemType() { return VersionItemTypeEnum.METRIC_TOPIC; @@ -152,13 +133,13 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe } @Override - public Map getLatestMetricsFromCacheFirst(Long clusterPhyId) { - Map metricsMap = topicLatestMetricsCache.getIfPresent(clusterPhyId); - if (metricsMap != null) { - return metricsMap; + public Map getLatestMetricsFromCache(Long clusterPhyId) { + Map metricsMap = DataBaseDataLocalCache.getTopicMetrics(clusterPhyId); + if (metricsMap == null) { + return new HashMap<>(); } - return this.updateCacheAndGetMetrics(clusterPhyId); + return metricsMap; } @Override @@ -308,19 +289,8 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe return Result.buildSuc(count); } + /**************************************************** private method ****************************************************/ - private Map updateCacheAndGetMetrics(Long clusterPhyId) { - List topicNames = topicService.listTopicsFromDB(clusterPhyId) - .stream().map(Topic::getTopicName).collect(Collectors.toList()); - - List metrics = listTopicLatestMetricsFromES(clusterPhyId, topicNames, Arrays.asList()); - - Map metricsMap = metrics.stream() - .collect(Collectors.toMap(TopicMetrics::getTopic, Function.identity())); - - topicLatestMetricsCache.put(clusterPhyId, metricsMap); - return metricsMap; - } private List listTopNTopics(Long clusterId, int topN){ diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java index 5e91f7b2..822c44e1 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; import com.didiglobal.logi.elasticsearch.client.ESClient; import com.didiglobal.logi.elasticsearch.client.gateway.document.ESIndexRequest; import com.didiglobal.logi.elasticsearch.client.gateway.document.ESIndexResponse; +import com.didiglobal.logi.elasticsearch.client.model.exception.ESIndexNotFoundException; import com.didiglobal.logi.elasticsearch.client.model.type.ESVersion; import com.didiglobal.logi.elasticsearch.client.request.batch.BatchNode; import com.didiglobal.logi.elasticsearch.client.request.batch.BatchType; @@ -20,10 +21,11 @@ import com.didiglobal.logi.elasticsearch.client.response.indices.puttemplate.ESI import com.didiglobal.logi.elasticsearch.client.response.query.query.ESQueryResponse; import com.didiglobal.logi.elasticsearch.client.response.setting.template.TemplateConfig; import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.google.common.collect.Lists; import com.xiaojukeji.know.streaming.km.common.bean.po.BaseESPO; -import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; @@ -36,6 +38,7 @@ import javax.annotation.Nullable; import javax.annotation.PostConstruct; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -43,7 +46,7 @@ import java.util.stream.Collectors; @Component public class ESOpClient { - private static final ILog LOGGER = LogFactory.getLog("ES_LOGGER"); + private static final ILog LOGGER = LoggerUtil.getESLogger(); /** * es 地址 @@ -90,40 +93,27 @@ public class ESOpClient { ESClient esClient = this.buildEsClient(esAddress, esPass, "", ""); if (esClient != null) { this.esClientPool.add(esClient); - LOGGER.info("class=ESOpClient||method=init||msg=add new es client {}", esAddress); + LOGGER.info("method=init||esAddress={}||msg=add new es client", esAddress); } } } /** - * 从更新es http 客户端连接池找那个获取 - * - * @return + * 获取ES客户端 */ public ESClient getESClientFromPool() { + if (ValidateUtils.isEmptyList(esClientPool)) { + return null; + } + return esClientPool.get((int)(System.currentTimeMillis() % clientCnt)); } - /** - * 归还到es http 客户端连接池 - * @param esClient - */ - public void returnESClientToPool(ESClient esClient) { - // 已不需要进行归还,后续再删除该代码 - } - /** * 查询并获取第一个元素 - * - * @param indexName - * @param queryDsl - * @param clzz - * @param - * @return */ - public T performRequestAndTakeFirst(String indexName, String queryDsl, Class clzz) { - List hits = performRequest(indexName, queryDsl, clzz); - + public T performRequestAndTakeFirst(String indexName, String queryDsl, Class clazz) { + List hits = this.performRequest(indexName, queryDsl, clazz); if (CollectionUtils.isEmpty(hits)) { return null; } @@ -133,31 +123,20 @@ public class ESOpClient { /** * 查询并获取第一个元素 - * - * @param indexName - * @param queryDsl - * @param clazz - * @param - * @return */ - public T performRequestAndTakeFirst(String routingValue, String indexName, - String queryDsl, Class clazz) { - List hits = performRequestWithRouting(routingValue, indexName, queryDsl, clazz); - - if (CollectionUtils.isEmpty(hits)) {return null;} + public T performRequestAndTakeFirst(String routingValue, String indexName, String queryDsl, Class clazz) { + List hits = this.performRequestWithRouting(routingValue, indexName, queryDsl, clazz); + if (CollectionUtils.isEmpty(hits)) { + return null; + } return hits.get(0); } /** * 执行查询 - * - * @param indexName - * @param queryDsl - * @return - * @throws IOException */ - public ESQueryResponse performRequest(String indexName,String queryDsl) { + public ESQueryResponse performRequest(String indexName, String queryDsl) { return doQuery(new ESQueryRequest().indices(indexName).source(queryDsl)); } @@ -170,7 +149,7 @@ public class ESOpClient { return func.apply(esQueryResponse); } - public List performRequest(String indexName, String queryDsl, Class clzz) { + public List performRequest(String indexName, String queryDsl, Class clzz) { ESQueryResponse esQueryResponse = doQuery( new ESQueryRequest().indices(indexName).source(queryDsl).clazz(clzz)); if (esQueryResponse == null) { @@ -210,8 +189,7 @@ public class ESOpClient { return hits; } - public R performRequestWithRouting(String routingValue, String indexName, - String queryDsl, Function func, int tryTimes) { + public R performRequestWithRouting(String routingValue, String indexName, String queryDsl, Function func, int tryTimes) { ESQueryResponse esQueryResponse; do { esQueryResponse = doQuery(new ESQueryRequest().routing(routingValue).indices(indexName).source(queryDsl)); @@ -222,16 +200,12 @@ public class ESOpClient { /** * 写入单条 - * - * @param source - * @return */ public boolean index(String indexName, String id, String source) { - ESClient esClient = null; ESIndexResponse response = null; try { - esClient = getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); if (esClient == null) { return false; } @@ -250,20 +224,11 @@ public class ESOpClient { return response.getRestStatus().getStatus() == HttpStatus.SC_OK || response.getRestStatus().getStatus() == HttpStatus.SC_CREATED; } - } catch (Exception e) { - LOGGER.warn( - "class=ESOpClient||method=index||indexName={}||id={}||source={}||errMsg=index doc error. ", - indexName, id, source, e); - if (response != null) { - LOGGER.warn( - "class=ESOpClient||method=index||indexName={}||id={}||source={}||errMsg=response {}", - indexName, id, source, JSON.toJSONString(response)); - } - } finally { - if (esClient != null) { - returnESClientToPool(esClient); - } + LOGGER.error( + "method=index||indexName={}||id={}||source={}||response={}||errMsg=index failed", + indexName, id, source, ConvertUtil.obj2Json(response), e + ); } return false; @@ -271,19 +236,15 @@ public class ESOpClient { /** * 批量写入 - * - * @param indexName - * @return */ public boolean batchInsert(String indexName, List pos) { if (CollectionUtils.isEmpty(pos)) { return true; } - ESClient esClient = null; ESBatchResponse response = null; try { - esClient = getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); if (esClient == null) { return false; } @@ -312,16 +273,10 @@ public class ESOpClient { return response.getRestStatus().getStatus() == HttpStatus.SC_OK && !response.getErrors(); } } catch (Exception e) { - LOGGER.warn( - "method=batchInsert||indexName={}||errMsg=batch insert error. ", indexName, e); - if (response != null) { - LOGGER.warn("method=batchInsert||indexName={}||errMsg=response {}", indexName, JSON.toJSONString(response)); - } - - } finally { - if (esClient != null) { - returnESClientToPool(esClient); - } + LOGGER.error( + "method=batchInsert||indexName={}||response={}||errMsg=batch index failed", + indexName, ConvertUtil.obj2Json(response), e + ); } return false; @@ -331,9 +286,8 @@ public class ESOpClient { * 根据表达式判断索引是否已存在 */ public boolean indexExist(String indexName) { - ESClient esClient = null; try { - esClient = this.getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); if (esClient == null) { return false; } @@ -341,11 +295,7 @@ public class ESOpClient { // 检查索引是否存在 return esClient.admin().indices().prepareExists(indexName).execute().actionGet(30, TimeUnit.SECONDS).isExists(); } catch (Exception e){ - LOGGER.warn("class=ESOpClient||method=indexExist||indexName={}||msg=exception!", indexName, e); - } finally { - if (esClient != null) { - returnESClientToPool(esClient); - } + LOGGER.error("method=indexExist||indexName={}||msg=exception!", indexName, e); } return false; @@ -355,48 +305,45 @@ public class ESOpClient { * 创建索引 */ public boolean createIndex(String indexName) { - if (indexExist(indexName)) { + if (this.indexExist(indexName)) { return true; } - ESClient client = getESClientFromPool(); - if (client != null) { - try { - ESIndicesPutIndexResponse response = client.admin().indices().preparePutIndex(indexName).execute() - .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); - return response.getAcknowledged(); - } catch (Exception e){ - LOGGER.warn( "msg=create index fail||indexName={}", indexName, e); - } finally { - returnESClientToPool(client); - } + ESClient client = this.getESClientFromPool(); + try { + ESIndicesPutIndexResponse response = client + .admin() + .indices() + .preparePutIndex(indexName) + .execute() + .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); + + return response.getAcknowledged(); + } catch (Exception e){ + LOGGER.error( "method=createIndex||indexName={}||errMsg=exception!", indexName, e); } return false; } public boolean templateExist(String indexTemplateName){ - ESClient esClient = null; - try { - esClient = this.getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); // 获取es中原来index template的配置 - ESIndicesGetTemplateResponse getTemplateResponse = - esClient.admin().indices().prepareGetTemplate( indexTemplateName ).execute().actionGet( ES_OPERATE_TIMEOUT, TimeUnit.SECONDS ); + ESIndicesGetTemplateResponse getTemplateResponse = esClient + .admin() + .indices() + .prepareGetTemplate(indexTemplateName) + .execute() + .actionGet( ES_OPERATE_TIMEOUT, TimeUnit.SECONDS ); TemplateConfig templateConfig = getTemplateResponse.getMultiTemplatesConfig().getSingleConfig(); - if (null != templateConfig) { return true; } } catch (Exception e) { - LOGGER.warn( "method=templateExist||indexTemplateName={}||msg=exception!", - indexTemplateName, e); - } finally { - if (esClient != null) { - this.returnESClientToPool(esClient); - } + LOGGER.error( "method=templateExist||indexTemplateName={}||msg=exception!", indexTemplateName, e); } return false; @@ -406,27 +353,29 @@ public class ESOpClient { * 创建索引模板 */ public boolean createIndexTemplateIfNotExist(String indexTemplateName, String config) { - ESClient esClient = null; - try { - esClient = this.getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); - //存在模板就返回,不存在就创建 - if(templateExist(indexTemplateName)){return true;} + // 存在模板就返回,不存在就创建 + if(this.templateExist(indexTemplateName)) { + return true; + } // 创建新的模板 - ESIndicesPutTemplateResponse response = esClient.admin().indices().preparePutTemplate( indexTemplateName ) - .setTemplateConfig( config ).execute().actionGet( ES_OPERATE_TIMEOUT, TimeUnit.SECONDS ); + ESIndicesPutTemplateResponse response = esClient + .admin() + .indices() + .preparePutTemplate( indexTemplateName ) + .setTemplateConfig(config) + .execute() + .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); return response.getAcknowledged(); } catch (Exception e) { - LOGGER.warn( "method=createIndexTemplateIfNotExist||indexTemplateName={}||config={}||msg=exception!", + LOGGER.error( + "method=createIndexTemplateIfNotExist||indexTemplateName={}||config={}||msg=exception!", indexTemplateName, config, e ); - } finally { - if (esClient != null) { - this.returnESClientToPool(esClient); - } } return false; @@ -434,54 +383,47 @@ public class ESOpClient { /** * 根据索引模板获取所有的索引 - * @param indexName - * @return */ - public List listIndexByName(String indexName){ - ESClient esClient = null; - + public List listIndexByName(String indexName) { try { - esClient = this.getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); - ESIndicesCatIndicesResponse response = esClient.admin().indices().prepareCatIndices(indexName + "*").execute() + ESIndicesCatIndicesResponse response = esClient + .admin() + .indices() + .prepareCatIndices(indexName + "*") + .execute() .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); - - if(null != response){ + if(null != response) { return response.getCatIndexResults().stream().map(CatIndexResult::getIndex).collect(Collectors.toList()); } } catch (Exception e) { - LOGGER.warn( "method=listIndexByTemplate||indexName={}||msg=exception!", - indexName, e); - } finally { - if (esClient != null) { - this.returnESClientToPool(esClient); - } + LOGGER.error( "method=listIndexByName||indexName={}||msg=exception!", indexName, e); } - return new ArrayList<>(); + return Collections.emptyList(); } /** * 删除索引 - * @param indexRealName - * @return */ public boolean delIndexByName(String indexRealName){ - ESClient esClient = null; - try { - esClient = this.getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); - ESIndicesDeleteIndexResponse response = esClient.admin().indices().prepareDeleteIndex(indexRealName).execute() + ESIndicesDeleteIndexResponse response = esClient + .admin() + .indices() + .prepareDeleteIndex(indexRealName) + .execute() .actionGet(ES_OPERATE_TIMEOUT, TimeUnit.SECONDS); + return response.getAcknowledged(); + } catch (ESIndexNotFoundException nfe) { + // 索引不存在时,debug环境时再进行打印 + LOGGER.debug( "method=delIndexByName||indexRealName={}||errMsg=exception!", indexRealName, nfe); } catch (Exception e) { - LOGGER.warn( "method=delIndexByName||indexRealName={}||msg=exception!", - indexRealName, e); - } finally { - if (esClient != null) { - this.returnESClientToPool(esClient); - } + LOGGER.error( "method=delIndexByName||indexRealName={}||errMsg=exception!", indexRealName, e); } return false; @@ -491,61 +433,55 @@ public class ESOpClient { /** * 执行查询 - * @param request - * @return */ @Nullable private ESQueryResponse doQuery(ESQueryRequest request) { - ESClient esClient = null; try { - esClient = getESClientFromPool(); + ESClient esClient = this.getESClientFromPool(); ESQueryResponse response = esClient.query(request).actionGet(120, TimeUnit.SECONDS); - if(!EnvUtil.isOnline()){ - LOGGER.info("method=doQuery||indexName={}||queryDsl={}||ret={}", - request.indices(), bytesReferenceConvertDsl(request.source()), JSON.toJSONString(response)); - } + LOGGER.debug( + "method=doQuery||indexName={}||queryDsl={}||ret={}", + request.indices(), bytesReferenceConvertDsl(request.source()), JSON.toJSONString(response) + ); return response; } catch (Exception e) { LOGGER.error( "method=doQuery||indexName={}||queryDsl={}||errMsg=query error. ", request.indices(), bytesReferenceConvertDsl(request.source()), e); return null; - }finally { - if (esClient != null) { - returnESClientToPool(esClient); - } } } private boolean handleErrorResponse(String indexName, List pos, ESBatchResponse response) { - if (response.getErrors().booleanValue()) { - int errorItemIndex = 0; - - if (CollectionUtils.isNotEmpty(response.getItems())) { - for (IndexResultItemNode item : response.getItems()) { - recordErrorResponseItem(indexName, pos, errorItemIndex++, item); - } - } - - return true; + if (response.getErrors()) { + return false; } - return false; + int errorItemIndex = 0; + + if (CollectionUtils.isNotEmpty(response.getItems())) { + for (IndexResultItemNode item : response.getItems()) { + recordErrorResponseItem(indexName, pos, errorItemIndex++, item); + } + } + + return true; } private void recordErrorResponseItem(String indexName, List pos, int errorItemIndex, IndexResultItemNode item) { - if (item.getIndex() != null && item.getIndex().getShards() != null + if (item.getIndex() != null + && item.getIndex().getShards() != null && CollectionUtils.isNotEmpty(item.getIndex().getShards().getFailures())) { LOGGER.warn( - "class=ESOpClient||method=batchInsert||indexName={}||errMsg=Failures: {}, content: {}", + "method=batchInsert||indexName={}||errMsg=Failures: {}, content: {}", indexName, item.getIndex().getShards().getFailures().toString(), JSON.toJSONString(pos.get(errorItemIndex))); } if (item.getIndex() != null && item.getIndex().getError() != null) { LOGGER.warn( - "class=ESOpClient||method=batchInsert||indexName={}||errMsg=Error: {}, content: {}", + "method=batchInsert||indexName={}||errMsg=Error: {}, content: {}", indexName, item.getIndex().getError().getReason(), JSON.toJSONString(pos.get(errorItemIndex))); } @@ -553,21 +489,18 @@ public class ESOpClient { /** * 转换dsl语句 - * - * @param bytes - * @return */ private String bytesReferenceConvertDsl(BytesReference bytes) { try { return XContentHelper.convertToJson(bytes, false); } catch (IOException e) { - LOGGER.warn("class=ESOpClient||method=bytesReferenceConvertDsl||errMsg=fail to covert", e); + LOGGER.warn("method=bytesReferenceConvertDsl||errMsg=fail to covert", e); } return ""; } - private ESClient buildEsClient(String address,String password,String clusterName, String version) { + private ESClient buildEsClient(String address, String password,String clusterName, String version) { if (StringUtils.isBlank(address)) { return null; } @@ -602,7 +535,7 @@ public class ESOpClient { // ignore } - LOGGER.error("class=ESESOpClient||method=buildEsClient||errMsg={}||address={}", e.getMessage(), address, e); + LOGGER.error("method=buildEsClient||address={}||errMsg=exception", address, e); return null; } } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java index e70f2656..e997a778 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java @@ -207,17 +207,27 @@ public class TopicMetricESDAO extends BaseMetricESDAO { /** * 获取每个 metric 的 topN 个 topic 的指标,如果获取不到 topN 的topics, 则默认返回 defaultTopics 的指标 */ - public Table> listTopicMetricsByTopN(Long clusterPhyId, List defaultTopics, - List metrics, String aggType, int topN, - Long startTime, Long endTime){ + public Table> listTopicMetricsByTopN(Long clusterPhyId, + List defaultTopics, + List metrics, + String aggType, + int topN, + Long startTime, + Long endTime){ //1、获取topN要查询的topic,每一个指标的topN的topic可能不一样 - Map> metricTopics = getTopNTopics(clusterPhyId, metrics, aggType, topN, startTime, endTime); + Map> metricTopics = this.getTopNTopics(clusterPhyId, metrics, aggType, topN, startTime, endTime); Table> table = HashBasedTable.create(); - for(String metric : metrics){ - table.putAll(listTopicMetricsByTopics(clusterPhyId, Arrays.asList(metric), - aggType, metricTopics.getOrDefault(metric, defaultTopics), startTime, endTime)); + for(String metric : metrics) { + table.putAll(this.listTopicMetricsByTopics( + clusterPhyId, + Arrays.asList(metric), + aggType, + metricTopics.getOrDefault(metric, defaultTopics), + startTime, + endTime) + ); } return table; @@ -226,9 +236,12 @@ public class TopicMetricESDAO extends BaseMetricESDAO { /** * 获取每个 metric 指定个 topic 的指标 */ - public Table> listTopicMetricsByTopics(Long clusterPhyId, List metrics, - String aggType, List topics, - Long startTime, Long endTime){ + public Table> listTopicMetricsByTopics(Long clusterPhyId, + List metrics, + String aggType, + List topics, + Long startTime, + Long endTime){ //1、获取需要查下的索引 String realIndex = realIndex(startTime, endTime); diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java index d5512843..1c0492c1 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/service/listener/TaskClusterAddedListener.java @@ -22,7 +22,7 @@ public class TaskClusterAddedListener implements ApplicationListener Date: Mon, 5 Dec 2022 14:20:07 +0800 Subject: [PATCH 045/150] =?UTF-8?q?[Optimize]=E8=A7=84=E8=8C=83=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=BE=93=E5=87=BA=E6=A0=BC=E5=BC=8F(#800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改log输出配置,使其输出的日志中自带class={className}的信息,后续书写代码时,就无需书写该部分内容。 --- km-rest/src/main/resources/logback-spring.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/km-rest/src/main/resources/logback-spring.xml b/km-rest/src/main/resources/logback-spring.xml index 477c60ef..118be70e 100644 --- a/km-rest/src/main/resources/logback-spring.xml +++ b/km-rest/src/main/resources/logback-spring.xml @@ -30,7 +30,7 @@ ${log.path}/log_debug.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -58,7 +58,7 @@ ${log.path}/log_info.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -85,7 +85,7 @@ ${log.path}/log_warn.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -112,7 +112,7 @@ ${log.path}/log_error.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -136,7 +136,7 @@ ${log.path}/es/es.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -152,7 +152,7 @@ ${log.path}/metric/metric_collected.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -168,7 +168,7 @@ ${log.path}/task/task.log - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level class=%logger{50}||%msg%n UTF-8 @@ -193,11 +193,12 @@ - + - + + From 7176e418f51fc0f6aea66b9b693d48e3bc036a1f Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 5 Dec 2022 16:22:49 +0800 Subject: [PATCH 046/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E5=B7=A1=E6=A3=80=E7=9B=B8=E5=85=B3=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E7=9A=84=E8=AE=A1=E7=AE=97(#726)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、增加缓存,减少健康状态指标计算时的IO; 2、健康巡检调整为按照资源维度并发处理; 3、明确HealthCheckResultService和HealthStateService的功能边界; --- .../entity/health/HealthCheckAggResult.java | 22 +- .../bean/entity/health/HealthScoreResult.java | 71 +------ .../vo/health/HealthScoreBaseResultVO.java | 3 +- .../converter/HealthScoreVOConverter.java | 13 +- .../health/HealthCheckDimensionEnum.java | 2 + .../km/core/cache/DataBaseDataLocalCache.java | 23 +++ .../km/core/flusher/DatabaseDataFlusher.java | 29 +++ .../checker/AbstractHealthCheckService.java | 9 +- .../broker/HealthCheckBrokerService.java | 21 +- .../group/HealthCheckGroupService.java | 3 - .../topic/HealthCheckTopicService.java | 9 +- .../HealthCheckZookeeperService.java | 44 ++-- .../checkresult/HealthCheckResultService.java | 20 +- .../impl/HealthCheckResultServiceImpl.java | 77 +++++-- .../health/state/HealthStateService.java | 26 +-- .../state/impl/HealthStateServiceImpl.java | 194 ++++++------------ .../kafka/health/AbstractHealthCheckTask.java | 46 +++-- .../kafka/health/TopicHealthCheckTask.java | 2 +- 18 files changed, 266 insertions(+), 348 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthCheckAggResult.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthCheckAggResult.java index 69d65a20..afd42120 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthCheckAggResult.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthCheckAggResult.java @@ -14,16 +14,16 @@ import java.util.stream.Collectors; @Data @NoArgsConstructor public class HealthCheckAggResult { - private HealthCheckNameEnum checkNameEnum; + protected HealthCheckNameEnum checkNameEnum; - private List poList; + protected List poList; - private Boolean passed; + protected Boolean passed; public HealthCheckAggResult(HealthCheckNameEnum checkNameEnum, List poList) { this.checkNameEnum = checkNameEnum; this.poList = poList; - if (!ValidateUtils.isEmptyList(poList) && poList.stream().filter(elem -> elem.getPassed() <= 0).count() <= 0) { + if (ValidateUtils.isEmptyList(poList) || poList.stream().filter(elem -> elem.getPassed() <= 0).count() <= 0) { passed = true; } else { passed = false; @@ -45,24 +45,12 @@ public class HealthCheckAggResult { 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 getNotPassedResNameList() { if (poList == null) { return new ArrayList<>(); } - return poList.stream().filter(elem -> elem.getPassed() <= 0).map(elem -> elem.getResName()).collect(Collectors.toList()); + return poList.stream().filter(elem -> elem.getPassed() <= 0 && !ValidateUtils.isBlank(elem.getResName())).map(elem -> elem.getResName()).collect(Collectors.toList()); } public Date getCreateTime() { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthScoreResult.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthScoreResult.java index c503c129..302feb5b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthScoreResult.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/health/HealthScoreResult.java @@ -3,87 +3,20 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.health; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; 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 HealthScoreResult { - private HealthCheckNameEnum checkNameEnum; - +public class HealthScoreResult extends HealthCheckAggResult { private BaseClusterHealthConfig baseConfig; - private List poList; - - private Boolean passed; - public HealthScoreResult(HealthCheckNameEnum checkNameEnum, BaseClusterHealthConfig baseConfig, List poList) { - this.checkNameEnum = checkNameEnum; + super(checkNameEnum, poList); this.baseConfig = baseConfig; - 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 getNotPassedResNameList() { - if (poList == null) { - return new ArrayList<>(); - } - - return poList.stream().filter(elem -> elem.getPassed() <= 0 && !ValidateUtils.isBlank(elem.getResName())).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(); } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java index c9abc528..113a74ab 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java @@ -30,8 +30,9 @@ public class HealthScoreBaseResultVO extends BaseTimeVO { @ApiModelProperty(value="检查说明", example = "Group延迟") private String configDesc; + @Deprecated @ApiModelProperty(value="得分", example = "100") - private Integer score; + private Integer score = 100; @ApiModelProperty(value="结果", example = "true") private Boolean passed; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java index 150306dc..e82960b1 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java @@ -24,15 +24,7 @@ public class HealthScoreVOConverter { vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName()); vo.setConfigItem(healthScoreResult.getCheckNameEnum().getConfigItem()); vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc()); - - vo.setScore(healthScoreResult.calRawHealthScore()); - if (healthScoreResult.getTotalCount() <= 0) { - // 未知 - vo.setPassed(null); - } else { - vo.setPassed(healthScoreResult.getPassedCount().equals(healthScoreResult.getTotalCount())); - } - + vo.setPassed(healthScoreResult.getPassed()); vo.setCheckConfig(convert2HealthCheckConfigVO(ConfigGroupEnum.HEALTH.name(), healthScoreResult.getBaseConfig())); vo.setNotPassedResNameList(healthScoreResult.getNotPassedResNameList()); @@ -51,8 +43,7 @@ public class HealthScoreVOConverter { vo.setDimensionName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getMessage()); vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName()); vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc()); - vo.setScore(healthScoreResult.calRawHealthScore()); - vo.setPassed(healthScoreResult.getPassedCount().equals(healthScoreResult.getTotalCount())); + vo.setPassed(healthScoreResult.getPassed()); vo.setCheckConfig(convert2HealthCheckConfigVO(ConfigGroupEnum.HEALTH.name(), healthScoreResult.getBaseConfig())); vo.setCreateTime(healthScoreResult.getCreateTime()); vo.setUpdateTime(healthScoreResult.getUpdateTime()); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java index daa4e641..d1b08181 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java @@ -20,6 +20,8 @@ public enum HealthCheckDimensionEnum { ZOOKEEPER(4, "Zookeeper"), + MAX_VAL(100, "所有的dimension的值需要小于MAX_VAL") + ; private final int dimension; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java index 0cb6f832..5412988c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java @@ -5,6 +5,8 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; +import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; +import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; import java.util.List; import java.util.Map; @@ -26,6 +28,11 @@ public class DataBaseDataLocalCache { .maximumSize(500) .build(); + private static final Cache>> healthCheckResultCache = Caffeine.newBuilder() + .expireAfterWrite(90, TimeUnit.SECONDS) + .maximumSize(1000) + .build(); + public static Map getTopicMetrics(Long clusterPhyId) { return topicLatestMetricsCache.getIfPresent(clusterPhyId); } @@ -50,6 +57,22 @@ public class DataBaseDataLocalCache { partitionsCache.put(clusterPhyId, partitionMap); } + public static Map> getHealthCheckResults(Long clusterId, HealthCheckDimensionEnum dimensionEnum) { + return healthCheckResultCache.getIfPresent(getHealthCheckCacheKey(clusterId, dimensionEnum.getDimension())); + } + + public static void putHealthCheckResults(Long cacheKey, Map> poMap) { + healthCheckResultCache.put(cacheKey, poMap); + } + + public static void putHealthCheckResults(Long clusterId, HealthCheckDimensionEnum dimensionEnum, Map> poMap) { + healthCheckResultCache.put(getHealthCheckCacheKey(clusterId, dimensionEnum.getDimension()), poMap); + } + + public static Long getHealthCheckCacheKey(Long clusterId, Integer dimensionCode) { + return clusterId * HealthCheckDimensionEnum.MAX_VAL.getDimension() + dimensionCode; + } + /**************************************************** private method ****************************************************/ private DataBaseDataLocalCache() { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java index 1ac1e86a..70d138be 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java @@ -8,10 +8,12 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; 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.topic.Topic; +import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil; import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; 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.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; @@ -42,6 +44,9 @@ public class DatabaseDataFlusher { @Autowired private ClusterMetricService clusterMetricService; + @Autowired + private HealthCheckResultService healthCheckResultService; + @Autowired private PartitionService partitionService; @@ -52,6 +57,8 @@ public class DatabaseDataFlusher { this.flushClusterLatestMetricsCache(); this.flushTopicLatestMetricsCache(); + + this.flushHealthCheckResultCache(); } @Scheduled(cron="0 0/1 * * * ?") @@ -76,6 +83,28 @@ public class DatabaseDataFlusher { } } + @Scheduled(cron="0 0/1 * * * ?") + public void flushHealthCheckResultCache() { + FutureUtil.quickStartupFutureUtil.submitTask(() -> { + List poList = healthCheckResultService.listAll(); + + Map>> newPOMap = new ConcurrentHashMap<>(); + + // 更新缓存 + poList.forEach(po -> { + Long cacheKey = DataBaseDataLocalCache.getHealthCheckCacheKey(po.getClusterPhyId(), po.getDimension()); + + newPOMap.putIfAbsent(cacheKey, new ConcurrentHashMap<>()); + newPOMap.get(cacheKey).putIfAbsent(po.getResName(), new ArrayList<>()); + newPOMap.get(cacheKey).get(po.getResName()).add(po); + }); + + for (Map.Entry>> entry: newPOMap.entrySet()) { + DataBaseDataLocalCache.putHealthCheckResults(entry.getKey(), entry.getValue()); + } + }); + } + @Scheduled(cron = "0 0/1 * * * ?") private void flushClusterLatestMetricsCache() { for (ClusterPhy clusterPhy: clusterPhyService.listAllClusters()) { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java index b25a7b8c..c6b2cf3f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java @@ -5,7 +5,6 @@ import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; @@ -30,7 +29,7 @@ public abstract class AbstractHealthCheckService { public abstract HealthCheckDimensionEnum getHealthCheckDimensionEnum(); public HealthCheckResult checkAndGetResult(ClusterParam clusterParam, BaseClusterHealthConfig clusterHealthConfig) { - if (ValidateUtils.anyNull( clusterParam, clusterHealthConfig)) { + if (ValidateUtils.anyNull(clusterParam, clusterHealthConfig)) { return null; } @@ -48,8 +47,10 @@ public abstract class AbstractHealthCheckService { try { return function.apply(new Tuple<>(clusterParam, clusterHealthConfig)); } catch (Exception e) { - log.error("method=checkAndGetResult||clusterPhyParam={}||clusterHealthConfig={}||errMsg=exception!", - clusterParam, clusterHealthConfig, e); + log.error( + "method=checkAndGetResult||clusterParam={}||clusterHealthConfig={}||errMsg=exception!", + clusterParam, clusterHealthConfig, e + ); } return null; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java index c9b173af..6da0ed4f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java @@ -9,7 +9,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckRes import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.broker.BrokerParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum; @@ -19,7 +18,6 @@ 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.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems; -import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -28,7 +26,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@Data @Service public class HealthCheckBrokerService extends AbstractHealthCheckService { private static final ILog log = LogFactory.getLog(HealthCheckBrokerService.class); @@ -48,9 +45,10 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { @Override public List getResList(Long clusterPhyId) { List paramList = new ArrayList<>(); - for (Broker broker: brokerService.listAliveBrokersFromDB(clusterPhyId)) { + for (Broker broker: brokerService.listAliveBrokersFromCacheFirst(clusterPhyId)) { paramList.add(new BrokerParam(clusterPhyId, broker.getBrokerId())); } + return paramList; } @@ -73,8 +71,11 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { String.valueOf(param.getBrokerId()) ); - Result metricsResult = brokerMetricService.getLatestMetricsFromES( - param.getClusterPhyId(), param.getBrokerId()); + Result metricsResult = brokerMetricService.collectBrokerMetricsFromKafka( + param.getClusterPhyId(), + param.getBrokerId(), + BrokerMetricVersionItems.BROKER_METRIC_NETWORK_RPO_AVG_IDLE + ); if (metricsResult.failed()) { log.error("method=checkBrokerNetworkProcessorAvgIdleTooLow||param={}||config={}||result={}||errMsg=get metrics failed", @@ -82,14 +83,14 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { return null; } - Float avgIdle = metricsResult.getData().getMetrics().get( BrokerMetricVersionItems.BROKER_METRIC_NETWORK_RPO_AVG_IDLE); + Float avgIdle = metricsResult.getData().getMetrics().get(BrokerMetricVersionItems.BROKER_METRIC_NETWORK_RPO_AVG_IDLE); if (avgIdle == null) { log.error("method=checkBrokerNetworkProcessorAvgIdleTooLow||param={}||config={}||result={}||errMsg=get metrics failed", param, singleConfig, metricsResult); return null; } - checkResult.setPassed(avgIdle >= singleConfig.getValue()? 1: 0); + checkResult.setPassed(avgIdle >= singleConfig.getValue()? Constant.YES: Constant.NO); return checkResult; } @@ -111,7 +112,7 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { Result metricsResult = brokerMetricService.collectBrokerMetricsFromKafka( param.getClusterPhyId(), param.getBrokerId(), - Arrays.asList( BrokerMetricVersionItems.BROKER_METRIC_TOTAL_REQ_QUEUE) + Arrays.asList(BrokerMetricVersionItems.BROKER_METRIC_TOTAL_REQ_QUEUE) ); if (metricsResult.failed()) { @@ -120,7 +121,7 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { return null; } - Float queueSize = metricsResult.getData().getMetrics().get( BrokerMetricVersionItems.BROKER_METRIC_TOTAL_REQ_QUEUE); + Float queueSize = metricsResult.getData().getMetrics().get(BrokerMetricVersionItems.BROKER_METRIC_TOTAL_REQ_QUEUE); if (queueSize == null) { log.error("method=checkBrokerRequestQueueFull||param={}||config={}||result={}||errMsg=get metrics failed", param, singleConfig, metricsResult); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java index 8cc40c20..d5022cfc 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java @@ -6,7 +6,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.Ba import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthDetectedInLatestMinutesConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.group.GroupParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchTerm; @@ -17,7 +16,6 @@ import com.xiaojukeji.know.streaming.km.common.utils.Tuple; 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.checker.AbstractHealthCheckService; -import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -27,7 +25,6 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.GROUP_METRIC_STATE; -@Data @Service public class HealthCheckGroupService extends AbstractHealthCheckService { private static final ILog log = LogFactory.getLog(HealthCheckGroupService.class); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java index 613d2902..2557fd5b 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java @@ -7,7 +7,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.He import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthDetectedInLatestMinutesConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -32,7 +31,7 @@ import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafk @Service public class HealthCheckTopicService extends AbstractHealthCheckService { - private static final ILog log = LogFactory.getLog(HealthCheckTopicService.class); + private static final ILog LOGGER = LogFactory.getLog(HealthCheckTopicService.class); @Autowired private TopicService topicService; @@ -52,7 +51,7 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { @Override public List getResList(Long clusterPhyId) { List paramList = new ArrayList<>(); - for (Topic topic: topicService.listTopicsFromDB(clusterPhyId)) { + for (Topic topic: topicService.listTopicsFromCacheFirst(clusterPhyId)) { paramList.add(new TopicParam(clusterPhyId, topic.getTopicName())); } return paramList; @@ -86,12 +85,12 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { ); if (countResult.failed() || !countResult.hasData()) { - log.error("method=checkTopicUnderReplicatedPartition||param={}||config={}||result={}||errMsg=get metrics failed", + LOGGER.error("method=checkTopicUnderReplicatedPartition||param={}||config={}||result={}||errMsg=get metrics failed", param, singleConfig, countResult); return null; } - checkResult.setPassed(countResult.getData() >= singleConfig.getDetectedTimes()? 0: 1); + checkResult.setPassed(countResult.getData() >= singleConfig.getDetectedTimes()? Constant.NO: Constant.YES); return checkResult; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java index 45e6e0b8..f79af127 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java @@ -6,11 +6,9 @@ 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.ClusterParam; -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; @@ -22,26 +20,23 @@ 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.kafka.ZookeeperMetricVersionItems; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperMetricService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; 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.Collections; 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; @@ -60,22 +55,24 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { @Override public List getResList(Long clusterPhyId) { - ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(clusterPhyId); + ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(clusterPhyId); if (clusterPhy == null) { return new ArrayList<>(); } try { - return Arrays.asList(new ZookeeperParam( - clusterPhyId, - ZookeeperUtils.connectStringParser(clusterPhy.getZookeeper()), - ConvertUtil.str2ObjByJson(clusterPhy.getZkProperties(), ZKConfig.class) - )); + return Collections.singletonList( + 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); + log.error("method=getResList||clusterPhyId={}||errMsg=exception!", clusterPhyId, e); } - return new ArrayList<>(); + return Collections.emptyList(); } @Override @@ -85,7 +82,6 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { private HealthCheckResult checkBrainSplit(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); - HealthCompareValueConfig valueConfig = (HealthCompareValueConfig) singleConfigSimpleTuple.getV2(); List infoList = zookeeperService.listFromDBByCluster(param.getClusterPhyId()); HealthCheckResult checkResult = new HealthCheckResult( @@ -97,7 +93,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { long value = infoList.stream().filter(elem -> ZKRoleEnum.LEADER.getRole().equals(elem.getRole())).count(); - checkResult.setPassed(value == valueConfig.getValue().longValue() ? Constant.YES : Constant.NO); + checkResult.setPassed(value == 1 ? Constant.YES : Constant.NO); return checkResult; } @@ -116,7 +112,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { ); if (metricsResult.failed() || !metricsResult.hasData()) { log.error( - "class=HealthCheckZookeeperService||method=checkOutstandingRequests||clusterPhyId={}||param={}||config={}||result={}||errMsg=get metrics failed",clusterPhyId ,param, valueConfig, metricsResult + "method=checkOutstandingRequests||clusterPhyId={}||param={}||config={}||result={}||errMsg=get metrics failed",clusterPhyId ,param, valueConfig, metricsResult ); return null; } @@ -130,14 +126,14 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS); if(null == value){ - log.error("class=HealthCheckZookeeperService||method=checkOutstandingRequests||clusterPhyId={}|| errMsg=get OutstandingRequests metric failed, may be collect failed or zk mntr command not in whitelist.", clusterPhyId); + log.error("method=checkOutstandingRequests||clusterPhyId={}|| errMsg=get OutstandingRequests metric failed, may be collect failed or zk mntr command not in whitelist.", clusterPhyId); return null; } Integer amount = valueConfig.getAmount(); Double ratio = valueConfig.getRatio(); if (null == amount || null == ratio) { - log.error("class=HealthCheckZookeeperService||method=checkOutstandingRequests||clusterPhyId={}||result={}||errMsg=get valueConfig amount/ratio config failed", clusterPhyId,valueConfig); + log.error("method=checkOutstandingRequests||clusterPhyId={}||result={}||errMsg=get valueConfig amount/ratio config failed", clusterPhyId,valueConfig); return null; } @@ -163,7 +159,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { if (metricsResult.failed() || !metricsResult.hasData()) { log.error( - "class=HealthCheckZookeeperService||method=checkWatchCount||param={}||config={}||result={}||errMsg=get metrics failed", + "method=checkWatchCount||param={}||config={}||result={}||errMsg=get metrics failed", param, valueConfig, metricsResult ); return null; @@ -199,7 +195,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { if (metricsResult.failed() || !metricsResult.hasData()) { log.error( - "class=HealthCheckZookeeperService||method=checkAliveConnections||param={}||config={}||result={}||errMsg=get metrics failed", + "method=checkAliveConnections||param={}||config={}||result={}||errMsg=get metrics failed", param, valueConfig, metricsResult ); return null; @@ -235,7 +231,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { if (metricsResult.failed() || !metricsResult.hasData()) { log.error( - "class=HealthCheckZookeeperService||method=checkApproximateDataSize||param={}||config={}||result={}||errMsg=get metrics failed", + "method=checkApproximateDataSize||param={}||config={}||result={}||errMsg=get metrics failed", param, valueConfig, metricsResult ); return null; @@ -271,7 +267,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { if (metricsResult.failed() || !metricsResult.hasData()) { log.error( - "class=HealthCheckZookeeperService||method=checkSentRate||param={}||config={}||result={}||errMsg=get metrics failed", + "method=checkSentRate||param={}||config={}||result={}||errMsg=get metrics failed", param, valueConfig, metricsResult ); return null; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java index 6aaddcdb..66a48904 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java @@ -1,25 +1,27 @@ package com.xiaojukeji.know.streaming.km.core.service.health.checkresult; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAggResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; +import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; -import java.util.Date; import java.util.List; import java.util.Map; public interface HealthCheckResultService { - int replace(HealthCheckResult healthCheckResult); + List getHealthCheckAggResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum, String resNme); + List getHealthCheckAggResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum); - int deleteByUpdateTimeBeforeInDB(Long clusterPhyId, Date beforeTime); + List listAll(); + List listCheckResult(Long clusterPhyId); + List listCheckResult(Long clusterPhyId, Integer resDimension); + List listCheckResult(Long clusterPhyId, Integer resDimension, String resNme); - List getClusterHealthCheckResult(Long clusterPhyId); - - List getClusterResourcesHealthCheckResult(Long clusterPhyId, Integer resDimension); - - List getResHealthCheckResult(Long clusterPhyId, Integer dimension, String resNme); + List listCheckResultFromCache(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum); + List listCheckResultFromCache(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum, String resNme); Map getClusterHealthConfig(Long clusterPhyId); - void batchReplace(Long clusterPhyId, List healthCheckResults); + void batchReplace(Long clusterPhyId, Integer dimension, List healthCheckResults); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java index cad2f396..796ef2d8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java @@ -5,13 +5,16 @@ import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.google.common.collect.Lists; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAggResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.po.config.PlatformClusterConfigPO; 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.config.ConfigGroupEnum; +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.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.core.service.config.PlatformClusterConfigService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.persistence.mysql.health.HealthCheckResultDAO; @@ -22,7 +25,7 @@ import java.util.*; @Service public class HealthCheckResultServiceImpl implements HealthCheckResultService { - private static final ILog log = LogFactory.getLog(HealthCheckResultServiceImpl.class); + private static final ILog LOGGER = LogFactory.getLog(HealthCheckResultServiceImpl.class); @Autowired private HealthCheckResultDAO healthCheckResultDAO; @@ -31,42 +34,71 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { private PlatformClusterConfigService platformClusterConfigService; @Override - public int replace(HealthCheckResult healthCheckResult) { - return healthCheckResultDAO.replace(ConvertUtil.obj2Obj(healthCheckResult, HealthCheckResultPO.class)); + public List getHealthCheckAggResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum, String resNme) { + List poList = this.listCheckResultFromCache(clusterPhyId, dimensionEnum, resNme); + + return this.convert2HealthCheckAggResultList(poList, dimensionEnum.getDimension()); } @Override - public int deleteByUpdateTimeBeforeInDB(Long clusterPhyId, Date beforeTime) { - LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); - lambdaQueryWrapper.eq(HealthCheckResultPO::getClusterPhyId, clusterPhyId); - lambdaQueryWrapper.le(HealthCheckResultPO::getUpdateTime, beforeTime); - return healthCheckResultDAO.delete(lambdaQueryWrapper); + public List getHealthCheckAggResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) { + List poList = this.listCheckResultFromCache(clusterPhyId, dimensionEnum); + + return this.convert2HealthCheckAggResultList(poList, dimensionEnum.getDimension()); } @Override - public List getClusterHealthCheckResult(Long clusterPhyId) { + public List listAll() { + return healthCheckResultDAO.selectList(null); + } + + @Override + public List listCheckResult(Long clusterPhyId) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(HealthCheckResultPO::getClusterPhyId, clusterPhyId); + return healthCheckResultDAO.selectList(lambdaQueryWrapper); } @Override - public List getClusterResourcesHealthCheckResult(Long clusterPhyId, Integer resDimension) { + public List listCheckResult(Long clusterPhyId, Integer resDimension) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(HealthCheckResultPO::getDimension, resDimension); lambdaQueryWrapper.eq(HealthCheckResultPO::getClusterPhyId, clusterPhyId); + return healthCheckResultDAO.selectList(lambdaQueryWrapper); } @Override - public List getResHealthCheckResult(Long clusterPhyId, Integer resDimension, String resNme) { + public List listCheckResult(Long clusterPhyId, Integer resDimension, String resNme) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(HealthCheckResultPO::getDimension, resDimension); lambdaQueryWrapper.eq(HealthCheckResultPO::getClusterPhyId, clusterPhyId); lambdaQueryWrapper.eq(HealthCheckResultPO::getResName, resNme); + return healthCheckResultDAO.selectList(lambdaQueryWrapper); } + @Override + public List listCheckResultFromCache(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) { + Map> poMap = DataBaseDataLocalCache.getHealthCheckResults(clusterPhyId, dimensionEnum); + if (poMap != null) { + return poMap.values().stream().collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll); + } + + return new ArrayList<>(); + } + + @Override + public List listCheckResultFromCache(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum, String resNme) { + Map> poMap = DataBaseDataLocalCache.getHealthCheckResults(clusterPhyId, dimensionEnum); + if (poMap != null) { + return poMap.getOrDefault(resNme, new ArrayList<>()); + } + + return new ArrayList<>(); + } + @Override public Map getClusterHealthConfig(Long clusterPhyId) { Map configPOMap = platformClusterConfigService.getByClusterAndGroupWithoutDefault(clusterPhyId, ConfigGroupEnum.HEALTH.name()); @@ -76,7 +108,7 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { try { HealthCheckNameEnum nameEnum = HealthCheckNameEnum.getByName(po.getValueName()); if (HealthCheckNameEnum.UNKNOWN.equals(nameEnum)) { - log.warn("method=getClusterHealthConfig||config={}||errMsg=config name illegal", po); + LOGGER.warn("method=getClusterHealthConfig||config={}||errMsg=config name illegal", po); continue; } @@ -85,22 +117,37 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { healthConfig.setClusterPhyId(clusterPhyId); configMap.put(po.getValueName(), healthConfig); } catch (Exception e) { - log.error("method=getClusterHealthConfig||config={}||errMsg=exception!", po, e); + LOGGER.error("method=getClusterHealthConfig||config={}||errMsg=exception!", po, e); } } return configMap; } @Override - public void batchReplace(Long clusterPhyId, List healthCheckResults) { + public void batchReplace(Long clusterPhyId, Integer dimension, List healthCheckResults) { List> healthCheckResultPartitions = Lists.partition(healthCheckResults, Constant.PER_BATCH_MAX_VALUE); for (List checkResultPartition : healthCheckResultPartitions) { List healthCheckResultPos = ConvertUtil.list2List(checkResultPartition, HealthCheckResultPO.class); try { healthCheckResultDAO.batchReplace(healthCheckResultPos); } catch (Exception e) { - log.error("method=batchReplace||clusterPhyId={}||checkResultList={}||errMsg=exception!", clusterPhyId, healthCheckResultPos, e); + LOGGER.error("method=batchReplace||clusterPhyId={}||checkResultList={}||errMsg=exception!", clusterPhyId, healthCheckResultPos, e); } } } + + private List convert2HealthCheckAggResultList(List poList, Integer dimensionCode) { + Map /*检查结果列表*/> groupByCheckNamePOMap = new HashMap<>(); + for (HealthCheckResultPO po: poList) { + groupByCheckNamePOMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); + groupByCheckNamePOMap.get(po.getConfigName()).add(po); + } + + List stateList = new ArrayList<>(); + for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimensionCode(dimensionCode)) { + stateList.add(new HealthCheckAggResult(nameEnum, groupByCheckNamePOMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); + } + + return stateList; + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java index 3ef0a82d..35692cb8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java @@ -9,42 +9,18 @@ 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 getClusterHealthResult(Long clusterPhyId); - - /** - * 获取集群某个维度健康检查结果 - */ List getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum); - - /** - * 获取集群某个资源的健康检查结果 - */ List getResHealthResult(Long clusterPhyId, Integer dimension, String resNme); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java index 8cb44fd4..5669f300 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java @@ -14,22 +14,16 @@ import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService; -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 java.util.*; +import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.BROKER_METRIC_HEALTH_STATE; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.GROUP_METRIC_HEALTH_CHECK_TOTAL; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; -import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.TOPIC_METRIC_HEALTH_CHECK_TOTAL; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems.*; @@ -49,7 +43,7 @@ public class HealthStateServiceImpl implements HealthStateService { ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); // 集群维度指标 - List resultList = this.getDimensionHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.CLUSTER); + List resultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.CLUSTER); if (ValidateUtils.isEmptyList(resultList)) { metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER, 0.0f); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER, 0.0f); @@ -98,16 +92,16 @@ public class HealthStateServiceImpl implements HealthStateService { @Override public BrokerMetrics calBrokerHealthMetrics(Long clusterPhyId, Integer brokerId) { - List healthScoreResultList = this.getResHealthResult(clusterPhyId, HealthCheckDimensionEnum.BROKER.getDimension(), String.valueOf(brokerId)); + List aggResultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.BROKER, String.valueOf(brokerId)); BrokerMetrics metrics = new BrokerMetrics(clusterPhyId, brokerId); - if (ValidateUtils.isEmptyList(healthScoreResultList)) { + if (ValidateUtils.isEmptyList(aggResultList)) { metrics.getMetrics().put(BROKER_METRIC_HEALTH_STATE, (float)HealthStateEnum.GOOD.getDimension()); 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_CHECK_PASSED, getHealthCheckResultPassed(healthScoreResultList)); - metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size())); + metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(aggResultList)); + metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_TOTAL, (float)aggResultList.size()); // 计算健康状态 Broker broker = brokerService.getBrokerFromCacheFirst(clusterPhyId, brokerId); @@ -117,7 +111,7 @@ public class HealthStateServiceImpl implements HealthStateService { } else if (!broker.alive()) { metrics.getMetrics().put(BROKER_METRIC_HEALTH_STATE, (float)HealthStateEnum.DEAD.getDimension()); } else { - metrics.getMetrics().put(BROKER_METRIC_HEALTH_STATE, (float)this.calHealthScoreResultState(healthScoreResultList).getDimension()); + metrics.getMetrics().put(BROKER_METRIC_HEALTH_STATE, (float)this.calHealthState(aggResultList).getDimension()); } } @@ -126,17 +120,17 @@ public class HealthStateServiceImpl implements HealthStateService { @Override public TopicMetrics calTopicHealthMetrics(Long clusterPhyId, String topicName) { - List healthScoreResultList = this.getResHealthResult(clusterPhyId, HealthCheckDimensionEnum.TOPIC.getDimension(), topicName); + List aggResultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.TOPIC, topicName); TopicMetrics metrics = new TopicMetrics(topicName, clusterPhyId,true); - if (ValidateUtils.isEmptyList(healthScoreResultList)) { + if (ValidateUtils.isEmptyList(aggResultList)) { metrics.getMetrics().put(TOPIC_METRIC_HEALTH_STATE, (float)HealthStateEnum.GOOD.getDimension()); 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_STATE, (float)this.calHealthScoreResultState(healthScoreResultList).getDimension()); - metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckResultPassed(healthScoreResultList)); - metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size())); + metrics.getMetrics().put(TOPIC_METRIC_HEALTH_STATE, (float)this.calHealthState(aggResultList).getDimension()); + metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(aggResultList)); + metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_TOTAL, (float)aggResultList.size()); } return metrics; @@ -144,17 +138,17 @@ public class HealthStateServiceImpl implements HealthStateService { @Override public GroupMetrics calGroupHealthMetrics(Long clusterPhyId, String groupName) { - List healthScoreResultList = this.getResHealthResult(clusterPhyId, HealthCheckDimensionEnum.GROUP.getDimension(), groupName); + List aggResultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.GROUP, groupName); GroupMetrics metrics = new GroupMetrics(clusterPhyId, groupName, true); - if (ValidateUtils.isEmptyList(healthScoreResultList)) { + if (ValidateUtils.isEmptyList(aggResultList)) { metrics.getMetrics().put(GROUP_METRIC_HEALTH_STATE, (float)HealthStateEnum.GOOD.getDimension()); 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_STATE, (float)this.calHealthScoreResultState(healthScoreResultList).getDimension()); - metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_PASSED, getHealthCheckResultPassed(healthScoreResultList)); - metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size())); + metrics.getMetrics().put(GROUP_METRIC_HEALTH_STATE, (float)this.calHealthState(aggResultList).getDimension()); + metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(aggResultList)); + metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_TOTAL, (float)aggResultList.size()); } return metrics; @@ -162,15 +156,15 @@ public class HealthStateServiceImpl implements HealthStateService { @Override public ZookeeperMetrics calZookeeperHealthMetrics(Long clusterPhyId) { - List resultList = this.getDimensionHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.ZOOKEEPER); + List aggResultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.ZOOKEEPER); ZookeeperMetrics metrics = new ZookeeperMetrics(clusterPhyId); - if (ValidateUtils.isEmptyList(resultList)) { + if (ValidateUtils.isEmptyList(aggResultList)) { metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED, 0.0f); metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL, 0.0f); } else { - metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(resultList)); - metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL, (float)resultList.size()); + metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(aggResultList)); + metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL, (float)aggResultList.size()); } if (zookeeperService.allServerDown(clusterPhyId)) { @@ -186,88 +180,29 @@ public class HealthStateServiceImpl implements HealthStateService { } // 服务未挂时,依据检查结果计算状态 - metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_STATE, (float)this.calHealthState(resultList).getDimension()); + metrics.getMetrics().put(ZOOKEEPER_METRIC_HEALTH_STATE, (float)this.calHealthState(aggResultList).getDimension()); return metrics; } @Override public List getClusterHealthResult(Long clusterPhyId) { - List poList = healthCheckResultService.getClusterHealthCheckResult(clusterPhyId); + List poList = healthCheckResultService.listCheckResult(clusterPhyId); - // <检查项,<检查结果>> - Map> checkResultMap = new HashMap<>(); - for (HealthCheckResultPO po: poList) { - checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); - checkResultMap.get(po.getConfigName()).add(po); - } - - Map configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId); - - List healthScoreResultList = new ArrayList<>(); - for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.values()) { - BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName()); - if (baseConfig == null) { - continue; - } - - healthScoreResultList.add(new HealthScoreResult( - nameEnum, - baseConfig, - checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>())) - ); - } - - return healthScoreResultList; + return this.convert2HealthScoreResultList(clusterPhyId, poList, null); } @Override public List getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) { - List poList = healthCheckResultService.getClusterResourcesHealthCheckResult(clusterPhyId, dimensionEnum.getDimension()); + List poList = healthCheckResultService.listCheckResult(clusterPhyId, dimensionEnum.getDimension()); - // <检查项,<通过的数量,不通过的数量>> - Map> checkResultMap = new HashMap<>(); - for (HealthCheckResultPO po: poList) { - checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); - checkResultMap.get(po.getConfigName()).add(po); - } - - Map configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId); - - List healthScoreResultList = new ArrayList<>(); - for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimension(dimensionEnum)) { - BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName()); - if (baseConfig == null) { - continue; - } - - healthScoreResultList.add(new HealthScoreResult(nameEnum, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); - } - - return healthScoreResultList; + return this.convert2HealthScoreResultList(clusterPhyId, poList, dimensionEnum.getDimension()); } @Override public List getResHealthResult(Long clusterPhyId, Integer dimension, String resNme) { - List poList = healthCheckResultService.getResHealthCheckResult(clusterPhyId, dimension, resNme); - Map> checkResultMap = new HashMap<>(); - for (HealthCheckResultPO po: poList) { - checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); - checkResultMap.get(po.getConfigName()).add(po); - } + List poList = healthCheckResultService.listCheckResult(clusterPhyId, dimension, resNme); - Map configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId); - - List healthScoreResultList = new ArrayList<>(); - for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimensionCode(dimension)) { - BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName()); - if (baseConfig == null) { - continue; - } - - healthScoreResultList.add(new HealthScoreResult(nameEnum, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); - } - - return healthScoreResultList; + return this.convert2HealthScoreResultList(clusterPhyId, poList, dimension); } @@ -275,7 +210,7 @@ public class HealthStateServiceImpl implements HealthStateService { private ClusterMetrics calClusterTopicsHealthMetrics(Long clusterPhyId) { - List resultList = this.getDimensionHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.TOPIC); + List resultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.TOPIC); ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); if (ValidateUtils.isEmptyList(resultList)) { @@ -292,7 +227,7 @@ public class HealthStateServiceImpl implements HealthStateService { } private ClusterMetrics calClusterGroupsHealthMetrics(Long clusterPhyId) { - List resultList = this.getDimensionHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.GROUP); + List resultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.GROUP); ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); if (ValidateUtils.isEmptyList(resultList)) { @@ -309,7 +244,7 @@ public class HealthStateServiceImpl implements HealthStateService { } private ClusterMetrics calClusterBrokersHealthMetrics(Long clusterPhyId) { - List resultList = this.getDimensionHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.BROKER); + List resultList = healthCheckResultService.getHealthCheckAggResult(clusterPhyId, HealthCheckDimensionEnum.BROKER); ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); if (ValidateUtils.isEmptyList(resultList)) { @@ -337,29 +272,45 @@ public class HealthStateServiceImpl implements HealthStateService { return metrics; } - private List getDimensionHealthCheckAggResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) { - List poList = healthCheckResultService.getClusterResourcesHealthCheckResult(clusterPhyId, dimensionEnum.getDimension()); - Map /*检查结果列表*/> groupByCheckNamePOMap = new HashMap<>(); + /**************************************************** 聚合数据 ****************************************************/ + + public List convert2HealthScoreResultList(Long clusterPhyId, List poList, Integer dimensionCode) { + Map> checkResultMap = new HashMap<>(); for (HealthCheckResultPO po: poList) { - groupByCheckNamePOMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); - groupByCheckNamePOMap.get(po.getConfigName()).add(po); + checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); + checkResultMap.get(po.getConfigName()).add(po); } - List stateList = new ArrayList<>(); - for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimension(dimensionEnum)) { - stateList.add(new HealthCheckAggResult(nameEnum, groupByCheckNamePOMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); + Map configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId); + + List nameEnums = + dimensionCode == null? + Arrays.stream(HealthCheckNameEnum.values()).collect(Collectors.toList()): HealthCheckNameEnum.getByDimensionCode(dimensionCode); + + List resultList = new ArrayList<>(); + for (HealthCheckNameEnum nameEnum: nameEnums) { + BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName()); + if (baseConfig == null) { + continue; + } + + resultList.add(new HealthScoreResult(nameEnum, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); } - return stateList; + return resultList; } - private float getHealthCheckPassed(List resultList){ - if(ValidateUtils.isEmptyList(resultList)) { + + /**************************************************** 计算指标 ****************************************************/ + + + private float getHealthCheckPassed(List aggResultList){ + if(ValidateUtils.isEmptyList(aggResultList)) { return 0f; } - return Float.valueOf(resultList.stream().filter(elem -> elem.getPassed()).count()); + return Float.valueOf(aggResultList.stream().filter(elem -> elem.getPassed()).count()); } private HealthStateEnum calHealthState(List resultList) { @@ -380,29 +331,4 @@ public class HealthStateServiceImpl implements HealthStateService { return existNotPassed? HealthStateEnum.MEDIUM: HealthStateEnum.GOOD; } - - private float getHealthCheckResultPassed(List healthScoreResultList){ - if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;} - - return Float.valueOf(healthScoreResultList.stream().filter(elem -> elem.getPassed()).count()); - } - - private HealthStateEnum calHealthScoreResultState(List resultList) { - if(ValidateUtils.isEmptyList(resultList)) { - return HealthStateEnum.GOOD; - } - - boolean existNotPassed = false; - for (HealthScoreResult aggResult: resultList) { - if (aggResult.getCheckNameEnum().isAvailableChecker() && !aggResult.getPassed()) { - return HealthStateEnum.POOR; - } - - if (!aggResult.getPassed()) { - existNotPassed = true; - } - } - - return existNotPassed? HealthStateEnum.MEDIUM: HealthStateEnum.GOOD; - } } diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java index f8c2185b..bc367a71 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/AbstractHealthCheckTask.java @@ -3,30 +3,31 @@ package com.xiaojukeji.know.streaming.km.task.kafka.health; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.service.CollectThreadPoolService; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; 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.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.task.kafka.metrics.AbstractAsyncMetricsDispatchTask; import org.springframework.beans.factory.annotation.Autowired; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; public abstract class AbstractHealthCheckTask extends AbstractAsyncMetricsDispatchTask { - private static final ILog log = LogFactory.getLog(AbstractHealthCheckTask.class); + private static final ILog LOGGER = LogFactory.getLog(AbstractHealthCheckTask.class); @Autowired private HealthCheckResultService healthCheckResultService; + @Autowired + private CollectThreadPoolService collectThreadPoolService; + public abstract AbstractHealthCheckService getCheckService(); @Override @@ -38,32 +39,37 @@ public abstract class AbstractHealthCheckTask extends AbstractAsyncMetricsDispat // 获取配置,<配置名,配置信息> Map healthConfigMap = healthCheckResultService.getClusterHealthConfig(clusterPhy.getId()); - // 检查结果 - List resultList = new ArrayList<>(); - - // 遍历Check-Service + // 获取资源列表 List paramList = this.getCheckService().getResList(clusterPhy.getId()); + + // 检查结果 + List checkResultList = Collections.synchronizedList(new ArrayList<>()); if (ValidateUtils.isEmptyList(paramList)) { // 当前无该维度的资源,则直接设置为 - resultList.addAll(this.getNoResResult(clusterPhy.getId(), this.getCheckService(), healthConfigMap)); + checkResultList.addAll(this.getNoResResult(clusterPhy.getId(), this.getCheckService(), healthConfigMap)); } + // 获取合适的线程池 + FutureWaitUtil futureWaitUtil = collectThreadPoolService.selectSuitableFutureUtil(clusterPhy.getId() * 100 + this.getCheckService().getHealthCheckDimensionEnum().getDimension()); + // 遍历资源 for (ClusterParam clusterParam: paramList) { - resultList.addAll(this.checkAndGetResult(clusterParam, healthConfigMap)); + futureWaitUtil.runnableTask( + String.format("class=%s||method=calAndUpdateHealthCheckResult||clusterId=%d", this.getCheckService().getClass().getSimpleName(), clusterPhy.getId()), + 30000, + () -> checkResultList.addAll(this.checkAndGetResult(clusterParam, healthConfigMap)) + ); } - try { - healthCheckResultService.batchReplace(clusterPhy.getId(), resultList); - } catch (Exception e) { - log.error("class=AbstractHealthCheckTask||method=processSubTask||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); - } + futureWaitUtil.waitExecute(30000); - // 删除10分钟之前的检查结果 try { - healthCheckResultService.deleteByUpdateTimeBeforeInDB(clusterPhy.getId(), new Date(triggerTimeUnitMs - 20 * 60 * 1000)); + healthCheckResultService.batchReplace(clusterPhy.getId(), this.getCheckService().getHealthCheckDimensionEnum().getDimension(), checkResultList); } catch (Exception e) { - log.error("class=AbstractHealthCheckTask||method=processSubTask||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); + LOGGER.error( + "extendClass={}||method=calAndUpdateHealthCheckResult||clusterPhyId={}||errMsg=exception!", + this.getCheckService().getClass().getSimpleName(), clusterPhy.getId(), e + ); } return TaskResult.SUCCESS; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java index c3226feb..167c8c57 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/health/TopicHealthCheckTask.java @@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; @AllArgsConstructor @Task(name = "TopicHealthCheckTask", description = "Topic健康检查", - cron = "0 0/1 * * * ? *", + cron = "30 0/1 * * * ? *", autoRegister = true, consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) From ac30436324d437a6d22c156840c7e4617ff465fb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 5 Dec 2022 16:29:30 +0800 Subject: [PATCH 047/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=81=A5=E5=BA=B7=E5=B7=A1=E6=A3=80=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E6=97=B6=E5=87=BA=E7=8E=B0=E6=AD=BB=E9=94=81=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98(#728)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/HealthCheckResultServiceImpl.java | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java index 796ef2d8..2689c50c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java @@ -3,13 +3,11 @@ package com.xiaojukeji.know.streaming.km.core.service.health.checkresult.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.google.common.collect.Lists; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAggResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.po.config.PlatformClusterConfigPO; 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.config.ConfigGroupEnum; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum; @@ -19,6 +17,7 @@ import com.xiaojukeji.know.streaming.km.core.service.config.PlatformClusterConfi import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.persistence.mysql.health.HealthCheckResultDAO; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import java.util.*; @@ -125,15 +124,37 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { @Override public void batchReplace(Long clusterPhyId, Integer dimension, List healthCheckResults) { - List> healthCheckResultPartitions = Lists.partition(healthCheckResults, Constant.PER_BATCH_MAX_VALUE); - for (List checkResultPartition : healthCheckResultPartitions) { - List healthCheckResultPos = ConvertUtil.list2List(checkResultPartition, HealthCheckResultPO.class); + List inDBList = this.listCheckResult(clusterPhyId, dimension); + + // list 转 map + Map inDBMap = new HashMap<>(inDBList.size()); + inDBList.forEach(elem -> inDBMap.put(elem.getConfigName() + elem.getResName(), elem)); + + for (HealthCheckResult checkResult: healthCheckResults) { + HealthCheckResultPO inDB = inDBMap.remove(checkResult.getConfigName() + checkResult.getResName()); + try { - healthCheckResultDAO.batchReplace(healthCheckResultPos); - } catch (Exception e) { - LOGGER.error("method=batchReplace||clusterPhyId={}||checkResultList={}||errMsg=exception!", clusterPhyId, healthCheckResultPos, e); + HealthCheckResultPO newPO = ConvertUtil.obj2Obj(checkResult, HealthCheckResultPO.class); + if (inDB == null) { + healthCheckResultDAO.insert(newPO); + } else { + newPO.setId(inDB.getId()); + newPO.setUpdateTime(new Date()); + healthCheckResultDAO.updateById(newPO); + } + } catch (DuplicateKeyException dke) { + // ignore } } + + inDBMap.values().forEach(elem -> { + if (System.currentTimeMillis() - elem.getUpdateTime().getTime() <= 1200000) { + // 20分钟之内的数据,不进行删除 + return; + } + + healthCheckResultDAO.deleteById(elem.getId()); + }); } private List convert2HealthCheckAggResultList(List poList, Integer dimensionCode) { From 3a3141a3615fdfa8093c15f5844546b7ef24b19e Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 16:40:52 +0800 Subject: [PATCH 048/150] =?UTF-8?q?=E8=B0=83=E6=95=B4ZK=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E7=9A=84=E9=87=87=E9=9B=86=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/task/kafka/metrics/ZookeeperMetricCollectorTask.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java index 59aa86da..749a687f 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metrics/ZookeeperMetricCollectorTask.java @@ -3,8 +3,6 @@ package com.xiaojukeji.know.streaming.km.task.kafka.metrics; import com.didiglobal.logi.job.annotation.Task; import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.metric.kafka.ZookeeperMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import org.springframework.beans.factory.annotation.Autowired; @@ -14,13 +12,11 @@ import org.springframework.beans.factory.annotation.Autowired; */ @Task(name = "ZookeeperMetricCollectorTask", description = "Zookeeper指标采集任务", - cron = "0 0/1 * * * ? *", + cron = "50 0/1 * * * ? *", autoRegister = true, consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) public class ZookeeperMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { - private static final ILog log = LogFactory.getLog(ZookeeperMetricCollectorTask.class); - @Autowired private ZookeeperMetricCollector zookeeperMetricCollector; From 9735c4f88522afed18ce719ee0b1715c4d87868b Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 16:41:27 +0800 Subject: [PATCH 049/150] =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E9=87=87=E9=9B=86=E7=9A=84=E6=8C=87=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../version/metrics/kafka/ClusterMetricVersionItems.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java index 2c7408a9..c19bebc6 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java @@ -75,7 +75,6 @@ public class ClusterMetricVersionItems extends BaseMetricVersionMetric { public static final String CLUSTER_METRIC_PARTITION_MIN_ISR_E = "PartitionMinISR_E"; public static final String CLUSTER_METRIC_PARTITION_URP = "PartitionURP"; public static final String CLUSTER_METRIC_MESSAGES_IN = "MessagesIn"; - public static final String CLUSTER_METRIC_MESSAGES = "Messages"; public static final String CLUSTER_METRIC_LEADER_MESSAGES = "LeaderMessages"; public static final String CLUSTER_METRIC_BYTES_IN = "BytesIn"; public static final String CLUSTER_METRIC_BYTES_IN_5_MIN = "BytesIn_min_5"; @@ -293,11 +292,6 @@ public class ClusterMetricVersionItems extends BaseMetricVersionMetric { .name(CLUSTER_METRIC_LEADER_MESSAGES).unit("条").desc("集群中leader总的消息条数").category(CATEGORY_CLUSTER) .extend( buildMethodExtend( CLUSTER_METHOD_GET_MESSAGE_SIZE ))); - // Messages 指标 - itemList.add( buildAllVersionsItem() - .name(CLUSTER_METRIC_MESSAGES).unit("条").desc("集群总的消息条数").category(CATEGORY_CLUSTER) - .extend( buildMethodExtend( CLUSTER_METHOD_GET_MESSAGE_SIZE ))); - // BytesInPerSec 指标 itemList.add( buildAllVersionsItem() .name(CLUSTER_METRIC_BYTES_IN).unit(BYTE_PER_SEC).desc("集群的每秒写入字节数").category(CATEGORY_CLUSTER) From c3d47d309336bf7ebd04da7b2ee2f0cbc9ff98f5 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 16:46:11 +0800 Subject: [PATCH 050/150] =?UTF-8?q?=E6=B1=A0=E5=8C=96KafkaAdminClient?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8DKafkaAdminClient=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/kafka/KafkaAdminClient.java | 79 ++++++++++++++----- km-rest/src/main/resources/application.yml | 3 +- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminClient.java index 46fb1dae..20447798 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminClient.java @@ -1,31 +1,39 @@ package com.xiaojukeji.know.streaming.km.persistence.kafka; +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.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.persistence.AbstractClusterLoadedChangedHandler; import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; -import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.AdminClientConfig; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; -@Slf4j @Component public class KafkaAdminClient extends AbstractClusterLoadedChangedHandler { - private static final Map KAFKA_ADMIN_CLIENT_MAP = new ConcurrentHashMap<>(); + private static final ILog LOGGER = LogFactory.getLog(KafkaAdminClient.class); + + @Value("${client-pool.kafka-admin.client-cnt:1}") + private Integer clientCnt; + + private static final Map> KAFKA_ADMIN_CLIENT_MAP = new ConcurrentHashMap<>(); public AdminClient getClient(Long clusterPhyId) throws NotExistException { - AdminClient adminClient = KAFKA_ADMIN_CLIENT_MAP.get(clusterPhyId); - if (adminClient != null) { - return adminClient; + List adminClientList = KAFKA_ADMIN_CLIENT_MAP.get(clusterPhyId); + if (adminClientList != null) { + return adminClientList.get((int)(System.currentTimeMillis() % clientCnt)); } - adminClient = this.createKafkaAdminClient(clusterPhyId); + AdminClient adminClient = this.createKafkaAdminClient(clusterPhyId); if (adminClient == null) { throw new NotExistException("kafka admin-client not exist due to create failed"); } @@ -61,18 +69,20 @@ public class KafkaAdminClient extends AbstractClusterLoadedChangedHandler { try { modifyClientMapLock.lock(); - AdminClient adminClient = KAFKA_ADMIN_CLIENT_MAP.remove(clusterPhyId); - if (adminClient == null) { + List adminClientList = KAFKA_ADMIN_CLIENT_MAP.remove(clusterPhyId); + if (adminClientList == null) { return; } - log.info("close kafka AdminClient starting, clusterPhyId:{}", clusterPhyId); + LOGGER.info("close kafka AdminClient starting, clusterPhyId:{}", clusterPhyId); - adminClient.close(); + boolean allSuccess = this.closeAdminClientList(adminClientList); - log.info("close kafka AdminClient success, clusterPhyId:{}", clusterPhyId); + if (allSuccess) { + LOGGER.info("close kafka AdminClient success, clusterPhyId:{}", clusterPhyId); + } } catch (Exception e) { - log.error("close kafka AdminClient failed, clusterPhyId:{}", clusterPhyId, e); + LOGGER.error("close kafka AdminClient failed, clusterPhyId:{}", clusterPhyId, e); } finally { modifyClientMapLock.unlock(); } @@ -88,29 +98,56 @@ public class KafkaAdminClient extends AbstractClusterLoadedChangedHandler { } private AdminClient createKafkaAdminClient(Long clusterPhyId, String bootstrapServers, Properties props) { + List adminClientList = null; try { modifyClientMapLock.lock(); - AdminClient adminClient = KAFKA_ADMIN_CLIENT_MAP.get(clusterPhyId); - if (adminClient != null) { - return adminClient; + adminClientList = KAFKA_ADMIN_CLIENT_MAP.get(clusterPhyId); + if (adminClientList != null) { + return adminClientList.get((int)(System.currentTimeMillis() % clientCnt)); } - log.debug("create kafka AdminClient starting, clusterPhyId:{} props:{}", clusterPhyId, props); + LOGGER.debug("create kafka AdminClient starting, clusterPhyId:{} props:{}", clusterPhyId, props); if (props == null) { props = new Properties(); } props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - KAFKA_ADMIN_CLIENT_MAP.put(clusterPhyId, AdminClient.create(props)); - log.info("create kafka AdminClient success, clusterPhyId:{}", clusterPhyId); + adminClientList = new ArrayList<>(); + for (int i = 0; i < clientCnt; ++i) { + adminClientList.add(AdminClient.create(props)); + } + + KAFKA_ADMIN_CLIENT_MAP.put(clusterPhyId, adminClientList); + + LOGGER.info("create kafka AdminClient success, clusterPhyId:{}", clusterPhyId); } catch (Exception e) { - log.error("create kafka AdminClient failed, clusterPhyId:{} props:{}", clusterPhyId, props, e); + LOGGER.error("create kafka AdminClient failed, clusterPhyId:{} props:{}", clusterPhyId, props, e); + + this.closeAdminClientList(adminClientList); } finally { modifyClientMapLock.unlock(); } - return KAFKA_ADMIN_CLIENT_MAP.get(clusterPhyId); + return KAFKA_ADMIN_CLIENT_MAP.get(clusterPhyId).get((int)(System.currentTimeMillis() % clientCnt)); + } + + private boolean closeAdminClientList(List adminClientList) { + if (adminClientList == null) { + return true; + } + + boolean allSuccess = true; + for (AdminClient adminClient: adminClientList) { + try { + adminClient.close(); + } catch (Exception e) { + // ignore + allSuccess = false; + } + } + + return allSuccess; } } diff --git a/km-rest/src/main/resources/application.yml b/km-rest/src/main/resources/application.yml index 3b01022e..c9753b7e 100644 --- a/km-rest/src/main/resources/application.yml +++ b/km-rest/src/main/resources/application.yml @@ -78,7 +78,8 @@ client-pool: max-idle-client-num: 20 # 最大空闲客户端数 max-total-client-num: 20 # 最大客户端数 borrow-timeout-unit-ms: 5000 # 租借超时时间,单位秒 - + kafka-admin: + client-cnt: 1 # 每个Kafka集群创建的KafkaAdminClient数 # ES客户端配置 es: From 0123ce4a5a4bd0b25d719b937c928edeaa204e22 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 16:47:07 +0800 Subject: [PATCH 051/150] =?UTF-8?q?=E4=BC=98=E5=8C=96Broker=E5=88=97?= =?UTF-8?q?=E8=A1=A8JMX=E7=AB=AF=E5=8F=A3=E7=9A=84=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/ClusterBrokersManagerImpl.java | 20 +++++++++++++++++-- .../km/common/bean/entity/broker/Broker.java | 4 ++-- .../broker/impl/BrokerServiceImpl.java | 19 +++++++++--------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java index 6b180126..7f98e86f 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java @@ -6,6 +6,8 @@ import com.xiaojukeji.know.streaming.km.biz.cluster.ClusterBrokersManager; import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterBrokersOverviewDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationSortDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.KafkaController; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; @@ -16,6 +18,8 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.res.ClusterBroker import com.xiaojukeji.know.streaming.km.common.bean.vo.kafkacontroller.KafkaControllerVO; import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.enums.SortTypeEnum; +import com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterRunStateEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil; import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; @@ -24,6 +28,7 @@ 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.kafkacontroller.KafkaControllerService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -83,9 +88,13 @@ public class ClusterBrokersManagerImpl implements ClusterBrokersManager { Map jmxConnectedMap = new HashMap<>(); brokerList.forEach(elem -> jmxConnectedMap.put(elem.getBrokerId(), kafkaJMXClient.getClientWithCheck(clusterPhyId, elem.getBrokerId()) != null)); + + ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(clusterPhyId); + // 格式转换 return PaginationResult.buildSuc( this.convert2ClusterBrokersOverviewVOList( + clusterPhy, paginationResult.getData().getBizData(), brokerList, metricsResult.getData(), @@ -169,7 +178,8 @@ public class ClusterBrokersManagerImpl implements ClusterBrokersManager { ); } - private List convert2ClusterBrokersOverviewVOList(List pagedBrokerIdList, + private List convert2ClusterBrokersOverviewVOList(ClusterPhy clusterPhy, + List pagedBrokerIdList, List brokerList, List metricsList, Topic groupTopic, @@ -185,9 +195,15 @@ public class ClusterBrokersManagerImpl implements ClusterBrokersManager { Broker broker = brokerMap.get(brokerId); BrokerMetrics brokerMetrics = metricsMap.get(brokerId); Boolean jmxConnected = jmxConnectedMap.get(brokerId); - voList.add(this.convert2ClusterBrokersOverviewVO(brokerId, broker, brokerMetrics, groupTopic, transactionTopic, kafkaController, jmxConnected)); } + + //补充非zk模式的JMXPort信息 + if (!clusterPhy.getRunState().equals(ClusterRunStateEnum.RUN_ZK.getRunState())) { + JmxConfig jmxConfig = ConvertUtil.str2ObjByJson(clusterPhy.getJmxProperties(), JmxConfig.class); + voList.forEach(elem -> elem.setJmxPort(jmxConfig.getJmxPort() == null ? -1 : jmxConfig.getJmxPort())); + } + return voList; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java index 513e926e..a1e39f34 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java @@ -66,13 +66,13 @@ public class Broker implements Serializable { */ private Map 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); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java index 47f3bdbe..0bd7f364 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java @@ -168,7 +168,7 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok allBrokerList = this.listAllBrokersAndUpdateCache(clusterPhyId); } - return allBrokerList.stream().filter( elem -> elem.alive()).collect(Collectors.toList()); + return allBrokerList.stream().filter(elem -> elem.alive()).collect(Collectors.toList()); } @Override @@ -234,11 +234,10 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok @Override public String getBrokerVersionFromKafka(Long clusterId, Integer brokerId) { - JmxConnectorWrap jmxConnectorWrap = kafkaJMXClient.getClient(clusterId, brokerId); - if (ValidateUtils.isNull(jmxConnectorWrap) || !jmxConnectorWrap.checkJmxConnectionAndInitIfNeed()) { + JmxConnectorWrap jmxConnectorWrap = kafkaJMXClient.getClientWithCheck(clusterId, brokerId); + if (jmxConnectorWrap == null) { return ""; } - try { return (String) jmxConnectorWrap.getAttribute(new ObjectName(JMX_SERVER_APP_INFO + ",id=" + brokerId), VERSION); } catch (Exception e) { @@ -331,7 +330,7 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok return Result.buildSuc(brokerList); } catch (Exception e) { - log.error("class=BrokerServiceImpl||method=getBrokersFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getBrokersFromZKClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.ZK_OPERATE_FAILED, e.getMessage()); } @@ -353,7 +352,7 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok return Result.buildSuc(newBrokerList); } catch (Exception e) { - log.error("class=BrokerServiceImpl||method=getBrokersFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + log.error("method=getBrokersFromAdminClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, e.getMessage()); } @@ -361,13 +360,13 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok private Broker getStartTimeAndBuildBroker(Long clusterPhyId, Node newNode, JmxConfig jmxConfig) { try { - Long startTime = jmxDAO.getServerStartTime(clusterPhyId, newNode.host(), null, jmxConfig); + Long startTime = jmxDAO.getServerStartTime(clusterPhyId, newNode.host(), jmxConfig.getJmxPort(), 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); + log.error("method=getStartTimeAndBuildBroker||clusterPhyId={}||brokerNode={}||jmxConfig={}||errMsg=exception!", clusterPhyId, newNode, jmxConfig, e); } - return Broker.buildFrom(clusterPhyId, newNode, null, jmxConfig); + return Broker.buildFrom(clusterPhyId, newNode, null); } } From 4386563c2c9415d9a9356c2d554ed082b6ba5161 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 16:47:53 +0800 Subject: [PATCH 052/150] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E9=87=87=E9=9B=86=E7=9A=84=E9=BB=98=E8=AE=A4=E8=80=97=E6=97=B6?= =?UTF-8?q?=E5=80=BC=EF=BC=8C=E4=BB=A5=E4=BE=BF=E5=9C=A8=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?Top=E6=8C=87=E6=A0=87=E6=97=B6=E5=8D=B3=E5=8F=AF=E7=9C=8B?= =?UTF-8?q?=E5=88=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiaojukeji/know/streaming/km/common/constant/Constant.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java index 59a52c1c..6cfdf338 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java @@ -59,7 +59,7 @@ public class Constant { * 采集指标的花费时间 */ public static final String COLLECT_METRICS_COST_TIME_METRICS_NAME = "CollectMetricsCostTimeUnitSec"; - public static final Float COLLECT_METRICS_ERROR_COST_TIME = -1.0F; + public static final Float COLLECT_METRICS_ERROR_COST_TIME = 100.001F; public static final Integer DEFAULT_RETRY_TIME = 3; From 36cf28539713090deca853d5d977134fdebb2476 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Mon, 5 Dec 2022 14:39:19 +0800 Subject: [PATCH 053/150] =?UTF-8?q?[Bug]=E4=BF=AE=E5=A4=8Dlogi-securiy?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=95=B0=E6=8D=AE=E5=BA=93=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E9=94=99=E8=AF=AF(#808)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/LogiSecurityDataSourceConfig.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/config/LogiSecurityDataSourceConfig.java diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/config/LogiSecurityDataSourceConfig.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/config/LogiSecurityDataSourceConfig.java new file mode 100644 index 00000000..5913f08b --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/config/LogiSecurityDataSourceConfig.java @@ -0,0 +1,35 @@ +package com.xiaojukeji.know.streaming.km.rest.config; + +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +/** + * @author wyb + * @date 2022/12/5 + */ +@Configuration("myLogiSecurityDataSourceConfig") +@MapperScan(basePackages = "com.didiglobal.logi.security.dao.mapper",sqlSessionFactoryRef = "logiSecuritySqlSessionFactory") +public class LogiSecurityDataSourceConfig { + + @Bean("logiSecuritySqlSessionFactory") + public SqlSessionFactory logiSecuritySqlSessionFactory( + @Qualifier("logiSecurityDataSource") DataSource dataSource) throws Exception { + MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); + bean.setDataSource(dataSource); + bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); + return bean.getObject(); + } + + @Bean("logiSecuritySqlSessionTemplate") + public SqlSessionTemplate logiSecuritySqlSessionTemplate( + @Qualifier("logiSecuritySqlSessionFactory") SqlSessionFactory sqlSessionFactory) { + return new SqlSessionTemplate(sqlSessionFactory); + } +} From 5b3f3e55751892a482f44de1616119df1d2e522c Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 17:26:25 +0800 Subject: [PATCH 054/150] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E5=85=A5ES=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/collector/sink/{ => kafka}/BrokerMetricESSender.java | 5 +++-- .../collector/sink/{ => kafka}/ClusterMetricESSender.java | 6 +++--- .../km/collector/sink/{ => kafka}/GroupMetricESSender.java | 6 +++--- .../collector/sink/{ => kafka}/PartitionMetricESSender.java | 5 +++-- .../collector/sink/{ => kafka}/ReplicaMetricESSender.java | 5 +++-- .../km/collector/sink/{ => kafka}/TopicMetricESSender.java | 6 +++--- .../collector/sink/{ => kafka}/ZookeeperMetricESSender.java | 5 +++-- 7 files changed, 21 insertions(+), 17 deletions(-) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/BrokerMetricESSender.java (79%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/ClusterMetricESSender.java (79%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/GroupMetricESSender.java (79%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/PartitionMetricESSender.java (80%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/ReplicaMetricESSender.java (79%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/TopicMetricESSender.java (79%) rename km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/{ => kafka}/ZookeeperMetricESSender.java (80%) diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/BrokerMetricESSender.java similarity index 79% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/BrokerMetricESSender.java index 57d43ad8..1c074df5 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/BrokerMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/BrokerMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BrokerMetricEvent; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BrokerMetricPO; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; @@ -10,7 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.BROKER_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.BROKER_INDEX; @Component public class BrokerMetricESSender extends AbstractMetricESSender implements ApplicationListener { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ClusterMetricESSender.java similarity index 79% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ClusterMetricESSender.java index 478a27bd..70ee3d89 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ClusterMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ClusterMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.ClusterMetricEvent; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ClusterMetricPO; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; @@ -10,8 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; - -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.CLUSTER_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CLUSTER_INDEX; @Component public class ClusterMetricESSender extends AbstractMetricESSender implements ApplicationListener { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/GroupMetricESSender.java similarity index 79% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/GroupMetricESSender.java index e7e622c5..2192ad90 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/GroupMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/GroupMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.GroupMetricEvent; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.GroupMetricPO; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; @@ -10,8 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; - -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.GROUP_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.GROUP_INDEX; @Component public class GroupMetricESSender extends AbstractMetricESSender implements ApplicationListener { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/PartitionMetricESSender.java similarity index 80% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/PartitionMetricESSender.java index 460d5e92..40087e28 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/PartitionMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/PartitionMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.PartitionMetricEvent; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.PartitionMetricPO; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; @@ -10,7 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.PARTITION_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.PARTITION_INDEX; @Component public class PartitionMetricESSender extends AbstractMetricESSender implements ApplicationListener { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java similarity index 79% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java index 9b39f3af..d7b74905 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ReplicaMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.ReplicaMetricEvent; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ReplicationMetricPO; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; @@ -10,7 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.REPLICATION_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.REPLICATION_INDEX; @Component public class ReplicaMetricESSender extends AbstractMetricESSender implements ApplicationListener { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/TopicMetricESSender.java similarity index 79% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/TopicMetricESSender.java index 311a26fa..fb3cff7d 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/TopicMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/TopicMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.*; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.*; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; @@ -10,8 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; - -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.TOPIC_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.TOPIC_INDEX; @Component public class TopicMetricESSender extends AbstractMetricESSender implements ApplicationListener { diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ZookeeperMetricESSender.java similarity index 80% rename from km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java rename to km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ZookeeperMetricESSender.java index f2f254d6..c93f4860 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/ZookeeperMetricESSender.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ZookeeperMetricESSender.java @@ -1,7 +1,8 @@ -package com.xiaojukeji.know.streaming.km.collector.sink; +package com.xiaojukeji.know.streaming.km.collector.sink.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.ZookeeperMetricEvent; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ZookeeperMetricPO; @@ -10,7 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.ZOOKEEPER_INDEX; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.ZOOKEEPER_INDEX; @Component public class ZookeeperMetricESSender extends AbstractMetricESSender implements ApplicationListener { From cc2a590b33a2733609ed374925817e67764a8e71 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 17:34:15 +0800 Subject: [PATCH 055/150] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=9A=84KSPartialKafkaAdminClient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由于原生的KafkaAdminClient在解析Group时,会将Connect集群的Group过滤掉,因此自定义KSPartialKafkaAdminClient,使其具备获取Connect Group的能力 --- .../entity/kafka/KSDescribeGroupsResult.java | 45 + .../bean/entity/kafka/KSGroupDescription.java | 124 ++ .../bean/entity/kafka/KSListGroupsResult.java | 79 + .../entity/kafka/KSMemberBaseAssignment.java | 4 + .../kafka/KSMemberConnectAssignment.java | 25 + .../kafka/KSMemberConsumerAssignment.java | 50 + .../entity/kafka/KSMemberDescription.java | 93 + .../kafka/KSPartialKafkaAdminClient.java | 1539 +++++++++++++++++ 8 files changed, 1959 insertions(+) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSDescribeGroupsResult.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSGroupDescription.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSListGroupsResult.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberBaseAssignment.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConnectAssignment.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConsumerAssignment.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberDescription.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/kafka/KSPartialKafkaAdminClient.java diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSDescribeGroupsResult.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSDescribeGroupsResult.java new file mode 100644 index 00000000..9630703c --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSDescribeGroupsResult.java @@ -0,0 +1,45 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +import org.apache.kafka.common.KafkaFuture; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +public class KSDescribeGroupsResult { + private final Map> futures; + + public KSDescribeGroupsResult(final Map> futures) { + this.futures = futures; + } + + /** + * Return a map from group id to futures which yield group descriptions. + */ + public Map> describedGroups() { + return futures; + } + + /** + * Return a future which yields all ConsumerGroupDescription objects, if all the describes succeed. + */ + public KafkaFuture> all() { + return KafkaFuture.allOf(futures.values().toArray(new KafkaFuture[0])).thenApply( + new KafkaFuture.BaseFunction>() { + @Override + public Map apply(Void v) { + try { + Map descriptions = new HashMap<>(futures.size()); + for (Map.Entry> entry : futures.entrySet()) { + descriptions.put(entry.getKey(), entry.getValue().get()); + } + return descriptions; + } catch (InterruptedException | ExecutionException e) { + // This should be unreachable, since the KafkaFuture#allOf already ensured + // that all of the futures completed successfully. + throw new RuntimeException(e); + } + } + }); + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSGroupDescription.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSGroupDescription.java new file mode 100644 index 00000000..c58f89d2 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSGroupDescription.java @@ -0,0 +1,124 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +import org.apache.kafka.common.ConsumerGroupState; +import org.apache.kafka.common.Node; +import org.apache.kafka.common.acl.AclOperation; +import org.apache.kafka.common.utils.Utils; + +import java.util.*; + +public class KSGroupDescription { + private final String groupId; + private final String protocolType; + private final Collection members; + private final String partitionAssignor; + private final ConsumerGroupState state; + private final Node coordinator; + private final Set authorizedOperations; + + public KSGroupDescription(String groupId, + String protocolType, + Collection members, + String partitionAssignor, + ConsumerGroupState state, + Node coordinator) { + this(groupId, protocolType, members, partitionAssignor, state, coordinator, Collections.emptySet()); + } + + public KSGroupDescription(String groupId, + String protocolType, + Collection members, + String partitionAssignor, + ConsumerGroupState state, + Node coordinator, + Set authorizedOperations) { + this.groupId = groupId == null ? "" : groupId; + this.protocolType = protocolType; + this.members = members == null ? Collections.emptyList() : + Collections.unmodifiableList(new ArrayList<>(members)); + this.partitionAssignor = partitionAssignor == null ? "" : partitionAssignor; + this.state = state; + this.coordinator = coordinator; + this.authorizedOperations = authorizedOperations; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final KSGroupDescription that = (KSGroupDescription) o; + return protocolType == that.protocolType && + Objects.equals(groupId, that.groupId) && + Objects.equals(members, that.members) && + Objects.equals(partitionAssignor, that.partitionAssignor) && + state == that.state && + Objects.equals(coordinator, that.coordinator) && + Objects.equals(authorizedOperations, that.authorizedOperations); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, protocolType, members, partitionAssignor, state, coordinator, authorizedOperations); + } + + /** + * The id of the consumer group. + */ + public String groupId() { + return groupId; + } + + /** + * If consumer group is simple or not. + */ + public String protocolType() { + return protocolType; + } + + /** + * A list of the members of the consumer group. + */ + public Collection members() { + return members; + } + + /** + * The consumer group partition assignor. + */ + public String partitionAssignor() { + return partitionAssignor; + } + + /** + * The consumer group state, or UNKNOWN if the state is too new for us to parse. + */ + public ConsumerGroupState state() { + return state; + } + + /** + * The consumer group coordinator, or null if the coordinator is not known. + */ + public Node coordinator() { + return coordinator; + } + + /** + * authorizedOperations for this group, or null if that information is not known. + */ + public Set authorizedOperations() { + return authorizedOperations; + } + + @Override + public String toString() { + return "(groupId=" + groupId + + ", protocolType=" + protocolType + + ", members=" + Utils.join(members, ",") + + ", partitionAssignor=" + partitionAssignor + + ", state=" + state + + ", coordinator=" + coordinator + + ", authorizedOperations=" + authorizedOperations + + ")"; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSListGroupsResult.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSListGroupsResult.java new file mode 100644 index 00000000..9f07138b --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSListGroupsResult.java @@ -0,0 +1,79 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +import org.apache.kafka.clients.admin.ConsumerGroupListing; +import org.apache.kafka.common.KafkaFuture; +import org.apache.kafka.common.internals.KafkaFutureImpl; + +import java.util.ArrayList; +import java.util.Collection; + +public class KSListGroupsResult { + private final KafkaFutureImpl> all; + private final KafkaFutureImpl> valid; + private final KafkaFutureImpl> errors; + + public KSListGroupsResult(KafkaFutureImpl> future) { + this.all = new KafkaFutureImpl<>(); + this.valid = new KafkaFutureImpl<>(); + this.errors = new KafkaFutureImpl<>(); + future.thenApply(new KafkaFuture.BaseFunction, Void>() { + @Override + public Void apply(Collection results) { + ArrayList curErrors = new ArrayList<>(); + ArrayList curValid = new ArrayList<>(); + for (Object resultObject : results) { + if (resultObject instanceof Throwable) { + curErrors.add((Throwable) resultObject); + } else { + curValid.add((ConsumerGroupListing) resultObject); + } + } + if (!curErrors.isEmpty()) { + all.completeExceptionally(curErrors.get(0)); + } else { + all.complete(curValid); + } + valid.complete(curValid); + errors.complete(curErrors); + return null; + } + }); + } + + /** + * Returns a future that yields either an exception, or the full set of consumer group + * listings. + * + * In the event of a failure, the future yields nothing but the first exception which + * occurred. + */ + public KafkaFuture> all() { + return all; + } + + /** + * Returns a future which yields just the valid listings. + * + * This future never fails with an error, no matter what happens. Errors are completely + * ignored. If nothing can be fetched, an empty collection is yielded. + * If there is an error, but some results can be returned, this future will yield + * those partial results. When using this future, it is a good idea to also check + * the errors future so that errors can be displayed and handled. + */ + public KafkaFuture> valid() { + return valid; + } + + /** + * Returns a future which yields just the errors which occurred. + * + * If this future yields a non-empty collection, it is very likely that elements are + * missing from the valid() set. + * + * This future itself never fails with an error. In the event of an error, this future + * will successfully yield a collection containing at least one exception. + */ + public KafkaFuture> errors() { + return errors; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberBaseAssignment.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberBaseAssignment.java new file mode 100644 index 00000000..3c38e4e5 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberBaseAssignment.java @@ -0,0 +1,4 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +public class KSMemberBaseAssignment { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConnectAssignment.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConnectAssignment.java new file mode 100644 index 00000000..ba6bf638 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConnectAssignment.java @@ -0,0 +1,25 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +import lombok.Getter; +import org.apache.kafka.connect.runtime.distributed.ConnectProtocol; + + +@Getter +public class KSMemberConnectAssignment extends KSMemberBaseAssignment { + private final ConnectProtocol.Assignment assignment; + + private final ConnectProtocol.WorkerState workerState; + + public KSMemberConnectAssignment(ConnectProtocol.Assignment assignment, ConnectProtocol.WorkerState workerState) { + this.assignment = assignment; + this.workerState = workerState; + } + + @Override + public String toString() { + return "KSMemberConnectAssignment{" + + "assignment=" + assignment + + ", workerState=" + workerState + + '}'; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConsumerAssignment.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConsumerAssignment.java new file mode 100644 index 00000000..0a9950c3 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberConsumerAssignment.java @@ -0,0 +1,50 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.utils.Utils; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class KSMemberConsumerAssignment extends KSMemberBaseAssignment { + private final Set topicPartitions; + + /** + * Creates an instance with the specified parameters. + * + * @param topicPartitions List of topic partitions + */ + public KSMemberConsumerAssignment(Set topicPartitions) { + this.topicPartitions = topicPartitions == null ? Collections.emptySet() : + Collections.unmodifiableSet(new HashSet<>(topicPartitions)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + KSMemberConsumerAssignment that = (KSMemberConsumerAssignment) o; + + return Objects.equals(topicPartitions, that.topicPartitions); + } + + @Override + public int hashCode() { + return topicPartitions != null ? topicPartitions.hashCode() : 0; + } + + /** + * The topic partitions assigned to a group member. + */ + public Set topicPartitions() { + return topicPartitions; + } + + @Override + public String toString() { + return "(topicPartitions=" + Utils.join(topicPartitions, ",") + ")"; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberDescription.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberDescription.java new file mode 100644 index 00000000..2690ba77 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/kafka/KSMemberDescription.java @@ -0,0 +1,93 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.kafka; + +import java.util.Objects; +import java.util.Optional; + +public class KSMemberDescription { + private final String memberId; + private final Optional groupInstanceId; + private final String clientId; + private final String host; + private final KSMemberBaseAssignment assignment; + + public KSMemberDescription(String memberId, + Optional groupInstanceId, + String clientId, + String host, + KSMemberBaseAssignment assignment) { + this.memberId = memberId == null ? "" : memberId; + this.groupInstanceId = groupInstanceId; + this.clientId = clientId == null ? "" : clientId; + this.host = host == null ? "" : host; + this.assignment = assignment == null ? + new KSMemberBaseAssignment() : assignment; + } + + public KSMemberDescription(String memberId, + String clientId, + String host, + KSMemberBaseAssignment assignment) { + this(memberId, Optional.empty(), clientId, host, assignment); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KSMemberDescription that = (KSMemberDescription) o; + return memberId.equals(that.memberId) && + groupInstanceId.equals(that.groupInstanceId) && + clientId.equals(that.clientId) && + host.equals(that.host) && + assignment.equals(that.assignment); + } + + @Override + public int hashCode() { + return Objects.hash(memberId, groupInstanceId, clientId, host, assignment); + } + + /** + * The consumer id of the group member. + */ + public String consumerId() { + return memberId; + } + + /** + * The instance id of the group member. + */ + public Optional groupInstanceId() { + return groupInstanceId; + } + + /** + * The client id of the group member. + */ + public String clientId() { + return clientId; + } + + /** + * The host where the group member is running. + */ + public String host() { + return host; + } + + /** + * The assignment of the group member. + */ + public KSMemberBaseAssignment assignment() { + return assignment; + } + + @Override + public String toString() { + return "(memberId=" + memberId + + ", groupInstanceId=" + groupInstanceId.orElse("null") + + ", clientId=" + clientId + + ", host=" + host + + ", assignment=" + assignment + ")"; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/kafka/KSPartialKafkaAdminClient.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/kafka/KSPartialKafkaAdminClient.java new file mode 100644 index 00000000..f985fb01 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/kafka/KSPartialKafkaAdminClient.java @@ -0,0 +1,1539 @@ +package com.xiaojukeji.know.streaming.km.common.utils.kafka; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.xiaojukeji.know.streaming.km.common.annotations.KafkaSource; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.*; +import org.apache.kafka.clients.ApiVersions; +import org.apache.kafka.clients.ClientDnsLookup; +import org.apache.kafka.clients.ClientRequest; +import org.apache.kafka.clients.ClientResponse; +import org.apache.kafka.clients.ClientUtils; +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.KafkaClient; +import org.apache.kafka.clients.NetworkClient; +import org.apache.kafka.clients.StaleMetadataException; +import org.apache.kafka.clients.admin.*; +import org.apache.kafka.clients.admin.internals.AdminMetadataManager; +import org.apache.kafka.clients.admin.internals.ConsumerGroupOperationContext; +import org.apache.kafka.clients.consumer.ConsumerPartitionAssignor.Assignment; +import org.apache.kafka.clients.consumer.internals.ConsumerProtocol; +import org.apache.kafka.common.*; +import org.apache.kafka.common.acl.AclOperation; +import org.apache.kafka.common.config.ConfigException; +import org.apache.kafka.common.errors.AuthenticationException; +import org.apache.kafka.common.errors.DisconnectException; +import org.apache.kafka.common.errors.InvalidGroupIdException; +import org.apache.kafka.common.errors.RetriableException; +import org.apache.kafka.common.errors.TimeoutException; +import org.apache.kafka.common.errors.UnsupportedVersionException; +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.apache.kafka.common.message.DescribeGroupsRequestData; +import org.apache.kafka.common.message.DescribeGroupsResponseData.DescribedGroup; +import org.apache.kafka.common.message.DescribeGroupsResponseData.DescribedGroupMember; +import org.apache.kafka.common.message.FindCoordinatorRequestData; +import org.apache.kafka.common.message.ListGroupsRequestData; +import org.apache.kafka.common.message.ListGroupsResponseData; +import org.apache.kafka.common.message.MetadataRequestData; +import org.apache.kafka.common.metrics.JmxReporter; +import org.apache.kafka.common.metrics.KafkaMetricsContext; +import org.apache.kafka.common.metrics.MetricConfig; +import org.apache.kafka.common.metrics.Metrics; +import org.apache.kafka.common.metrics.MetricsContext; +import org.apache.kafka.common.metrics.MetricsReporter; +import org.apache.kafka.common.metrics.Sensor; +import org.apache.kafka.common.network.ChannelBuilder; +import org.apache.kafka.common.network.Selector; +import org.apache.kafka.common.protocol.Errors; +import org.apache.kafka.common.requests.AbstractRequest; +import org.apache.kafka.common.requests.AbstractResponse; +import org.apache.kafka.common.requests.ApiError; +import org.apache.kafka.common.requests.DescribeGroupsRequest; +import org.apache.kafka.common.requests.DescribeGroupsResponse; +import org.apache.kafka.common.requests.FindCoordinatorRequest; +import org.apache.kafka.common.requests.FindCoordinatorRequest.CoordinatorType; +import org.apache.kafka.common.requests.FindCoordinatorResponse; +import org.apache.kafka.common.requests.ListGroupsRequest; +import org.apache.kafka.common.requests.ListGroupsResponse; +import org.apache.kafka.common.requests.MetadataRequest; +import org.apache.kafka.common.requests.MetadataResponse; +import org.apache.kafka.common.utils.AppInfoParser; +import org.apache.kafka.common.utils.KafkaThread; +import org.apache.kafka.common.utils.LogContext; +import org.apache.kafka.common.utils.Time; +import org.apache.kafka.common.utils.Utils; +import org.apache.kafka.connect.runtime.distributed.ConnectProtocol; +import org.slf4j.Logger; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.apache.kafka.common.utils.Utils.closeQuietly; + +/** + * The default implementation of {@link Admin}. An instance of this class is created by invoking one of the + * {@code create()} methods in {@code AdminClient}. Users should not refer to this class directly. + * + *

+ * This class is thread-safe. + *

+ * The API of this class is evolving, see {@link Admin} for details. + */ +@KafkaSource(modified = 1) +public class KSPartialKafkaAdminClient { + + /** + * The next integer to use to name a KafkaAdminClient which the user hasn't specified an explicit name for. + */ + private static final AtomicInteger ADMIN_CLIENT_ID_SEQUENCE = new AtomicInteger(1); + + /** + * The prefix to use for the JMX metrics for this class + */ + private static final String JMX_PREFIX = "ks-kafka.admin.client"; + + /** + * An invalid shutdown time which indicates that a shutdown has not yet been performed. + */ + private static final long INVALID_SHUTDOWN_TIME = -1; + + /** + * Thread name prefix for admin client network thread + */ + static final String NETWORK_THREAD_PREFIX = "ks-kafka-admin-client-thread"; + + private final Logger log; + + /** + * The default timeout to use for an operation. + */ + private final int defaultApiTimeoutMs; + + /** + * The timeout to use for a single request. + */ + private final int requestTimeoutMs; + + /** + * The name of this AdminClient instance. + */ + private final String clientId; + + /** + * Provides the time. + */ + private final Time time; + + /** + * The cluster metadata manager used by the KafkaClient. + */ + private final AdminMetadataManager metadataManager; + + /** + * The metrics for this KafkaAdminClient. + */ + private final Metrics metrics; + + /** + * The network client to use. + */ + private final KafkaClient client; + + /** + * The runnable used in the service thread for this admin client. + */ + private final AdminClientRunnable runnable; + + /** + * The network service thread for this admin client. + */ + private final Thread thread; + + /** + * During a close operation, this is the time at which we will time out all pending operations + * and force the RPC thread to exit. If the admin client is not closing, this will be 0. + */ + private final AtomicLong hardShutdownTimeMs = new AtomicLong(INVALID_SHUTDOWN_TIME); + + /** + * A factory which creates TimeoutProcessors for the RPC thread. + */ + private final TimeoutProcessorFactory timeoutProcessorFactory; + + private final int maxRetries; + + private final long retryBackoffMs; + + /** + * Get or create a list value from a map. + * + * @param map The map to get or create the element from. + * @param key The key. + * @param The key type. + * @param The value type. + * @return The list value. + */ + static List getOrCreateListValue(Map> map, K key) { + return map.computeIfAbsent(key, k -> new LinkedList<>()); + } + + /** + * Get the current time remaining before a deadline as an integer. + * + * @param now The current time in milliseconds. + * @param deadlineMs The deadline time in milliseconds. + * @return The time delta in milliseconds. + */ + static int calcTimeoutMsRemainingAsInt(long now, long deadlineMs) { + long deltaMs = deadlineMs - now; + if (deltaMs > Integer.MAX_VALUE) + deltaMs = Integer.MAX_VALUE; + else if (deltaMs < Integer.MIN_VALUE) + deltaMs = Integer.MIN_VALUE; + return (int) deltaMs; + } + + /** + * Generate the client id based on the configuration. + * + * @param config The configuration + * + * @return The client id + */ + static String generateClientId(AdminClientConfig config) { + String clientId = config.getString(AdminClientConfig.CLIENT_ID_CONFIG); + if (!clientId.isEmpty()) + return clientId; + return "adminclient-" + ADMIN_CLIENT_ID_SEQUENCE.getAndIncrement(); + } + + /** + * Get the deadline for a particular call. + * + * @param now The current time in milliseconds. + * @param optionTimeoutMs The timeout option given by the user. + * + * @return The deadline in milliseconds. + */ + private long calcDeadlineMs(long now, Integer optionTimeoutMs) { + if (optionTimeoutMs != null) + return now + Math.max(0, optionTimeoutMs); + return now + defaultApiTimeoutMs; + } + + /** + * Pretty-print an exception. + * + * @param throwable The exception. + * + * @return A compact human-readable string. + */ + static String prettyPrintException(Throwable throwable) { + if (throwable == null) + return "Null exception."; + if (throwable.getMessage() != null) { + return throwable.getClass().getSimpleName() + ": " + throwable.getMessage(); + } + return throwable.getClass().getSimpleName(); + } + + public static KSPartialKafkaAdminClient create(Properties props) { + return KSPartialKafkaAdminClient.createInternal(new AdminClientConfig(props), null); + } + + static KSPartialKafkaAdminClient createInternal(AdminClientConfig config, TimeoutProcessorFactory timeoutProcessorFactory) { + Metrics metrics = null; + NetworkClient networkClient = null; + Time time = Time.SYSTEM; + String clientId = generateClientId(config); + ChannelBuilder channelBuilder = null; + Selector selector = null; + ApiVersions apiVersions = new ApiVersions(); + LogContext logContext = createLogContext(clientId); + + try { + // Since we only request node information, it's safe to pass true for allowAutoTopicCreation (and it + // simplifies communication with older brokers) + AdminMetadataManager metadataManager = new AdminMetadataManager(logContext, + config.getLong(AdminClientConfig.RETRY_BACKOFF_MS_CONFIG), + config.getLong(AdminClientConfig.METADATA_MAX_AGE_CONFIG)); + List addresses = ClientUtils.parseAndValidateAddresses( + config.getList(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG), + config.getString(AdminClientConfig.CLIENT_DNS_LOOKUP_CONFIG)); + metadataManager.update(Cluster.bootstrap(addresses), time.milliseconds()); + List reporters = config.getConfiguredInstances(AdminClientConfig.METRIC_REPORTER_CLASSES_CONFIG, + MetricsReporter.class, + Collections.singletonMap(AdminClientConfig.CLIENT_ID_CONFIG, clientId)); + Map metricTags = Collections.singletonMap("client-id", clientId); + MetricConfig metricConfig = new MetricConfig().samples(config.getInt(AdminClientConfig.METRICS_NUM_SAMPLES_CONFIG)) + .timeWindow(config.getLong(AdminClientConfig.METRICS_SAMPLE_WINDOW_MS_CONFIG), TimeUnit.MILLISECONDS) + .recordLevel(Sensor.RecordingLevel.forName(config.getString(AdminClientConfig.METRICS_RECORDING_LEVEL_CONFIG))) + .tags(metricTags); + JmxReporter jmxReporter = new JmxReporter(); + jmxReporter.configure(config.originals()); + reporters.add(jmxReporter); + MetricsContext metricsContext = new KafkaMetricsContext(JMX_PREFIX, + config.originalsWithPrefix(CommonClientConfigs.METRICS_CONTEXT_PREFIX)); + metrics = new Metrics(metricConfig, reporters, time, metricsContext); + String metricGrpPrefix = "admin-client"; + channelBuilder = ClientUtils.createChannelBuilder(config, time, logContext); + selector = new Selector(config.getLong(AdminClientConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG), + metrics, time, metricGrpPrefix, channelBuilder, logContext); + networkClient = new NetworkClient( + selector, + metadataManager.updater(), + clientId, + 1, + config.getLong(AdminClientConfig.RECONNECT_BACKOFF_MS_CONFIG), + config.getLong(AdminClientConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG), + config.getInt(AdminClientConfig.SEND_BUFFER_CONFIG), + config.getInt(AdminClientConfig.RECEIVE_BUFFER_CONFIG), + (int) TimeUnit.HOURS.toMillis(1), + config.getLong(AdminClientConfig.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG), + config.getLong(AdminClientConfig.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG), + ClientDnsLookup.forConfig(config.getString(AdminClientConfig.CLIENT_DNS_LOOKUP_CONFIG)), + time, + true, + apiVersions, + logContext); + return new KSPartialKafkaAdminClient(config, clientId, time, metadataManager, metrics, networkClient, + timeoutProcessorFactory, logContext); + } catch (Throwable exc) { + closeQuietly(metrics, "Metrics"); + closeQuietly(networkClient, "NetworkClient"); + closeQuietly(selector, "Selector"); + closeQuietly(channelBuilder, "ChannelBuilder"); + throw new KafkaException("Failed to create new KafkaAdminClient", exc); + } + } + + static LogContext createLogContext(String clientId) { + return new LogContext("[AdminClient clientId=" + clientId + "] "); + } + + private KSPartialKafkaAdminClient(AdminClientConfig config, + String clientId, + Time time, + AdminMetadataManager metadataManager, + Metrics metrics, + KafkaClient client, + TimeoutProcessorFactory timeoutProcessorFactory, + LogContext logContext) { + this.clientId = clientId; + this.log = logContext.logger(KafkaAdminClient.class); + this.requestTimeoutMs = config.getInt(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG); + this.defaultApiTimeoutMs = configureDefaultApiTimeoutMs(config); + this.time = time; + this.metadataManager = metadataManager; + this.metrics = metrics; + this.client = client; + this.runnable = new AdminClientRunnable(); + String threadName = NETWORK_THREAD_PREFIX + " | " + clientId; + this.thread = new KafkaThread(threadName, runnable, true); + this.timeoutProcessorFactory = (timeoutProcessorFactory == null) ? + new TimeoutProcessorFactory() : timeoutProcessorFactory; + this.maxRetries = config.getInt(AdminClientConfig.RETRIES_CONFIG); + this.retryBackoffMs = config.getLong(AdminClientConfig.RETRY_BACKOFF_MS_CONFIG); + config.logUnused(); + AppInfoParser.registerAppInfo(JMX_PREFIX, clientId, metrics, time.milliseconds()); + log.debug("Kafka admin client initialized"); + thread.start(); + } + + /** + * If a default.api.timeout.ms has been explicitly specified, raise an error if it conflicts with request.timeout.ms. + * If no default.api.timeout.ms has been configured, then set its value as the max of the default and request.timeout.ms. Also we should probably log a warning. + * Otherwise, use the provided values for both configurations. + * + * @param config The configuration + */ + private int configureDefaultApiTimeoutMs(AdminClientConfig config) { + int requestTimeoutMs = config.getInt(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG); + int defaultApiTimeoutMs = config.getInt(AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG); + + if (defaultApiTimeoutMs < requestTimeoutMs) { + if (config.originals().containsKey(AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG)) { + throw new ConfigException("The specified value of " + AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG + + " must be no smaller than the value of " + AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG + "."); + } else { + log.warn("Overriding the default value for {} ({}) with the explicitly configured request timeout {}", + AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG, this.defaultApiTimeoutMs, + requestTimeoutMs); + return requestTimeoutMs; + } + } + return defaultApiTimeoutMs; + } + + public void close(Duration timeout) { + long waitTimeMs = timeout.toMillis(); + if (waitTimeMs < 0) + throw new IllegalArgumentException("The timeout cannot be negative."); + waitTimeMs = Math.min(TimeUnit.DAYS.toMillis(365), waitTimeMs); // Limit the timeout to a year. + long now = time.milliseconds(); + long newHardShutdownTimeMs = now + waitTimeMs; + long prev = INVALID_SHUTDOWN_TIME; + while (true) { + if (hardShutdownTimeMs.compareAndSet(prev, newHardShutdownTimeMs)) { + if (prev == INVALID_SHUTDOWN_TIME) { + log.debug("Initiating close operation."); + } else { + log.debug("Moving hard shutdown time forward."); + } + client.wakeup(); // Wake the thread, if it is blocked inside poll(). + break; + } + prev = hardShutdownTimeMs.get(); + if (prev < newHardShutdownTimeMs) { + log.debug("Hard shutdown time is already earlier than requested."); + newHardShutdownTimeMs = prev; + break; + } + } + if (log.isDebugEnabled()) { + long deltaMs = Math.max(0, newHardShutdownTimeMs - time.milliseconds()); + log.debug("Waiting for the I/O thread to exit. Hard shutdown in {} ms.", deltaMs); + } + try { + // close() can be called by AdminClient thread when it invokes callback. That will + // cause deadlock, so check for that condition. + if (Thread.currentThread() != thread) { + // Wait for the thread to be joined. + thread.join(waitTimeMs); + } + log.debug("Kafka admin client closed."); + } catch (InterruptedException e) { + log.debug("Interrupted while joining I/O thread", e); + Thread.currentThread().interrupt(); + } + } + + /** + * An interface for providing a node for a call. + */ + private interface NodeProvider { + Node provide(); + } + + private class MetadataUpdateNodeIdProvider implements NodeProvider { + @Override + public Node provide() { + return client.leastLoadedNode(time.milliseconds()); + } + } + + private class ConstantNodeIdProvider implements NodeProvider { + private final int nodeId; + + ConstantNodeIdProvider(int nodeId) { + this.nodeId = nodeId; + } + + @Override + public Node provide() { + if (metadataManager.isReady() && + (metadataManager.nodeById(nodeId) != null)) { + return metadataManager.nodeById(nodeId); + } + // If we can't find the node with the given constant ID, we schedule a + // metadata update and hope it appears. This behavior is useful for avoiding + // flaky behavior in tests when the cluster is starting up and not all nodes + // have appeared. + metadataManager.requestUpdate(); + return null; + } + } + + /** + * Provides the least loaded node. + */ + private class LeastLoadedNodeProvider implements NodeProvider { + @Override + public Node provide() { + if (metadataManager.isReady()) { + // This may return null if all nodes are busy. + // In that case, we will postpone node assignment. + return client.leastLoadedNode(time.milliseconds()); + } + metadataManager.requestUpdate(); + return null; + } + } + + abstract class Call { + private final boolean internal; + private final String callName; + private final long deadlineMs; + private final NodeProvider nodeProvider; + private int tries = 0; + private boolean aborted = false; + private Node curNode = null; + private long nextAllowedTryMs = 0; + + Call(boolean internal, String callName, long deadlineMs, NodeProvider nodeProvider) { + this.internal = internal; + this.callName = callName; + this.deadlineMs = deadlineMs; + this.nodeProvider = nodeProvider; + } + + Call(String callName, long deadlineMs, NodeProvider nodeProvider) { + this(false, callName, deadlineMs, nodeProvider); + } + + protected Node curNode() { + return curNode; + } + + /** + * Handle a failure. + * + * Depending on what the exception is and how many times we have already tried, we may choose to + * fail the Call, or retry it. It is important to print the stack traces here in some cases, + * since they are not necessarily preserved in ApiVersionException objects. + * + * @param now The current time in milliseconds. + * @param throwable The failure exception. + */ + final void fail(long now, Throwable throwable) { + if (aborted) { + // If the call was aborted while in flight due to a timeout, deliver a + // TimeoutException. In this case, we do not get any more retries - the call has + // failed. We increment tries anyway in order to display an accurate log message. + tries++; + failWithTimeout(now, throwable); + return; + } + // If this is an UnsupportedVersionException that we can retry, do so. Note that a + // protocol downgrade will not count against the total number of retries we get for + // this RPC. That is why 'tries' is not incremented. + if ((throwable instanceof UnsupportedVersionException) && + handleUnsupportedVersionException((UnsupportedVersionException) throwable)) { + log.debug("{} attempting protocol downgrade and then retry.", this); + runnable.enqueue(this, now); + return; + } + tries++; + nextAllowedTryMs = now + retryBackoffMs; + + // If the call has timed out, fail. + if (calcTimeoutMsRemainingAsInt(now, deadlineMs) < 0) { + failWithTimeout(now, throwable); + return; + } + // If the exception is not retriable, fail. + if (!(throwable instanceof RetriableException)) { + if (log.isDebugEnabled()) { + log.debug("{} failed with non-retriable exception after {} attempt(s)", this, tries, + new Exception(prettyPrintException(throwable))); + } + handleFailure(throwable); + return; + } + // If we are out of retries, fail. + if (tries > maxRetries) { + failWithTimeout(now, throwable); + return; + } + if (log.isDebugEnabled()) { + log.debug("{} failed: {}. Beginning retry #{}", + this, prettyPrintException(throwable), tries); + } + runnable.enqueue(this, now); + } + + private void failWithTimeout(long now, Throwable cause) { + if (log.isDebugEnabled()) { + log.debug("{} timed out at {} after {} attempt(s)", this, now, tries, + new Exception(prettyPrintException(cause))); + } + handleFailure(new TimeoutException(this + " timed out at " + now + + " after " + tries + " attempt(s)", cause)); + } + + /** + * Create an AbstractRequest.Builder for this Call. + * + * @param timeoutMs The timeout in milliseconds. + * + * @return The AbstractRequest builder. + */ + @SuppressWarnings("rawtypes") + abstract AbstractRequest.Builder createRequest(int timeoutMs); + + /** + * Process the call response. + * + * @param abstractResponse The AbstractResponse. + * + */ + abstract void handleResponse(AbstractResponse abstractResponse); + + /** + * Handle a failure. This will only be called if the failure exception was not + * retriable, or if we hit a timeout. + * + * @param throwable The exception. + */ + abstract void handleFailure(Throwable throwable); + + /** + * Handle an UnsupportedVersionException. + * + * @param exception The exception. + * + * @return True if the exception can be handled; false otherwise. + */ + boolean handleUnsupportedVersionException(UnsupportedVersionException exception) { + return false; + } + + @Override + public String toString() { + return "Call(callName=" + callName + ", deadlineMs=" + deadlineMs + + ", tries=" + tries + ", nextAllowedTryMs=" + nextAllowedTryMs + ")"; + } + + public boolean isInternal() { + return internal; + } + } + + static class TimeoutProcessorFactory { + TimeoutProcessor create(long now) { + return new TimeoutProcessor(now); + } + } + + static class TimeoutProcessor { + /** + * The current time in milliseconds. + */ + private final long now; + + /** + * The number of milliseconds until the next timeout. + */ + private int nextTimeoutMs; + + /** + * Create a new timeout processor. + * + * @param now The current time in milliseconds since the epoch. + */ + TimeoutProcessor(long now) { + this.now = now; + this.nextTimeoutMs = Integer.MAX_VALUE; + } + + /** + * Check for calls which have timed out. + * Timed out calls will be removed and failed. + * The remaining milliseconds until the next timeout will be updated. + * + * @param calls The collection of calls. + * + * @return The number of calls which were timed out. + */ + int handleTimeouts(Collection calls, String msg) { + int numTimedOut = 0; + for (Iterator iter = calls.iterator(); iter.hasNext(); ) { + Call call = iter.next(); + int remainingMs = calcTimeoutMsRemainingAsInt(now, call.deadlineMs); + if (remainingMs < 0) { + call.fail(now, new TimeoutException(msg + " Call: " + call.callName)); + iter.remove(); + numTimedOut++; + } else { + nextTimeoutMs = Math.min(nextTimeoutMs, remainingMs); + } + } + return numTimedOut; + } + + /** + * Check whether a call should be timed out. + * The remaining milliseconds until the next timeout will be updated. + * + * @param call The call. + * + * @return True if the call should be timed out. + */ + boolean callHasExpired(Call call) { + int remainingMs = calcTimeoutMsRemainingAsInt(now, call.deadlineMs); + if (remainingMs < 0) + return true; + nextTimeoutMs = Math.min(nextTimeoutMs, remainingMs); + return false; + } + + int nextTimeoutMs() { + return nextTimeoutMs; + } + } + + private final class AdminClientRunnable implements Runnable { + /** + * Calls which have not yet been assigned to a node. + * Only accessed from this thread. + */ + private final ArrayList pendingCalls = new ArrayList<>(); + + /** + * Maps nodes to calls that we want to send. + * Only accessed from this thread. + */ + private final Map> callsToSend = new HashMap<>(); + + /** + * Maps node ID strings to calls that have been sent. + * Only accessed from this thread. + */ + private final Map> callsInFlight = new HashMap<>(); + + /** + * Maps correlation IDs to calls that have been sent. + * Only accessed from this thread. + */ + private final Map correlationIdToCalls = new HashMap<>(); + + /** + * Pending calls. Protected by the object monitor. + * This will be null only if the thread has shut down. + */ + private List newCalls = new LinkedList<>(); + + /** + * Time out the elements in the pendingCalls list which are expired. + * + * @param processor The timeout processor. + */ + private void timeoutPendingCalls(TimeoutProcessor processor) { + int numTimedOut = processor.handleTimeouts(pendingCalls, "Timed out waiting for a node assignment."); + if (numTimedOut > 0) + log.debug("Timed out {} pending calls.", numTimedOut); + } + + /** + * Time out calls which have been assigned to nodes. + * + * @param processor The timeout processor. + */ + private int timeoutCallsToSend(TimeoutProcessor processor) { + int numTimedOut = 0; + for (List callList : callsToSend.values()) { + numTimedOut += processor.handleTimeouts(callList, + "Timed out waiting to send the call."); + } + if (numTimedOut > 0) + log.debug("Timed out {} call(s) with assigned nodes.", numTimedOut); + return numTimedOut; + } + + /** + * Drain all the calls from newCalls into pendingCalls. + * + * This function holds the lock for the minimum amount of time, to avoid blocking + * users of AdminClient who will also take the lock to add new calls. + */ + private synchronized void drainNewCalls() { + if (!newCalls.isEmpty()) { + pendingCalls.addAll(newCalls); + newCalls.clear(); + } + } + + /** + * Choose nodes for the calls in the pendingCalls list. + * + * @param now The current time in milliseconds. + * @return The minimum time until a call is ready to be retried if any of the pending + * calls are backing off after a failure + */ + private long maybeDrainPendingCalls(long now) { + long pollTimeout = Long.MAX_VALUE; + log.trace("Trying to choose nodes for {} at {}", pendingCalls, now); + + Iterator pendingIter = pendingCalls.iterator(); + while (pendingIter.hasNext()) { + Call call = pendingIter.next(); + + // If the call is being retried, await the proper backoff before finding the node + if (now < call.nextAllowedTryMs) { + pollTimeout = Math.min(pollTimeout, call.nextAllowedTryMs - now); + } else if (maybeDrainPendingCall(call, now)) { + pendingIter.remove(); + } + } + return pollTimeout; + } + + /** + * Check whether a pending call can be assigned a node. Return true if the pending call was either + * transferred to the callsToSend collection or if the call was failed. Return false if it + * should remain pending. + */ + private boolean maybeDrainPendingCall(Call call, long now) { + try { + Node node = call.nodeProvider.provide(); + if (node != null) { + log.trace("Assigned {} to node {}", call, node); + call.curNode = node; + getOrCreateListValue(callsToSend, node).add(call); + return true; + } else { + log.trace("Unable to assign {} to a node.", call); + return false; + } + } catch (Throwable t) { + // Handle authentication errors while choosing nodes. + log.debug("Unable to choose node for {}", call, t); + call.fail(now, t); + return true; + } + } + + /** + * Send the calls which are ready. + * + * @param now The current time in milliseconds. + * @return The minimum timeout we need for poll(). + */ + private long sendEligibleCalls(long now) { + long pollTimeout = Long.MAX_VALUE; + for (Iterator>> iter = callsToSend.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry> entry = iter.next(); + List calls = entry.getValue(); + if (calls.isEmpty()) { + iter.remove(); + continue; + } + Node node = entry.getKey(); + if (!client.ready(node, now)) { + long nodeTimeout = client.pollDelayMs(node, now); + pollTimeout = Math.min(pollTimeout, nodeTimeout); + log.trace("Client is not ready to send to {}. Must delay {} ms", node, nodeTimeout); + continue; + } + Call call = calls.remove(0); + int requestTimeoutMs = Math.min(KSPartialKafkaAdminClient.this.requestTimeoutMs, + calcTimeoutMsRemainingAsInt(now, call.deadlineMs)); + AbstractRequest.Builder requestBuilder; + try { + requestBuilder = call.createRequest(requestTimeoutMs); + } catch (Throwable throwable) { + call.fail(now, new KafkaException(String.format( + "Internal error sending %s to %s.", call.callName, node))); + continue; + } + ClientRequest clientRequest = client.newClientRequest(node.idString(), requestBuilder, now, + true, requestTimeoutMs, null); + log.debug("Sending {} to {}. correlationId={}", requestBuilder, node, clientRequest.correlationId()); + client.send(clientRequest, now); + getOrCreateListValue(callsInFlight, node.idString()).add(call); + correlationIdToCalls.put(clientRequest.correlationId(), call); + } + return pollTimeout; + } + + /** + * Time out expired calls that are in flight. + * + * Calls that are in flight may have been partially or completely sent over the wire. They may + * even be in the process of being processed by the remote server. At the moment, our only option + * to time them out is to close the entire connection. + * + * @param processor The timeout processor. + */ + private void timeoutCallsInFlight(TimeoutProcessor processor) { + int numTimedOut = 0; + for (Map.Entry> entry : callsInFlight.entrySet()) { + List contexts = entry.getValue(); + if (contexts.isEmpty()) + continue; + String nodeId = entry.getKey(); + // We assume that the first element in the list is the earliest. So it should be the + // only one we need to check the timeout for. + Call call = contexts.get(0); + if (processor.callHasExpired(call)) { + if (call.aborted) { + log.warn("Aborted call {} is still in callsInFlight.", call); + } else { + log.debug("Closing connection to {} to time out {}", nodeId, call); + call.aborted = true; + client.disconnect(nodeId); + numTimedOut++; + // We don't remove anything from the callsInFlight data structure. Because the connection + // has been closed, the calls should be returned by the next client#poll(), + // and handled at that point. + } + } + } + if (numTimedOut > 0) + log.debug("Timed out {} call(s) in flight.", numTimedOut); + } + + /** + * Handle responses from the server. + * + * @param now The current time in milliseconds. + * @param responses The latest responses from KafkaClient. + **/ + private void handleResponses(long now, List responses) { + for (ClientResponse response : responses) { + int correlationId = response.requestHeader().correlationId(); + + Call call = correlationIdToCalls.get(correlationId); + if (call == null) { + // If the server returns information about a correlation ID we didn't use yet, + // an internal server error has occurred. Close the connection and log an error message. + log.error("Internal server error on {}: server returned information about unknown " + + "correlation ID {}, requestHeader = {}", response.destination(), correlationId, + response.requestHeader()); + client.disconnect(response.destination()); + continue; + } + + // Stop tracking this call. + correlationIdToCalls.remove(correlationId); + List calls = callsInFlight.get(response.destination()); + if ((calls == null) || (!calls.remove(call))) { + log.error("Internal server error on {}: ignoring call {} in correlationIdToCall " + + "that did not exist in callsInFlight", response.destination(), call); + continue; + } + + // Handle the result of the call. This may involve retrying the call, if we got a + // retriable exception. + if (response.versionMismatch() != null) { + call.fail(now, response.versionMismatch()); + } else if (response.wasDisconnected()) { + AuthenticationException authException = client.authenticationException(call.curNode()); + if (authException != null) { + call.fail(now, authException); + } else { + call.fail(now, new DisconnectException(String.format( + "Cancelled %s request with correlation id %s due to node %s being disconnected", + call.callName, correlationId, response.destination()))); + } + } else { + try { + call.handleResponse(response.responseBody()); + if (log.isTraceEnabled()) + log.trace("{} got response {}", call, response.responseBody()); + } catch (Throwable t) { + if (log.isTraceEnabled()) + log.trace("{} handleResponse failed with {}", call, prettyPrintException(t)); + call.fail(now, t); + } + } + } + } + + /** + * Unassign calls that have not yet been sent based on some predicate. For example, this + * is used to reassign the calls that have been assigned to a disconnected node. + * + * @param shouldUnassign Condition for reassignment. If the predicate is true, then the calls will + * be put back in the pendingCalls collection and they will be reassigned + */ + private void unassignUnsentCalls(Predicate shouldUnassign) { + for (Iterator>> iter = callsToSend.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry> entry = iter.next(); + Node node = entry.getKey(); + List awaitingCalls = entry.getValue(); + + if (awaitingCalls.isEmpty()) { + iter.remove(); + } else if (shouldUnassign.test(node)) { + pendingCalls.addAll(awaitingCalls); + iter.remove(); + } + } + } + + private boolean hasActiveExternalCalls(Collection calls) { + for (Call call : calls) { + if (!call.isInternal()) { + return true; + } + } + return false; + } + + /** + * Return true if there are currently active external calls. + */ + private boolean hasActiveExternalCalls() { + if (hasActiveExternalCalls(pendingCalls)) { + return true; + } + for (List callList : callsToSend.values()) { + if (hasActiveExternalCalls(callList)) { + return true; + } + } + return hasActiveExternalCalls(correlationIdToCalls.values()); + } + + private boolean threadShouldExit(long now, long curHardShutdownTimeMs) { + if (!hasActiveExternalCalls()) { + log.trace("All work has been completed, and the I/O thread is now exiting."); + return true; + } + if (now >= curHardShutdownTimeMs) { + log.info("Forcing a hard I/O thread shutdown. Requests in progress will be aborted."); + return true; + } + log.debug("Hard shutdown in {} ms.", curHardShutdownTimeMs - now); + return false; + } + + @Override + public void run() { + log.trace("Thread starting"); + try { + processRequests(); + } finally { + AppInfoParser.unregisterAppInfo(JMX_PREFIX, clientId, metrics); + + int numTimedOut = 0; + TimeoutProcessor timeoutProcessor = new TimeoutProcessor(Long.MAX_VALUE); + synchronized (this) { + numTimedOut += timeoutProcessor.handleTimeouts(newCalls, "The AdminClient thread has exited."); + newCalls = null; + } + numTimedOut += timeoutProcessor.handleTimeouts(pendingCalls, "The AdminClient thread has exited."); + numTimedOut += timeoutCallsToSend(timeoutProcessor); + numTimedOut += timeoutProcessor.handleTimeouts(correlationIdToCalls.values(), + "The AdminClient thread has exited."); + if (numTimedOut > 0) { + log.debug("Timed out {} remaining operation(s).", numTimedOut); + } + closeQuietly(client, "KafkaClient"); + closeQuietly(metrics, "Metrics"); + log.debug("Exiting AdminClientRunnable thread."); + } + } + + private void processRequests() { + long now = time.milliseconds(); + while (true) { + // Copy newCalls into pendingCalls. + drainNewCalls(); + + // Check if the AdminClient thread should shut down. + long curHardShutdownTimeMs = hardShutdownTimeMs.get(); + if ((curHardShutdownTimeMs != INVALID_SHUTDOWN_TIME) && threadShouldExit(now, curHardShutdownTimeMs)) + break; + + // Handle timeouts. + TimeoutProcessor timeoutProcessor = timeoutProcessorFactory.create(now); + timeoutPendingCalls(timeoutProcessor); + timeoutCallsToSend(timeoutProcessor); + timeoutCallsInFlight(timeoutProcessor); + + long pollTimeout = Math.min(1200000, timeoutProcessor.nextTimeoutMs()); + if (curHardShutdownTimeMs != INVALID_SHUTDOWN_TIME) { + pollTimeout = Math.min(pollTimeout, curHardShutdownTimeMs - now); + } + + // Choose nodes for our pending calls. + pollTimeout = Math.min(pollTimeout, maybeDrainPendingCalls(now)); + long metadataFetchDelayMs = metadataManager.metadataFetchDelayMs(now); + if (metadataFetchDelayMs == 0) { + metadataManager.transitionToUpdatePending(now); + Call metadataCall = makeMetadataCall(now); + // Create a new metadata fetch call and add it to the end of pendingCalls. + // Assign a node for just the new call (we handled the other pending nodes above). + + if (!maybeDrainPendingCall(metadataCall, now)) + pendingCalls.add(metadataCall); + } + pollTimeout = Math.min(pollTimeout, sendEligibleCalls(now)); + + if (metadataFetchDelayMs > 0) { + pollTimeout = Math.min(pollTimeout, metadataFetchDelayMs); + } + + // Ensure that we use a small poll timeout if there are pending calls which need to be sent + if (!pendingCalls.isEmpty()) + pollTimeout = Math.min(pollTimeout, retryBackoffMs); + + // Wait for network responses. + log.trace("Entering KafkaClient#poll(timeout={})", pollTimeout); + List responses = client.poll(pollTimeout, now); + log.trace("KafkaClient#poll retrieved {} response(s)", responses.size()); + + // unassign calls to disconnected nodes + unassignUnsentCalls(client::connectionFailed); + + // Update the current time and handle the latest responses. + now = time.milliseconds(); + handleResponses(now, responses); + } + } + + /** + * Queue a call for sending. + * + * If the AdminClient thread has exited, this will fail. Otherwise, it will succeed (even + * if the AdminClient is shutting down). This function should called when retrying an + * existing call. + * + * @param call The new call object. + * @param now The current time in milliseconds. + */ + void enqueue(Call call, long now) { + if (call.tries > maxRetries) { + log.debug("Max retries {} for {} reached", maxRetries, call); + call.fail(time.milliseconds(), new TimeoutException()); + return; + } + if (log.isDebugEnabled()) { + log.debug("Queueing {} with a timeout {} ms from now.", call, call.deadlineMs - now); + } + boolean accepted = false; + synchronized (this) { + if (newCalls != null) { + newCalls.add(call); + accepted = true; + } + } + if (accepted) { + client.wakeup(); // wake the thread if it is in poll() + } else { + log.debug("The AdminClient thread has exited. Timing out {}.", call); + call.fail(Long.MAX_VALUE, new TimeoutException("The AdminClient thread has exited.")); + } + } + + /** + * Initiate a new call. + * + * This will fail if the AdminClient is scheduled to shut down. + * + * @param call The new call object. + * @param now The current time in milliseconds. + */ + void call(Call call, long now) { + if (hardShutdownTimeMs.get() != INVALID_SHUTDOWN_TIME) { + log.debug("The AdminClient is not accepting new calls. Timing out {}.", call); + call.fail(Long.MAX_VALUE, new TimeoutException("The AdminClient thread is not accepting new calls.")); + } else { + enqueue(call, now); + } + } + + /** + * Create a new metadata call. + */ + private Call makeMetadataCall(long now) { + return new Call(true, "fetchMetadata", calcDeadlineMs(now, requestTimeoutMs), + new MetadataUpdateNodeIdProvider()) { + @Override + public MetadataRequest.Builder createRequest(int timeoutMs) { + // Since this only requests node information, it's safe to pass true + // for allowAutoTopicCreation (and it simplifies communication with + // older brokers) + return new MetadataRequest.Builder(new MetadataRequestData() + .setTopics(Collections.emptyList()) + .setAllowAutoTopicCreation(true)); + } + + @Override + public void handleResponse(AbstractResponse abstractResponse) { + MetadataResponse response = (MetadataResponse) abstractResponse; + long now = time.milliseconds(); + metadataManager.update(response.buildCluster(), now); + + // Unassign all unsent requests after a metadata refresh to allow for a new + // destination to be selected from the new metadata + unassignUnsentCalls(node -> true); + } + + @Override + public void handleFailure(Throwable e) { + metadataManager.updateFailed(e); + } + }; + } + } + + private static boolean groupIdIsUnrepresentable(String groupId) { + return groupId == null; + } + + private void rescheduleFindCoordinatorTask(ConsumerGroupOperationContext context, Supplier nextCall, Call failedCall) { + log.info("Node {} is no longer the Coordinator. Retrying with new coordinator.", + context.node().orElse(null)); + // Requeue the task so that we can try with new coordinator + context.setNode(null); + + Call call = nextCall.get(); + call.tries = failedCall.tries + 1; + call.nextAllowedTryMs = calculateNextAllowedRetryMs(); + + Call findCoordinatorCall = getFindCoordinatorCall(context, nextCall); + runnable.call(findCoordinatorCall, time.milliseconds()); + } + + private static Map> createFutures(Collection groupIds) { + return new HashSet<>(groupIds).stream().collect( + Collectors.toMap(groupId -> groupId, + groupId -> { + if (groupIdIsUnrepresentable(groupId)) { + KafkaFutureImpl future = new KafkaFutureImpl<>(); + future.completeExceptionally(new InvalidGroupIdException("The given group id '" + + groupId + "' cannot be represented in a request.")); + return future; + } else { + return new KafkaFutureImpl<>(); + } + } + )); + } + + public KSDescribeGroupsResult describeConsumerGroups(final Collection groupIds, + final DescribeConsumerGroupsOptions options) { + + final Map> futures = createFutures(groupIds); + + // TODO: KAFKA-6788, we should consider grouping the request per coordinator and send one request with a list of + // all consumer groups this coordinator host + for (final Map.Entry> entry : futures.entrySet()) { + // skip sending request for those futures that already failed. + if (entry.getValue().isCompletedExceptionally()) + continue; + + final String groupId = entry.getKey(); + + final long startFindCoordinatorMs = time.milliseconds(); + final long deadline = calcDeadlineMs(startFindCoordinatorMs, options.timeoutMs()); + ConsumerGroupOperationContext context = + new ConsumerGroupOperationContext<>(groupId, options, deadline, futures.get(groupId)); + Call findCoordinatorCall = getFindCoordinatorCall(context, + () -> getDescribeConsumerGroupsCall(context)); + runnable.call(findCoordinatorCall, startFindCoordinatorMs); + } + + return new KSDescribeGroupsResult(new HashMap<>(futures)); + } + + /** + * Returns a {@code Call} object to fetch the coordinator for a consumer group id. Takes another Call + * parameter to schedule action that need to be taken using the coordinator. The param is a Supplier + * so that it can be lazily created, so that it can use the results of find coordinator call in its + * construction. + * + * @param The type of return value of the KafkaFuture, like ConsumerGroupDescription, Void etc. + * @param The type of configuration option, like DescribeConsumerGroupsOptions, ListConsumerGroupsOptions etc + */ + private > Call getFindCoordinatorCall(ConsumerGroupOperationContext context, + Supplier nextCall) { + return new Call("findCoordinator", context.deadline(), new LeastLoadedNodeProvider()) { + @Override + FindCoordinatorRequest.Builder createRequest(int timeoutMs) { + return new FindCoordinatorRequest.Builder( + new FindCoordinatorRequestData() + .setKeyType(CoordinatorType.GROUP.id()) + .setKey(context.groupId())); + } + + @Override + void handleResponse(AbstractResponse abstractResponse) { + final FindCoordinatorResponse response = (FindCoordinatorResponse) abstractResponse; + + if (handleGroupRequestError(response.error(), context.future())) + return; + + context.setNode(response.node()); + + runnable.call(nextCall.get(), time.milliseconds()); + } + + @Override + void handleFailure(Throwable throwable) { + context.future().completeExceptionally(throwable); + } + }; + } + + private Call getDescribeConsumerGroupsCall( + ConsumerGroupOperationContext context) { + return new Call("describeConsumerGroups", + context.deadline(), + new ConstantNodeIdProvider(context.node().get().id())) { + @Override + DescribeGroupsRequest.Builder createRequest(int timeoutMs) { + return new DescribeGroupsRequest.Builder( + new DescribeGroupsRequestData() + .setGroups(Collections.singletonList(context.groupId())) + .setIncludeAuthorizedOperations(context.options().includeAuthorizedOperations())); + } + + @Override + void handleResponse(AbstractResponse abstractResponse) { + final DescribeGroupsResponse response = (DescribeGroupsResponse) abstractResponse; + + List describedGroups = response.data().groups(); + if (describedGroups.isEmpty()) { + context.future().completeExceptionally( + new InvalidGroupIdException("No consumer group found for GroupId: " + context.groupId())); + return; + } + + if (describedGroups.size() > 1 || + !describedGroups.get(0).groupId().equals(context.groupId())) { + String ids = Arrays.toString(describedGroups.stream().map(DescribedGroup::groupId).toArray()); + context.future().completeExceptionally(new InvalidGroupIdException( + "DescribeConsumerGroup request for GroupId: " + context.groupId() + " returned " + ids)); + return; + } + + final DescribedGroup describedGroup = describedGroups.get(0); + + // If coordinator changed since we fetched it, retry + if (ConsumerGroupOperationContext.hasCoordinatorMoved(response)) { + Call call = getDescribeConsumerGroupsCall(context); + rescheduleFindCoordinatorTask(context, () -> call, this); + return; + } + + final Errors groupError = Errors.forCode(describedGroup.errorCode()); + if (handleGroupRequestError(groupError, context.future())) { + return; + } + + final String protocolType = describedGroup.protocolType(); + + final List memberDescriptions = new ArrayList<>(describedGroup.members().size()); + for (DescribedGroupMember groupMember : describedGroup.members()) { + KSMemberBaseAssignment memberBaseAssignment = null; + + if (protocolType.equals(ConsumerProtocol.PROTOCOL_TYPE) || protocolType.isEmpty()) { + if (groupMember.memberAssignment().length > 0) { + final Assignment assignment = ConsumerProtocol.deserializeAssignment(ByteBuffer.wrap(groupMember.memberAssignment())); + memberBaseAssignment = new KSMemberConsumerAssignment(new HashSet<>(assignment.partitions())); + } + } else { + ConnectProtocol.Assignment assignment = null; + if (groupMember.memberAssignment().length > 0) { + assignment = ConnectProtocol. + deserializeAssignment(ByteBuffer.wrap(groupMember.memberAssignment())); + } + + ConnectProtocol.WorkerState workerState = null; + if (groupMember.memberMetadata().length > 0) { + workerState = ConnectProtocol. + deserializeMetadata(ByteBuffer.wrap(groupMember.memberMetadata())); + } + + memberBaseAssignment = new KSMemberConnectAssignment(assignment, workerState); + } + + memberDescriptions.add(new KSMemberDescription( + groupMember.memberId(), + Optional.ofNullable(groupMember.groupInstanceId()), + groupMember.clientId(), + groupMember.clientHost(), + memberBaseAssignment + )); + } + + context.future().complete(new KSGroupDescription( + context.groupId(), + protocolType, + memberDescriptions, + describedGroup.protocolData(), + ConsumerGroupState.parse(describedGroup.groupState()), + context.node().get()) + ); + } + + @Override + void handleFailure(Throwable throwable) { + context.future().completeExceptionally(throwable); + } + }; + } + + + private Set validAclOperations(final int authorizedOperations) { + if (authorizedOperations == MetadataResponse.AUTHORIZED_OPERATIONS_OMITTED) { + return null; + } + return Utils.from32BitField(authorizedOperations) + .stream() + .map(AclOperation::fromCode) + .filter(operation -> operation != AclOperation.UNKNOWN + && operation != AclOperation.ALL + && operation != AclOperation.ANY) + .collect(Collectors.toSet()); + } + + private boolean handleGroupRequestError(Errors error, KafkaFutureImpl future) { + if (error == Errors.COORDINATOR_LOAD_IN_PROGRESS || error == Errors.COORDINATOR_NOT_AVAILABLE) { + throw error.exception(); + } else if (error != Errors.NONE) { + future.completeExceptionally(error.exception()); + return true; + } + return false; + } + + private final static class ListConsumerGroupsResults { + private final List errors; + private final HashMap listings; + private final HashSet remaining; + private final KafkaFutureImpl> future; + + ListConsumerGroupsResults(Collection leaders, + KafkaFutureImpl> future) { + this.errors = new ArrayList<>(); + this.listings = new HashMap<>(); + this.remaining = new HashSet<>(leaders); + this.future = future; + tryComplete(); + } + + synchronized void addError(Throwable throwable, Node node) { + ApiError error = ApiError.fromThrowable(throwable); + if (error.message() == null || error.message().isEmpty()) { + errors.add(error.error().exception("Error listing groups on " + node)); + } else { + errors.add(error.error().exception("Error listing groups on " + node + ": " + error.message())); + } + } + + synchronized void addListing(ConsumerGroupListing listing) { + listings.put(listing.groupId(), listing); + } + + synchronized void tryComplete(Node leader) { + remaining.remove(leader); + tryComplete(); + } + + private synchronized void tryComplete() { + if (remaining.isEmpty()) { + ArrayList results = new ArrayList<>(listings.values()); + results.addAll(errors); + future.complete(results); + } + } + } + + public KSListGroupsResult listConsumerGroups(ListConsumerGroupsOptions options) { + final KafkaFutureImpl> all = new KafkaFutureImpl<>(); + final long nowMetadata = time.milliseconds(); + final long deadline = calcDeadlineMs(nowMetadata, options.timeoutMs()); + runnable.call(new Call("findAllBrokers", deadline, new LeastLoadedNodeProvider()) { + @Override + MetadataRequest.Builder createRequest(int timeoutMs) { + return new MetadataRequest.Builder(new MetadataRequestData() + .setTopics(Collections.emptyList()) + .setAllowAutoTopicCreation(true)); + } + + @Override + void handleResponse(AbstractResponse abstractResponse) { + MetadataResponse metadataResponse = (MetadataResponse) abstractResponse; + Collection nodes = metadataResponse.brokers(); + if (nodes.isEmpty()) + throw new StaleMetadataException("Metadata fetch failed due to missing broker list"); + + HashSet allNodes = new HashSet<>(nodes); + final ListConsumerGroupsResults results = new ListConsumerGroupsResults(allNodes, all); + + for (final Node node : allNodes) { + final long nowList = time.milliseconds(); + runnable.call(new Call("listConsumerGroups", deadline, new ConstantNodeIdProvider(node.id())) { + @Override + ListGroupsRequest.Builder createRequest(int timeoutMs) { + List states = options.states() + .stream() + .map(s -> s.toString()) + .collect(Collectors.toList()); + return new ListGroupsRequest.Builder(new ListGroupsRequestData().setStatesFilter(states)); + } + + private void maybeAddConsumerGroup(ListGroupsResponseData.ListedGroup group) { + String protocolType = group.protocolType(); + + @KafkaSource(modified = 1, modifyDesc = "原先代码忽略了connect的消费组,这里修改为将其放开") + final String groupId = group.groupId(); + final Optional state = group.groupState().equals("") + ? Optional.empty() + : Optional.of(ConsumerGroupState.parse(group.groupState())); + final ConsumerGroupListing groupListing = new ConsumerGroupListing(groupId, protocolType.isEmpty(), state); + results.addListing(groupListing); + } + + @Override + void handleResponse(AbstractResponse abstractResponse) { + final ListGroupsResponse response = (ListGroupsResponse) abstractResponse; + synchronized (results) { + Errors error = Errors.forCode(response.data().errorCode()); + if (error == Errors.COORDINATOR_LOAD_IN_PROGRESS || error == Errors.COORDINATOR_NOT_AVAILABLE) { + throw error.exception(); + } else if (error != Errors.NONE) { + results.addError(error.exception(), node); + } else { + for (ListGroupsResponseData.ListedGroup group : response.data().groups()) { + maybeAddConsumerGroup(group); + } + } + results.tryComplete(node); + } + } + + @Override + void handleFailure(Throwable throwable) { + synchronized (results) { + results.addError(throwable, node); + results.tryComplete(node); + } + } + }, nowList); + } + } + + @Override + void handleFailure(Throwable throwable) { + KafkaException exception = new KafkaException("Failed to find brokers to send ListGroups", throwable); + all.complete(Collections.singletonList(exception)); + } + }, nowMetadata); + + return new KSListGroupsResult(all); + } + + + private long calculateNextAllowedRetryMs() { + return time.milliseconds() + retryBackoffMs; + } +} From 249fe7c7001df9fa8dc5e12eced4f0fcc6e820b7 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 17:59:39 +0800 Subject: [PATCH 056/150] =?UTF-8?q?=E8=B0=83=E6=95=B4ES=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=BD=8D=E7=BD=AE=20&=20=E8=A1=A5=E5=85=85co?= =?UTF-8?q?nnectESDAO=E7=9B=B8=E5=85=B3=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connect/ConnectClusterMetrics.java | 35 + .../metrics/connect/ConnectWorkerMetrics.java | 35 + .../metrics/connect/ConnectorMetrics.java | 39 + .../metrics/connect/ConnectorTaskMetrics.java | 39 + .../connect/ConnectClusterMetricPO.java | 30 + .../po/metrice/connect/ConnectorMetricPO.java | 39 + .../km/common/constant/ESIndexConstant.java | 709 ------------------ km-dist/init/template/ks_kafka_broker_metric | 3 +- km-dist/init/template/ks_kafka_cluster_metric | 5 +- .../template/ks_kafka_connect_cluster_metric | 86 +++ .../ks_kafka_connect_connector_metric | 194 +++++ km-dist/init/template/ks_kafka_group_metric | 1 - .../init/template/ks_kafka_partition_metric | 1 - .../init/template/ks_kafka_replication_metric | 3 +- km-dist/init/template/ks_kafka_topic_metric | 1 - .../init/template/ks_kafka_zookeeper_metric | 1 - .../km/persistence/es/ESFileLoader.java | 155 ++++ .../persistence/es/dao/BaseMetricESDAO.java | 22 +- .../persistence/es/dao/BrokerMetricESDAO.java | 21 +- .../es/dao/ClusterMetricESDAO.java | 17 +- .../persistence/es/dao/GroupMetricESDAO.java | 21 +- .../es/dao/PartitionMetricESDAO.java | 13 +- .../es/dao/ReplicationMetricESDAO.java | 13 +- .../persistence/es/dao/TopicMetricESDAO.java | 29 +- .../es/dao/ZookeeperMetricESDAO.java | 12 +- .../connect/ConnectClusterMetricESDAO.java | 275 +++++++ .../es/dao/connect/ConnectorMetricESDAO.java | 365 +++++++++ .../{DslsConstant.java => DslConstant.java} | 18 +- .../km/persistence/es/dsls/DslLoaderUtil.java | 183 +---- .../es/template/TemplateConstant.java | 19 + .../es/template/TemplateLoaderUtil.java | 28 + .../dsl/BaseMetricESDAO/getLatestMetricTime | 0 .../BrokerMetricESDAO/getAggListBrokerMetrics | 0 .../getAggSingleBrokerMetrics | 0 .../BrokerMetricESDAO/getAggTopMetricsBrokers | 0 .../BrokerMetricESDAO/getBrokerLatestMetrics | 0 .../getAggListClusterMetrics | 0 .../getAggSingleClusterMetrics | 0 .../getClusterLatestMetrics | 0 .../listClusterWithLatestMetrics | 0 .../getAggListConnectClusterMetrics | 44 ++ .../getAggTopMetricsConnectClusters | 45 ++ .../getConnectorAggListMetric | 51 ++ .../getConnectorAggTopMetric | 45 ++ .../getConnectorLatestMetric | 39 + .../GroupMetricESDAO/countGroupMetricValue | 0 .../GroupMetricESDAO/countGroupNotMetricValue | 0 .../GroupMetricESDAO/getTopicPartitionOfGroup | 0 .../dsl/GroupMetricESDAO/listGroupMetrics | 0 .../listLatestMetricsAggByGroupTopic | 0 .../listPartitionLatestMetrics | 0 .../getPartitionLatestMetrics | 0 .../listPartitionLatestMetricsByTopic | 0 .../getAggSingleReplicationMetrics | 0 .../getReplicationLatestMetrics | 0 .../TopicMetricESDAO/countTopicMetricValue | 0 .../TopicMetricESDAO/countTopicNotMetricValue | 0 .../dsl/TopicMetricESDAO/getAggListMetrics | 0 .../dsl/TopicMetricESDAO/getAggSingleMetrics | 0 .../TopicMetricESDAO/getAggTopMetricsTopics | 0 .../TopicMetricESDAO/getMaxOrMinSingleMetric | 0 .../dsl/TopicMetricESDAO/getTopicLatestMetric | 0 .../getTopicLatestMetricByBrokerId | 0 .../listTopicWithLatestMetrics | 0 .../getAggListZookeeperMetrics | 0 .../es/template/ks_kafka_broker_metric | 101 +++ .../es/template/ks_kafka_cluster_metric | 186 +++++ .../template/ks_kafka_connect_cluster_metric | 86 +++ .../ks_kafka_connect_connector_metric | 194 +++++ .../es/template/ks_kafka_group_metric | 74 ++ .../es/template/ks_kafka_partition_metric | 65 ++ .../es/template/ks_kafka_replication_metric | 65 ++ .../es/template/ks_kafka_topic_metric | 116 +++ .../es/template/ks_kafka_zookeeper_metric | 84 +++ 74 files changed, 2640 insertions(+), 967 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectClusterMetrics.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectWorkerMetrics.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorMetrics.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectClusterMetricPO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectorMetricPO.java delete mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESIndexConstant.java create mode 100644 km-dist/init/template/ks_kafka_connect_cluster_metric create mode 100644 km-dist/init/template/ks_kafka_connect_connector_metric create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESFileLoader.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java rename km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/{DslsConstant.java => DslConstant.java} (83%) create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateLoaderUtil.java rename km-persistence/src/main/resources/{ => es}/dsl/BaseMetricESDAO/getLatestMetricTime (100%) rename km-persistence/src/main/resources/{ => es}/dsl/BrokerMetricESDAO/getAggListBrokerMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/BrokerMetricESDAO/getAggSingleBrokerMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/BrokerMetricESDAO/getAggTopMetricsBrokers (100%) rename km-persistence/src/main/resources/{ => es}/dsl/BrokerMetricESDAO/getBrokerLatestMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ClusterMetricESDAO/getAggListClusterMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ClusterMetricESDAO/getAggSingleClusterMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ClusterMetricESDAO/getClusterLatestMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ClusterMetricESDAO/listClusterWithLatestMetrics (100%) create mode 100644 km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggListConnectClusterMetrics create mode 100644 km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggTopMetricsConnectClusters create mode 100644 km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggListMetric create mode 100644 km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggTopMetric create mode 100644 km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorLatestMetric rename km-persistence/src/main/resources/{ => es}/dsl/GroupMetricESDAO/countGroupMetricValue (100%) rename km-persistence/src/main/resources/{ => es}/dsl/GroupMetricESDAO/countGroupNotMetricValue (100%) rename km-persistence/src/main/resources/{ => es}/dsl/GroupMetricESDAO/getTopicPartitionOfGroup (100%) rename km-persistence/src/main/resources/{ => es}/dsl/GroupMetricESDAO/listGroupMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/GroupMetricESDAO/listLatestMetricsAggByGroupTopic (100%) rename km-persistence/src/main/resources/{ => es}/dsl/GroupMetricESDAO/listPartitionLatestMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/PartitionMetricESDAO/getPartitionLatestMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/PartitionMetricESDAO/listPartitionLatestMetricsByTopic (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/countTopicMetricValue (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/countTopicNotMetricValue (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/getAggListMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/getAggSingleMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/getAggTopMetricsTopics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/getMaxOrMinSingleMetric (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/getTopicLatestMetric (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/getTopicLatestMetricByBrokerId (100%) rename km-persistence/src/main/resources/{ => es}/dsl/TopicMetricESDAO/listTopicWithLatestMetrics (100%) rename km-persistence/src/main/resources/{ => es}/dsl/ZookeeperMetricESDAO/getAggListZookeeperMetrics (100%) create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_broker_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_cluster_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_connect_cluster_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_connect_connector_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_group_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_partition_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_replication_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_topic_metric create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectClusterMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectClusterMetrics.java new file mode 100644 index 00000000..fe710391 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectClusterMetrics.java @@ -0,0 +1,35 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @author zengqiao + * @date 20/6/17 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class ConnectClusterMetrics extends BaseMetrics { + private Long connectClusterId; + + public ConnectClusterMetrics(Long clusterPhyId, Long connectClusterId){ + super(clusterPhyId); + this.connectClusterId = connectClusterId; + } + + public static ConnectClusterMetrics initWithMetric(Long connectClusterId, String metric, Float value) { + ConnectClusterMetrics brokerMetrics = new ConnectClusterMetrics(connectClusterId, connectClusterId); + brokerMetrics.putMetric(metric, value); + return brokerMetrics; + } + + @Override + public String unique() { + return "KCC@" + clusterPhyId + "@" + connectClusterId; + } +} \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectWorkerMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectWorkerMetrics.java new file mode 100644 index 00000000..78d9fe06 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectWorkerMetrics.java @@ -0,0 +1,35 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @author wyb + * @date 2022/11/2 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class ConnectWorkerMetrics extends BaseMetrics { + + private Long connectClusterId; + + private String workerId; + + public static ConnectWorkerMetrics initWithMetric(Long connectClusterId, String workerId, String metric, Float value) { + ConnectWorkerMetrics connectWorkerMetrics = new ConnectWorkerMetrics(); + connectWorkerMetrics.setConnectClusterId(connectClusterId); + connectWorkerMetrics.setWorkerId(workerId); + connectWorkerMetrics.putMetric(metric, value); + return connectWorkerMetrics; + } + + @Override + public String unique() { + return "KCC@" + clusterPhyId + "@" + connectClusterId + "@" + workerId; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorMetrics.java new file mode 100644 index 00000000..08540ed5 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorMetrics.java @@ -0,0 +1,39 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @author zengqiao + * @date 20/6/17 + */ +@Data +@NoArgsConstructor +@ToString +public class ConnectorMetrics extends BaseMetrics { + private Long connectClusterId; + + private String connectorName; + + private String connectorNameAndClusterId; + + public ConnectorMetrics(Long connectClusterId, String connectorName) { + super(null); + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.connectorNameAndClusterId = connectorName + "#" + connectClusterId; + } + + public static ConnectorMetrics initWithMetric(Long connectClusterId, String connectorName, String metricName, Float value) { + ConnectorMetrics metrics = new ConnectorMetrics(connectClusterId, connectorName); + metrics.putMetric(metricName, value); + return metrics; + } + + @Override + public String unique() { + return "KCOR@" + connectClusterId + "@" + connectorName; + } +} \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java new file mode 100644 index 00000000..26b244e7 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java @@ -0,0 +1,39 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @author wyb + * @date 2022/11/4 + */ +@Data +@NoArgsConstructor +@ToString +public class ConnectorTaskMetrics extends BaseMetrics { + private Long connectClusterId; + + private String connectorName; + + private Integer taskId; + + public ConnectorTaskMetrics(Long connectClusterId, String connectorName, Integer taskId) { + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.taskId = taskId; + } + + public static ConnectorTaskMetrics initWithMetric(Long connectClusterId, String connectorName, Integer taskId, String metricName, Float value) { + ConnectorTaskMetrics metrics = new ConnectorTaskMetrics(connectClusterId, connectorName, taskId); + metrics.putMetric(metricName,value); + return metrics; + } + + @Override + public String unique() { + return "KCOR@" + connectClusterId + "@" + connectorName + "@" + taskId; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectClusterMetricPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectClusterMetricPO.java new file mode 100644 index 00000000..462c7763 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectClusterMetricPO.java @@ -0,0 +1,30 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BaseMetricESPO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import static com.xiaojukeji.know.streaming.km.common.utils.CommonUtils.monitorTimestamp2min; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectClusterMetricPO extends BaseMetricESPO { + private Long connectClusterId; + + public ConnectClusterMetricPO(Long kafkaClusterPhyId, Long connectClusterId){ + super(kafkaClusterPhyId); + this.connectClusterId = connectClusterId; + } + + @Override + public String getKey() { + return "KCC@" + clusterPhyId + "@" + connectClusterId + "@" + monitorTimestamp2min(timestamp); + } + + @Override + public String getRoutingValue() { + return String.valueOf(connectClusterId); + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectorMetricPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectorMetricPO.java new file mode 100644 index 00000000..7f3f7d71 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/connect/ConnectorMetricPO.java @@ -0,0 +1,39 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BaseMetricESPO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import static com.xiaojukeji.know.streaming.km.common.utils.CommonUtils.monitorTimestamp2min; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectorMetricPO extends BaseMetricESPO { + private Long connectClusterId; + + private String connectorName; + + /** + * 用于es内部排序 + */ + private String connectorNameAndClusterId; + + public ConnectorMetricPO(Long kafkaClusterPhyId, Long connectClusterId, String connectorName){ + super(kafkaClusterPhyId); + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.connectorNameAndClusterId = connectorName + "#" + connectClusterId; + } + + @Override + public String getKey() { + return "KCOR@" + clusterPhyId + "@" + connectClusterId + "@" + connectorName + "@" + monitorTimestamp2min(timestamp); + } + + @Override + public String getRoutingValue() { + return String.valueOf(connectClusterId); + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESIndexConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESIndexConstant.java deleted file mode 100644 index 1f5654d0..00000000 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESIndexConstant.java +++ /dev/null @@ -1,709 +0,0 @@ -package com.xiaojukeji.know.streaming.km.common.constant; - -public class ESIndexConstant { - - public final static String TOPIC_INDEX = "ks_kafka_topic_metric"; - public final static String TOPIC_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_topic_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"brokerId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"topic\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"BytesIn_min_15\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"Messages\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesRejected\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"PartitionURP\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthCheckTotal\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"ReplicationCount\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"ReplicationBytesOut\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"ReplicationBytesIn\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"FailedFetchRequests\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesIn_min_5\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthScore\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"LogSize\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesOut\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesOut_min_15\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"FailedProduceRequests\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesIn\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesOut_min_5\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"MessagesIn\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"TotalProduceRequests\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthCheckPassed\" : {\n" + - " \"type\" : \"float\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"brokerAgg\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"index\" : true,\n" + - " \"type\" : \"date\",\n" + - " \"doc_values\" : true\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; - - public final static String CLUSTER_INDEX = "ks_kafka_cluster_metric"; - public final static String CLUSTER_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_cluster_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"Connections\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"BytesIn_min_15\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"PartitionURP\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthScore_Topics\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"EventQueueSize\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"ActiveControllerCount\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"GroupDeads\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"BytesIn_min_5\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckTotal_Topics\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Partitions\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"BytesOut\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Groups\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"BytesOut_min_15\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"TotalRequestQueueSize\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckPassed_Groups\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"TotalProduceRequests\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckPassed\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"TotalLogSize\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"GroupEmptys\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"PartitionNoLeader\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthScore_Brokers\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Messages\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Topics\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"PartitionMinISR_E\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckTotal\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Brokers\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Replicas\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckTotal_Groups\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"GroupRebalances\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"MessageIn\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthScore\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckPassed_Topics\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckTotal_Brokers\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"PartitionMinISR_S\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"BytesIn\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"BytesOut_min_5\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"GroupActives\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"MessagesIn\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"GroupReBalances\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckPassed_Brokers\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthScore_Groups\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"TotalResponseQueueSize\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"Zookeepers\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"LeaderMessages\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthScore_Cluster\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckPassed_Cluster\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"HealthCheckTotal_Cluster\" : {\n" + - " \"type\" : \"double\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"type\" : \"date\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; - - public final static String BROKER_INDEX = "ks_kafka_broker_metric"; - public final static String BROKER_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_broker_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"brokerId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"NetworkProcessorAvgIdle\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"UnderReplicatedPartitions\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesIn_min_15\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthCheckTotal\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"RequestHandlerAvgIdle\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"connectionsCount\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesIn_min_5\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthScore\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesOut\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesOut_min_15\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesIn\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"BytesOut_min_5\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"TotalRequestQueueSize\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"MessagesIn\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"TotalProduceRequests\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthCheckPassed\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"TotalResponseQueueSize\" : {\n" + - " \"type\" : \"float\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"index\" : true,\n" + - " \"type\" : \"date\",\n" + - " \"doc_values\" : true\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; - - public final static String PARTITION_INDEX = "ks_kafka_partition_metric"; - public final static String PARTITION_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_partition_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"brokerId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"partitionId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"topic\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"LogStartOffset\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"Messages\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"LogEndOffset\" : {\n" + - " \"type\" : \"float\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"index\" : true,\n" + - " \"type\" : \"date\",\n" + - " \"doc_values\" : true\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; - - public final static String GROUP_INDEX = "ks_kafka_group_metric"; - public final static String GROUP_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_group_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"group\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"partitionId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"topic\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"HealthScore\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"Lag\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"OffsetConsumed\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthCheckTotal\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"HealthCheckPassed\" : {\n" + - " \"type\" : \"float\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"groupMetric\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"index\" : true,\n" + - " \"type\" : \"date\",\n" + - " \"doc_values\" : true\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; - - public final static String REPLICATION_INDEX = "ks_kafka_replication_metric"; - public final static String REPLICATION_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_replication_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"brokerId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"partitionId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"topic\" : {\n" + - " \"type\" : \"keyword\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"LogStartOffset\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"Messages\" : {\n" + - " \"type\" : \"float\"\n" + - " },\n" + - " \"LogEndOffset\" : {\n" + - " \"type\" : \"float\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"index\" : true,\n" + - " \"type\" : \"date\",\n" + - " \"doc_values\" : true\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; - - public final static String ZOOKEEPER_INDEX = "ks_kafka_zookeeper_metric"; - public final static String ZOOKEEPER_TEMPLATE = "{\n" + - " \"order\" : 10,\n" + - " \"index_patterns\" : [\n" + - " \"ks_kafka_zookeeper_metric*\"\n" + - " ],\n" + - " \"settings\" : {\n" + - " \"index\" : {\n" + - " \"number_of_shards\" : \"10\"\n" + - " }\n" + - " },\n" + - " \"mappings\" : {\n" + - " \"properties\" : {\n" + - " \"routingValue\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"clusterPhyId\" : {\n" + - " \"type\" : \"long\"\n" + - " },\n" + - " \"metrics\" : {\n" + - " \"properties\" : {\n" + - " \"AvgRequestLatency\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"MinRequestLatency\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"MaxRequestLatency\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"OutstandingRequests\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"NodeCount\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"WatchCount\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"NumAliveConnections\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"PacketsReceived\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"PacketsSent\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"EphemeralsCount\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"ApproximateDataSize\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"OpenFileDescriptorCount\" : {\n" + - " \"type\" : \"double\"\n" + - " },\n" + - " \"MaxFileDescriptorCount\" : {\n" + - " \"type\" : \"double\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"key\" : {\n" + - " \"type\" : \"text\",\n" + - " \"fields\" : {\n" + - " \"keyword\" : {\n" + - " \"ignore_above\" : 256,\n" + - " \"type\" : \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"timestamp\" : {\n" + - " \"format\" : \"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis\",\n" + - " \"type\" : \"date\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"aliases\" : { }\n" + - " }"; -} diff --git a/km-dist/init/template/ks_kafka_broker_metric b/km-dist/init/template/ks_kafka_broker_metric index 78933a4d..749b1494 100644 --- a/km-dist/init/template/ks_kafka_broker_metric +++ b/km-dist/init/template/ks_kafka_broker_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_broker_metric { "order" : 10, "index_patterns" : [ @@ -6,7 +5,7 @@ PUT _template/ks_kafka_broker_metric ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { diff --git a/km-dist/init/template/ks_kafka_cluster_metric b/km-dist/init/template/ks_kafka_cluster_metric index 76d51cbd..942fdce2 100644 --- a/km-dist/init/template/ks_kafka_cluster_metric +++ b/km-dist/init/template/ks_kafka_cluster_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_cluster_metric { "order" : 10, "index_patterns" : [ @@ -6,7 +5,7 @@ PUT _template/ks_kafka_cluster_metric ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -184,4 +183,4 @@ PUT _template/ks_kafka_cluster_metric } }, "aliases" : { } - } \ No newline at end of file + } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_connect_cluster_metric b/km-dist/init/template/ks_kafka_connect_cluster_metric new file mode 100644 index 00000000..7fa4c523 --- /dev/null +++ b/km-dist/init/template/ks_kafka_connect_cluster_metric @@ -0,0 +1,86 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_cluster_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "ConnectorCount" : { + "type" : "float" + }, + "TaskCount" : { + "type" : "float" + }, + "ConnectorStartupAttemptsTotal" : { + "type" : "float" + }, + "ConnectorStartupFailurePercentage" : { + "type" : "float" + }, + "ConnectorStartupFailureTotal" : { + "type" : "float" + }, + "ConnectorStartupSuccessPercentage" : { + "type" : "float" + }, + "ConnectorStartupSuccessTotal" : { + "type" : "float" + }, + "TaskStartupAttemptsTotal" : { + "type" : "float" + }, + "TaskStartupFailurePercentage" : { + "type" : "float" + }, + "TaskStartupFailureTotal" : { + "type" : "float" + }, + "TaskStartupSuccessPercentage" : { + "type" : "float" + }, + "TaskStartupSuccessTotal" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_connect_connector_metric b/km-dist/init/template/ks_kafka_connect_connector_metric new file mode 100644 index 00000000..b26836a0 --- /dev/null +++ b/km-dist/init/template/ks_kafka_connect_connector_metric @@ -0,0 +1,194 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_connector_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "connectorName" : { + "type" : "keyword" + }, + "connectorNameAndClusterId" : { + "type" : "keyword" + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "HealthState" : { + "type" : "float" + }, + "ConnectorTotalTaskCount" : { + "type" : "float" + }, + "HealthCheckPassed" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "ConnectorRunningTaskCount" : { + "type" : "float" + }, + "ConnectorPausedTaskCount" : { + "type" : "float" + }, + "ConnectorFailedTaskCount" : { + "type" : "float" + }, + "ConnectorUnassignedTaskCount" : { + "type" : "float" + }, + "BatchSizeAvg" : { + "type" : "float" + }, + "BatchSizeMax" : { + "type" : "float" + }, + "OffsetCommitAvgTimeMs" : { + "type" : "float" + }, + "OffsetCommitMaxTimeMs" : { + "type" : "float" + }, + "OffsetCommitFailurePercentage" : { + "type" : "float" + }, + "OffsetCommitSuccessPercentage" : { + "type" : "float" + }, + "PollBatchAvgTimeMs" : { + "type" : "float" + }, + "PollBatchMaxTimeMs" : { + "type" : "float" + }, + "SourceRecordActiveCount" : { + "type" : "float" + }, + "SourceRecordActiveCountAvg" : { + "type" : "float" + }, + "SourceRecordActiveCountMax" : { + "type" : "float" + }, + "SourceRecordPollRate" : { + "type" : "float" + }, + "SourceRecordPollTotal" : { + "type" : "float" + }, + "SourceRecordWriteRate" : { + "type" : "float" + }, + "SourceRecordWriteTotal" : { + "type" : "float" + }, + "OffsetCommitCompletionRate" : { + "type" : "float" + }, + "OffsetCommitCompletionTotal" : { + "type" : "float" + }, + "OffsetCommitSkipRate" : { + "type" : "float" + }, + "OffsetCommitSkipTotal" : { + "type" : "float" + }, + "PartitionCount" : { + "type" : "float" + }, + "PutBatchAvgTimeMs" : { + "type" : "float" + }, + "PutBatchMaxTimeMs" : { + "type" : "float" + }, + "SinkRecordActiveCount" : { + "type" : "float" + }, + "SinkRecordActiveCountAvg" : { + "type" : "float" + }, + "SinkRecordActiveCountMax" : { + "type" : "float" + }, + "SinkRecordLagMax" : { + "type" : "float" + }, + "SinkRecordReadRate" : { + "type" : "float" + }, + "SinkRecordReadTotal" : { + "type" : "float" + }, + "SinkRecordSendRate" : { + "type" : "float" + }, + "SinkRecordSendTotal" : { + "type" : "float" + }, + "DeadletterqueueProduceFailures" : { + "type" : "float" + }, + "DeadletterqueueProduceRequests" : { + "type" : "float" + }, + "LastErrorTimestamp" : { + "type" : "float" + }, + "TotalErrorsLogged" : { + "type" : "float" + }, + "TotalRecordErrors" : { + "type" : "float" + }, + "TotalRecordFailures" : { + "type" : "float" + }, + "TotalRecordsSkipped" : { + "type" : "float" + }, + "TotalRetries" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_group_metric b/km-dist/init/template/ks_kafka_group_metric index 9fe7008b..3f04f16a 100644 --- a/km-dist/init/template/ks_kafka_group_metric +++ b/km-dist/init/template/ks_kafka_group_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_group_metric { "order" : 10, "index_patterns" : [ diff --git a/km-dist/init/template/ks_kafka_partition_metric b/km-dist/init/template/ks_kafka_partition_metric index 2538eba3..68264660 100644 --- a/km-dist/init/template/ks_kafka_partition_metric +++ b/km-dist/init/template/ks_kafka_partition_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_partition_metric { "order" : 10, "index_patterns" : [ diff --git a/km-dist/init/template/ks_kafka_replication_metric b/km-dist/init/template/ks_kafka_replication_metric index c9c254be..a8ff4b53 100644 --- a/km-dist/init/template/ks_kafka_replication_metric +++ b/km-dist/init/template/ks_kafka_replication_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_replication_metric { "order" : 10, "index_patterns" : [ @@ -63,4 +62,4 @@ PUT _template/ks_kafka_replication_metric } }, "aliases" : { } - } + } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_topic_metric b/km-dist/init/template/ks_kafka_topic_metric index a2456dc6..b2eed35d 100644 --- a/km-dist/init/template/ks_kafka_topic_metric +++ b/km-dist/init/template/ks_kafka_topic_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_topic_metric { "order" : 10, "index_patterns" : [ diff --git a/km-dist/init/template/ks_kafka_zookeeper_metric b/km-dist/init/template/ks_kafka_zookeeper_metric index abb54a61..e2efb41a 100644 --- a/km-dist/init/template/ks_kafka_zookeeper_metric +++ b/km-dist/init/template/ks_kafka_zookeeper_metric @@ -1,4 +1,3 @@ -PUT _template/ks_kafka_zookeeper_metric { "order" : 10, "index_patterns" : [ diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESFileLoader.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESFileLoader.java new file mode 100644 index 00000000..2702f329 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESFileLoader.java @@ -0,0 +1,155 @@ +package com.xiaojukeji.know.streaming.km.persistence.es; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.DefaultJSONParser; +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.didiglobal.logi.log.ILog; +import com.google.common.collect.Lists; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author didi + */ +public class ESFileLoader { + private static final ILog LOGGER = LoggerUtil.getESLogger(); + + public Map loaderFileContext(String filePath, Field[] fields) { + LOGGER.info("method=loaderFileContext||DslLoaderUtil init start."); + List dslFileNames = Lists.newLinkedList(); + Map fileContextMap = new HashMap<>(); + + if(null == fields || 0 == fields.length){ + return fileContextMap; + } + + // 反射获取接口中定义的变量中的值 + for (int i = 0; i < fields.length; ++i) { + fields[i].setAccessible(true); + try { + dslFileNames.add(fields[i].get(null).toString()); + } catch (IllegalAccessException e) { + LOGGER.error("method=loaderFileContext||errMsg=fail to read {} error. ", fields[i].getName(), + e); + } + } + + // 加载dsl文件及内容 + for (String fileName : dslFileNames) { + fileContextMap.put(fileName, readEsFileInJarFile(filePath, fileName)); + } + + // 输出加载的查询语句 + LOGGER.info("method=loaderFileContext||msg=dsl files count {}", fileContextMap.size()); + for (Map.Entry entry : fileContextMap.entrySet()) { + LOGGER.info("method=loaderFileContext||msg=file name {}, dsl content {}", entry.getKey(), + entry.getValue()); + } + + LOGGER.info("method=loaderFileContext||DslLoaderUtil init finished."); + + return fileContextMap; + } + + /** + * 去除json中的空格 + * + * @param sourceDsl + * @return + */ + public String trimJsonBank(String sourceDsl) { + List dslList = Lists.newArrayList(); + + DefaultJSONParser parser = null; + Object obj = null; + String dsl = sourceDsl; + + // 解析多个json,直到pos为0 + for (;;) { + try { + // 这里需要Feature.OrderedField.getMask()保持有序 + parser = new DefaultJSONParser(dsl, ParserConfig.getGlobalInstance(), + JSON.DEFAULT_PARSER_FEATURE | Feature.OrderedField.getMask()); + obj = parser.parse(); + } catch (Exception t) { + LOGGER.error("method=trimJsonBank||errMsg=parse json {} error. ", dsl, t); + } + if (obj == null) { + break; + } + + if (obj instanceof JSONObject) { + dslList.add( JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue)); + int pos = parser.getLexer().pos(); + if (pos <= 0) { + break; + } + dsl = dsl.substring(pos); + parser.getLexer().close(); + } else { + parser.getLexer().close(); + break; + } + } + + // 格式化异常或者有多个查询语句,返回原来的查询语句 + if (dslList.isEmpty() || dslList.size() > 1) { + return sourceDsl; + } + + return dslList.get(0); + } + + /** + * 从jar包中读取es相关的语句文件 + * + * @param fileName + * @return + */ + private String readEsFileInJarFile(String filePath, String fileName) { + InputStream inputStream = this.getClass().getClassLoader() + .getResourceAsStream( filePath + fileName); + + if (inputStream != null) { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line = null; + List lines = Lists.newLinkedList(); + try { + while ((line = bufferedReader.readLine()) != null) { + lines.add(line); + } + return StringUtils.join(lines, ""); + + } catch (IOException e) { + LOGGER.error("method=readDslFileInJarFile||errMsg=read file {} error. ", fileName, + e); + + return ""; + } finally { + try { + inputStream.close(); + } catch (IOException e) { + LOGGER.error( + "method=readDslFileInJarFile||errMsg=fail to close file {} error. ", + fileName, e); + } + } + } else { + LOGGER.error("method=readDslFileInJarFile||errMsg=fail to read file {} content", + fileName); + return ""; + } + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java index 568ac34e..609b63d0 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java @@ -13,8 +13,10 @@ import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import com.xiaojukeji.know.streaming.km.common.utils.IndexNameUtils; import com.xiaojukeji.know.streaming.km.persistence.es.BaseESDAO; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateLoaderUtil; import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.util.CollectionUtils; @@ -29,8 +31,7 @@ public class BaseMetricESDAO extends BaseESDAO { /** * 操作的索引名称 */ - protected String indexName; - protected String indexTemplate; + protected String indexName; protected static final Long ONE_MIN = 60 * 1000L; protected static final Long FIVE_MIN = 5 * ONE_MIN; @@ -44,6 +45,9 @@ public class BaseMetricESDAO extends BaseESDAO { */ private static Map ariusStatsEsDaoMap = Maps.newConcurrentMap(); + @Autowired + private TemplateLoaderUtil templateLoaderUtil; + /** * es 地址 */ @@ -56,6 +60,7 @@ public class BaseMetricESDAO extends BaseESDAO { @Scheduled(cron = "0 3/5 * * * ?") public void checkCurrentDayIndexExist(){ try { + String indexTemplate = templateLoaderUtil.getContextByFileName(indexName); esOpClient.createIndexTemplateIfNotExist(indexName, indexTemplate); //检查最近7天索引存在不存 @@ -103,6 +108,15 @@ public class BaseMetricESDAO extends BaseESDAO { ariusStatsEsDaoMap.put(statsType, baseAriusStatsEsDao); } + /** + * 注册不同维度数据对应操作的es类 + * + * @param baseAriusStatsEsDao + */ + public void register(BaseMetricESDAO baseAriusStatsEsDao) { + BaseMetricESDAO.register(indexName, baseAriusStatsEsDao); + } + /** * 批量插入索引统计信息 @@ -416,7 +430,7 @@ public class BaseMetricESDAO extends BaseESDAO { Long endTime = System.currentTimeMillis(); Long startTime = endTime - 12 * ONE_HOUR; - String dsl = dslLoaderUtil.getFormatDslByFileName(DslsConstant.GET_LATEST_METRIC_TIME, startTime, endTime, appendQueryDsl); + String dsl = dslLoaderUtil.getFormatDslByFileName( DslConstant.GET_LATEST_METRIC_TIME, startTime, endTime, appendQueryDsl); String realIndexName = IndexNameUtils.genDailyIndexName(indexName, startTime, endTime); return esOpClient.performRequest( diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java index 7ee76a3e..d3d52f9e 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java @@ -9,7 +9,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPoint import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -18,16 +18,15 @@ import java.util.*; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.BROKER_INDEX; @Component public class BrokerMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = BROKER_INDEX; - super.indexTemplate = BROKER_TEMPLATE; + super.indexName = BROKER_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register( this); } protected FutureWaitUtil queryFuture = FutureWaitUtil.init("BrokerMetricESDAO", 4,8, 500); @@ -40,7 +39,7 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { Long startTime = endTime - FIVE_MIN; String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_BROKER_LATEST_METRICS, clusterId, brokerId, startTime, endTime); + DslConstant.GET_BROKER_LATEST_METRICS, clusterId, brokerId, startTime, endTime); BrokerMetricPO brokerMetricPO = esOpClient.performRequestAndTakeFirst( brokerId.toString(), @@ -68,7 +67,7 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { String aggDsl = buildAggsDSL(metrics, aggType); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_BROKER_AGG_SINGLE_METRICS, clusterPhyId, brokerId, startTime, endTime, aggDsl); + DslConstant.GET_BROKER_AGG_SINGLE_METRICS, clusterPhyId, brokerId, startTime, endTime, aggDsl); return esOpClient.performRequestWithRouting( String.valueOf(brokerId), @@ -132,7 +131,7 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { for(Long brokerId : brokerIds){ try { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_BROKER_AGG_LIST_METRICS, + DslConstant.GET_BROKER_AGG_LIST_METRICS, clusterPhyId, brokerId, startTime, @@ -154,8 +153,8 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { ); synchronized (table) { - for(String metric : metricMap.keySet()){ - table.put(metric, brokerId, metricMap.get(metric)); + for(Map.Entry> entry: metricMap.entrySet()){ + table.put(entry.getKey(), brokerId, entry.getValue()); } } }); @@ -187,7 +186,7 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { //4、查询es String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_BROKER_AGG_TOP_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); + DslConstant.GET_BROKER_AGG_TOP_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); return esOpClient.performRequest(realIndex, dsl, s -> handleTopBrokerESQueryResponse(s, metrics, topN), 3); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java index d53f83bf..5d53aa16 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java @@ -12,7 +12,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ClusterMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -23,17 +23,16 @@ import java.util.List; import java.util.Map; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CLUSTER_INDEX; @Component public class ClusterMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = CLUSTER_INDEX; - super.indexTemplate = CLUSTER_TEMPLATE; + super.indexName = CLUSTER_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register(this); } protected FutureWaitUtil queryFuture = FutureWaitUtil.init("ClusterMetricESDAO", 4,8, 500); @@ -46,7 +45,7 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { Long startTime = endTime - FIVE_MIN; String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_CLUSTER_LATEST_METRICS, clusterId, startTime, endTime); + DslConstant.GET_CLUSTER_LATEST_METRICS, clusterId, startTime, endTime); ClusterMetricPO clusterMetricPO = esOpClient.performRequestAndTakeFirst( clusterId.toString(), realIndex(startTime, endTime), dsl, ClusterMetricPO.class); @@ -67,7 +66,7 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { String aggDsl = buildAggsDSL(metrics, aggType); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_CLUSTER_AGG_SINGLE_METRICS, clusterPhyId, startTime, endTime, aggDsl); + DslConstant.GET_CLUSTER_AGG_SINGLE_METRICS, clusterPhyId, startTime, endTime, aggDsl); return esOpClient.performRequestWithRouting(String.valueOf(clusterPhyId), realIndex, dsl, s -> handleSingleESQueryResponse(s, metrics, aggType), 3); @@ -103,7 +102,7 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { } String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.LIST_CLUSTER_WITH_LATEST_METRICS, latestMetricTime, appendQueryDsl.toString(), sortDsl); + DslConstant.LIST_CLUSTER_WITH_LATEST_METRICS, latestMetricTime, appendQueryDsl.toString(), sortDsl); return esOpClient.performRequest(realIndex, dsl, ClusterMetricPO.class); } @@ -133,7 +132,7 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { 5000, () -> { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_CLUSTER_AGG_LIST_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); + DslConstant.GET_CLUSTER_AGG_LIST_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); Map> metricMap = esOpClient.performRequestWithRouting( String.valueOf(clusterPhyId), realIndex, dsl, diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java index 782adc2f..26ebdea1 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java @@ -12,7 +12,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPoint import com.xiaojukeji.know.streaming.km.common.enums.AggTypeEnum; import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -23,17 +23,16 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.constant.Constant.ZERO; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.GROUP_INDEX; @Component public class GroupMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = GROUP_INDEX; - super.indexTemplate = GROUP_TEMPLATE; + super.indexName = GROUP_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register(this); } protected FutureWaitUtil queryFuture = FutureWaitUtil.init("GroupMetricESDAO", 4,8, 500); @@ -59,7 +58,7 @@ public class GroupMetricESDAO extends BaseMetricESDAO { String topic = groupTopic.getTopicName(); try { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.LIST_GROUP_LATEST_METRICS_BY_GROUP_TOPIC, clusterPhyId, group, topic, + DslConstant.LIST_GROUP_LATEST_METRICS_BY_GROUP_TOPIC, clusterPhyId, group, topic, startTime, latestTime, aggDsl); String routing = routing(clusterPhyId, group); @@ -86,7 +85,7 @@ public class GroupMetricESDAO extends BaseMetricESDAO { String realIndex = realIndex(startTime, latestTime); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.LIST_GROUP_LATEST_METRICS_OF_PARTITION, clusterPhyId, group, topic, latestTime); + DslConstant.LIST_GROUP_LATEST_METRICS_OF_PARTITION, clusterPhyId, group, topic, latestTime); List groupMetricPOS = esOpClient.performRequest(realIndex, dsl, GroupMetricPO.class); return filterMetrics(groupMetricPOS, metrics); @@ -101,8 +100,8 @@ public class GroupMetricESDAO extends BaseMetricESDAO { String matchDsl = buildTermsDsl(Arrays.asList(match)); String dsl = match.isEqual() - ? dslLoaderUtil.getFormatDslByFileName(DslsConstant.COUNT_GROUP_METRIC_VALUE, clusterPhyId, groupName, startTime, endTime, matchDsl) - : dslLoaderUtil.getFormatDslByFileName(DslsConstant.COUNT_GROUP_NOT_METRIC_VALUE, clusterPhyId, groupName, startTime, endTime, matchDsl); + ? dslLoaderUtil.getFormatDslByFileName( DslConstant.COUNT_GROUP_METRIC_VALUE, clusterPhyId, groupName, startTime, endTime, matchDsl) + : dslLoaderUtil.getFormatDslByFileName( DslConstant.COUNT_GROUP_NOT_METRIC_VALUE, clusterPhyId, groupName, startTime, endTime, matchDsl); return esOpClient.performRequestWithRouting(clusterPhyId.toString() + "@" + groupName, realIndex, dsl, s -> handleESQueryResponseCount(s), 3); @@ -127,7 +126,7 @@ public class GroupMetricESDAO extends BaseMetricESDAO { Integer partition = tp.getPartition(); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.LIST_GROUP_METRICS, clusterId, groupName, topic, partition, startTime, endTime, interval, aggDsl); + DslConstant.LIST_GROUP_METRICS, clusterId, groupName, topic, partition, startTime, endTime, interval, aggDsl); Map> metricMap = esOpClient.performRequest(realIndex, dsl, s -> handleGroupMetrics(s, aggType, metrics), 3); @@ -148,7 +147,7 @@ public class GroupMetricESDAO extends BaseMetricESDAO { String realIndex = realIndex(startTime, endTime); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_GROUP_TOPIC_PARTITION, clusterPhyId, groupName, startTime, endTime); + DslConstant.GET_GROUP_TOPIC_PARTITION, clusterPhyId, groupName, startTime, endTime); List groupMetricPOS = esOpClient.performRequestWithRouting(routing(clusterPhyId, groupName), realIndex, dsl, GroupMetricPO.class); return groupMetricPOS.stream().map(g -> new TopicPartitionKS(g.getTopic(), g.getPartitionId().intValue())).collect( Collectors.toSet()); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/PartitionMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/PartitionMetricESDAO.java index 4f86852b..759f5eb5 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/PartitionMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/PartitionMetricESDAO.java @@ -1,14 +1,14 @@ package com.xiaojukeji.know.streaming.km.persistence.es.dao; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.PartitionMetricPO; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.List; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.PARTITION_INDEX; /** * @author didi @@ -18,10 +18,9 @@ public class PartitionMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = PARTITION_INDEX; - super.indexTemplate = PARTITION_TEMPLATE; + super.indexName = PARTITION_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register(this); } public PartitionMetricPO getPartitionLatestMetrics(Long clusterPhyId, String topic, @@ -31,7 +30,7 @@ public class PartitionMetricESDAO extends BaseMetricESDAO { Long startTime = endTime - FIVE_MIN; String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_PARTITION_LATEST_METRICS, clusterPhyId, topic, brokerId, partitionId, startTime, endTime); + DslConstant.GET_PARTITION_LATEST_METRICS, clusterPhyId, topic, brokerId, partitionId, startTime, endTime); PartitionMetricPO partitionMetricPO = esOpClient.performRequestAndTakeFirst( partitionId.toString(), realIndex(startTime, endTime), dsl, PartitionMetricPO.class); @@ -45,7 +44,7 @@ public class PartitionMetricESDAO extends BaseMetricESDAO { Long startTime = endTime - FIVE_MIN; String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.LIST_PARTITION_LATEST_METRICS_BY_TOPIC, clusterPhyId, topic, startTime, endTime); + DslConstant.LIST_PARTITION_LATEST_METRICS_BY_TOPIC, clusterPhyId, topic, startTime, endTime); List partitionMetricPOS = esOpClient.performRequest( realIndex(startTime, endTime), dsl, PartitionMetricPO.class); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java index fbd874f2..6f1c7561 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java @@ -4,7 +4,7 @@ import com.didiglobal.logi.elasticsearch.client.response.query.query.ESQueryResp import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ReplicationMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -14,7 +14,7 @@ import java.util.List; import java.util.Map; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.VALUE; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.REPLICATION_INDEX; /** * @author didi @@ -24,10 +24,9 @@ public class ReplicationMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = REPLICATION_INDEX; - super.indexTemplate = REPLICATION_TEMPLATE; + super.indexName = REPLICATION_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register(this); } /** @@ -39,7 +38,7 @@ public class ReplicationMetricESDAO extends BaseMetricESDAO { Long startTime = endTime - FIVE_MIN; String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_REPLICATION_LATEST_METRICS, clusterPhyId, brokerId, topic, partitionId, startTime, endTime); + DslConstant.GET_REPLICATION_LATEST_METRICS, clusterPhyId, brokerId, topic, partitionId, startTime, endTime); ReplicationMetricPO replicationMetricPO = esOpClient.performRequestAndTakeFirst( realIndex(startTime, endTime), dsl, ReplicationMetricPO.class); @@ -61,7 +60,7 @@ public class ReplicationMetricESDAO extends BaseMetricESDAO { String aggDsl = buildAggsDSL(metrics, aggType); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_REPLICATION_AGG_SINGLE_METRICS, clusterPhyId, brokerId,topic, partitionId, startTime, endTime, aggDsl); + DslConstant.GET_REPLICATION_AGG_SINGLE_METRICS, clusterPhyId, brokerId,topic, partitionId, startTime, endTime, aggDsl); return esOpClient.performRequestWithRouting(String.valueOf(brokerId), realIndex, dsl, s -> handleSingleESQueryResponse(s, metrics, aggType), 3); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java index e997a778..49749160 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java @@ -13,7 +13,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPoint import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -22,17 +22,16 @@ import java.util.*; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.TOPIC_INDEX; @Component public class TopicMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = TOPIC_INDEX; - super.indexTemplate = TOPIC_TEMPLATE; + super.indexName = TOPIC_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register(this); } protected FutureWaitUtil queryFuture = FutureWaitUtil.init("TopicMetricESDAO", 4,8, 500); @@ -47,7 +46,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { String sortDsl = buildSortDsl(sort, SearchSort.DEFAULT); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_MAX_OR_MIN_SINGLE_METRIC, clusterPhyId, startTime, endTime, topic, sortDsl); + DslConstant.GET_TOPIC_MAX_OR_MIN_SINGLE_METRIC, clusterPhyId, startTime, endTime, topic, sortDsl); TopicMetricPO topicMetricPO = esOpClient.performRequestAndTakeFirst(topic, realIndex, dsl, TopicMetricPO.class); ret.add(topicMetricPO); } @@ -74,7 +73,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { } String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_AGG_SINGLE_METRICS, clusterPhyId, startTime, endTime, appendQueryDsl.toString(), aggDsl); + DslConstant.GET_TOPIC_AGG_SINGLE_METRICS, clusterPhyId, startTime, endTime, appendQueryDsl.toString(), aggDsl); return esOpClient.performRequest(realIndex, dsl, s -> handleSingleESQueryResponse(s, metrics, aggType), 3); @@ -112,7 +111,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { String realIndex = realIndex(startTime, latestMetricTime); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.LIST_TOPIC_WITH_LATEST_METRICS, clusterId, latestMetricTime, appendQueryDsl.toString(), sortDsl); + DslConstant.LIST_TOPIC_WITH_LATEST_METRICS, clusterId, latestMetricTime, appendQueryDsl.toString(), sortDsl); return esOpClient.performRequest(realIndex, dsl, TopicMetricPO.class); } @@ -126,8 +125,8 @@ public class TopicMetricESDAO extends BaseMetricESDAO { String termDsl = buildTermsDsl(Arrays.asList(term)); String dsl = term.isEqual() - ? dslLoaderUtil.getFormatDslByFileName(DslsConstant.COUNT_TOPIC_METRIC_VALUE, clusterPhyId, topic, startTime, endTime, termDsl) - : dslLoaderUtil.getFormatDslByFileName(DslsConstant.COUNT_TOPIC_NOT_METRIC_VALUE, clusterPhyId, topic, startTime, endTime, termDsl); + ? dslLoaderUtil.getFormatDslByFileName( DslConstant.COUNT_TOPIC_METRIC_VALUE, clusterPhyId, topic, startTime, endTime, termDsl) + : dslLoaderUtil.getFormatDslByFileName( DslConstant.COUNT_TOPIC_NOT_METRIC_VALUE, clusterPhyId, topic, startTime, endTime, termDsl); return esOpClient.performRequestWithRouting(topic, realIndex, dsl, s -> handleESQueryResponseCount(s), 3); @@ -141,7 +140,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { Long startTime = endTime - FIVE_MIN; String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_BROKER_LATEST_METRICS, clusterPhyId, topic, brokerId, startTime, endTime); + DslConstant.GET_TOPIC_BROKER_LATEST_METRICS, clusterPhyId, topic, brokerId, startTime, endTime); TopicMetricPO topicMetricPO = esOpClient.performRequestAndTakeFirst(topic, realIndex(startTime, endTime), dsl, TopicMetricPO.class); @@ -165,7 +164,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { } String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_LATEST_METRICS, clusterPhyId, startTime, endTime, appendQueryDsl.toString()); + DslConstant.GET_TOPIC_LATEST_METRICS, clusterPhyId, startTime, endTime, appendQueryDsl.toString()); //topicMetricPOS 已经按照 timeStamp 倒序排好序了 List topicMetricPOS = esOpClient.performRequest(realIndex(startTime, endTime), dsl, TopicMetricPO.class); @@ -197,7 +196,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { } String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_LATEST_METRICS, clusterPhyId, startTime, endTime, appendQueryDsl.toString()); + DslConstant.GET_TOPIC_LATEST_METRICS, clusterPhyId, startTime, endTime, appendQueryDsl.toString()); TopicMetricPO topicMetricPO = esOpClient.performRequestAndTakeFirst(topic, realIndex(startTime, endTime), dsl, TopicMetricPO.class); @@ -262,7 +261,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { 3000, () -> { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_AGG_LIST_METRICS, clusterPhyId, topic, startTime, endTime, interval, aggDsl); + DslConstant.GET_TOPIC_AGG_LIST_METRICS, clusterPhyId, topic, startTime, endTime, interval, aggDsl); Map> metricMap = esOpClient.performRequestWithRouting(topic, realIndex, dsl, s -> handleListESQueryResponse(s, metrics, aggType), 3); @@ -299,7 +298,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { //4、查询es String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_TOPIC_AGG_TOP_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); + DslConstant.GET_TOPIC_AGG_TOP_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); return esOpClient.performRequest(realIndex, dsl, s -> handleTopTopicESQueryResponse(s, metrics, topN), 3); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java index dc19d2b8..b94a2461 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ZookeeperMetricESDAO.java @@ -5,7 +5,7 @@ import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.constant.ESConstant; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslsConstant; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -15,18 +15,16 @@ import java.util.List; import java.util.Map; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.ZOOKEEPER_INDEX; -import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.ZOOKEEPER_TEMPLATE; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.ZOOKEEPER_INDEX; @Component public class ZookeeperMetricESDAO extends BaseMetricESDAO { @PostConstruct public void init() { - super.indexName = ZOOKEEPER_INDEX; - super.indexTemplate = ZOOKEEPER_TEMPLATE; + super.indexName = ZOOKEEPER_INDEX; checkCurrentDayIndexExist(); - BaseMetricESDAO.register(indexName, this); + register(this); } /** @@ -49,7 +47,7 @@ public class ZookeeperMetricESDAO extends BaseMetricESDAO { //4、构造dsl查询条件,开始查询 try { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslsConstant.GET_ZOOKEEPER_AGG_LIST_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); + DslConstant.GET_ZOOKEEPER_AGG_LIST_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); return esOpClient.performRequestWithRouting( String.valueOf(clusterPhyId), diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java new file mode 100644 index 00000000..07394f51 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java @@ -0,0 +1,275 @@ +package com.xiaojukeji.know.streaming.km.persistence.es.dao.connect; + +import com.didiglobal.logi.elasticsearch.client.response.query.query.ESQueryResponse; +import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; +import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.BaseMetricESDAO; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.annotation.PostConstruct; +import java.util.*; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_CLUSTER_INDEX; + +@Component +public class ConnectClusterMetricESDAO extends BaseMetricESDAO { + @PostConstruct + public void init() { + super.indexName = CONNECT_CLUSTER_INDEX; + checkCurrentDayIndexExist(); + register( this); + } + + protected FutureWaitUtil queryFuture = FutureWaitUtil.init("ConnectClusterMetricESDAO", 4,8, 500); + + /** + * 获取集群 clusterPhyId 中每个 metric 的 topN 的 connectCluster 在指定时间[startTime、endTime]区间内所有的指标 + * topN 按照[startTime, endTime] 时间段内最后一个值来排序 + */ + public Table> listMetricsByTop(Long clusterPhyId, + List connectClusterIdList, + List metricNameList, + String aggType, + int topN, + Long startTime, + Long endTime){ + // 1、获取TopN + Map> topNConnectClusterIds = getTopNConnectClusterIds(clusterPhyId, metricNameList, aggType, topN, startTime, endTime); + + Table> table = HashBasedTable.create(); + + // 2、查询指标 + for(String metric : metricNameList) { + table.putAll( + this.listMetricsByConnectClusterIdList( + clusterPhyId, + Arrays.asList(metric), + aggType, + topNConnectClusterIds.getOrDefault(metric, connectClusterIdList), + startTime, + endTime + ) + ); + } + + return table; + } + + /** + * 获取集群 clusterPhyId 中每个 metric 的指定 connectClusters 在指定时间[startTime、endTime]区间内所有的指标 + */ + public Table> listMetricsByConnectClusterIdList(Long clusterPhyId, + List metricNameList, + String aggType, + List connectClusterIdList, + Long startTime, + Long endTime){ + //1、获取需要查下的索引 + String realIndex = realIndex(startTime, endTime); + + //2、根据查询的时间区间大小来确定指标点的聚合区间大小 + String interval = MetricsUtils.getInterval(endTime - startTime); + + //3、构造agg查询条件 + String aggDsl = buildAggsDSL(metricNameList, aggType); + + final Table> table = HashBasedTable.create(); + + //4、构造dsl查询条件 + for(Long connectClusterId : connectClusterIdList){ + try { + String dsl = dslLoaderUtil.getFormatDslByFileName( + DslConstant.GET_CONNECT_CLUSTER_AGG_LIST_METRICS, + clusterPhyId, + connectClusterId, + startTime, + endTime, + interval, + aggDsl + ); + + queryFuture.runnableTask( + String.format("class=ConnectClusterMetricESDAO||method=listMetricsByConnectClusterIdList||ClusterPhyId=%d", clusterPhyId), + 5000, + () -> { + Map> metricMap = esOpClient.performRequestWithRouting( + String.valueOf(connectClusterId), + realIndex, + dsl, + s -> handleListESQueryResponse(s, metricNameList, aggType), + 3 + ); + + synchronized (table) { + for(String metric : metricMap.keySet()){ + table.put(metric, connectClusterId, metricMap.get(metric)); + } + } + }); + } catch (Exception e) { + LOGGER.error( + "class=ConnectClusterMetricESDAO||method=listMetricsByConnectClusterIdList||clusterPhyId={}||connectClusterId{}||errMsg=exception!", + clusterPhyId, connectClusterId, e + ); + } + } + + queryFuture.waitExecute(); + + return table; + } + + /** + * 获取集群 clusterPhyId 中每个 metric 的 topN 的 broker + */ + //public for test + public Map> getTopNConnectClusterIds(Long clusterPhyId, + List metricNameList, + String aggType, + int topN, + Long startTime, + Long endTime){ + //1、获取需要查下的索引 + String realIndex = realIndex(startTime, endTime); + + //2、根据查询的时间区间大小来确定指标点的聚合区间大小 + String interval = MetricsUtils.getInterval(endTime - startTime); + + //3、构造agg查询条件 + String aggDsl = buildAggsDSL(metricNameList, aggType); + + //4、查询es + String dsl = dslLoaderUtil.getFormatDslByFileName( + DslConstant.GET_CONNECT_CLUSTER_AGG_TOP_METRICS, + clusterPhyId, + startTime, + endTime, + interval, + aggDsl + ); + + return esOpClient.performRequest( + realIndex, + dsl, + s -> handleTopConnectClusterESQueryResponse(s, metricNameList, topN), + 3 + ); + } + + /**************************************************** private method ****************************************************/ + + private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ + Map> metricMap = new HashMap<>(); + + if(null == response || null == response.getAggs()){ + return metricMap; + } + + Map esAggrMap = response.getAggs().getEsAggrMap(); + if (null == esAggrMap || null == esAggrMap.get(HIST)) { + return metricMap; + } + + if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ + return metricMap; + } + + for(String metric : metrics){ + List metricPoints = new ArrayList<>(); + + esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + try { + if (null != esBucket.getUnusedMap().get(KEY)) { + Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); + if(null == value){return;} + + MetricPointVO metricPoint = new MetricPointVO(); + metricPoint.setAggType(aggType); + metricPoint.setTimeStamp(timestamp); + metricPoint.setValue(value.toString()); + metricPoint.setName(metric); + + metricPoints.add(metricPoint); + }else { + LOGGER.info(""); + } + }catch (Exception e){ + LOGGER.error("metric={}||errMsg=exception!", metric, e); + } + } ); + + metricMap.put(metric, optimizeMetricPoints(metricPoints)); + } + + return metricMap; + } + + private Map> handleTopConnectClusterESQueryResponse(ESQueryResponse response, List metrics, int topN){ + Map> ret = new HashMap<>(); + + if(null == response || null == response.getAggs()){ + return ret; + } + + Map esAggrMap = response.getAggs().getEsAggrMap(); + if (null == esAggrMap || null == esAggrMap.get(HIST)) { + return ret; + } + + if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ + return ret; + } + + Map>> metricBrokerValueMap = new HashMap<>(); + + //1、先获取每个指标对应的所有brokerIds以及指标的值 + for(String metric : metrics) { + esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + try { + if (null != esBucket.getUnusedMap().get(KEY)) { + Long connectorClusterId = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(HIST).getBucketList() + .get(0).getAggrMap().get(metric).getUnusedMap().get(VALUE); + + if(null == value){return;} + + List> connectorClusterValue = (null == metricBrokerValueMap.get(metric)) ? + new ArrayList<>() : metricBrokerValueMap.get(metric); + + connectorClusterValue.add(new Tuple<>(connectorClusterId, Double.valueOf(value.toString()))); + metricBrokerValueMap.put(metric, connectorClusterValue); + } + }catch (Exception e){ + LOGGER.error("metric={}||errMsg=exception!", metric, e); + } + } ); + } + + //2、对每个指标的broker按照指标值排序,并截取前topN个brokerIds + for(String metric : metricBrokerValueMap.keySet()){ + List> connectorClusterValue = metricBrokerValueMap.get(metric); + + connectorClusterValue.sort((o1, o2) -> { + if(null == o1 || null == o2){return 0;} + return o2.getV2().compareTo(o1.getV2()); + } ); + + List> temp = (connectorClusterValue.size() > topN) ? connectorClusterValue.subList(0, topN) : connectorClusterValue; + List connectorClusterIds = temp.stream().map(t -> t.getV1()).collect(Collectors.toList()); + + ret.put(metric, connectorClusterIds); + } + + return ret; + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java new file mode 100644 index 00000000..060f57be --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java @@ -0,0 +1,365 @@ +package com.xiaojukeji.know.streaming.km.persistence.es.dao.connect; + +import com.alibaba.druid.util.StringUtils; +import com.didiglobal.logi.elasticsearch.client.response.query.query.ESQueryResponse; +import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchTerm; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect.ConnectorMetricPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; +import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; +import com.xiaojukeji.know.streaming.km.common.utils.Triple; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.BaseMetricESDAO; +import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_CONNECTOR_INDEX; + +@Component +public class ConnectorMetricESDAO extends BaseMetricESDAO { + + @PostConstruct + public void init() { + super.indexName = CONNECT_CONNECTOR_INDEX; + checkCurrentDayIndexExist(); + register( this); + } + + protected FutureWaitUtil queryFuture = FutureWaitUtil.init("ConnectorMetricESDAO", 4,8, 500); + + + /** + * 获取每个 metric 的 topN 个 connector 的指标,如果获取不到 topN 的topics, 则默认返回 defaultTopics 的指标 + */ + public Table, List> listMetricsByTopN(Long clusterPhyId, + List> defaultConnectorList, + List metricNameList, + String aggType, + int topN, + Long startTime, + Long endTime){ + //1、获取topN要查询的topic,每一个指标的topN的topic可能不一样 + Map>> metricsMap = this.getTopNConnectors(clusterPhyId, metricNameList, aggType, topN, startTime, endTime); + + Table, List> table = HashBasedTable.create(); + + for(String metricName : metricNameList){ + table.putAll(this.listMetricsByConnectors( + clusterPhyId, + Arrays.asList(metricName), + aggType, + metricsMap.getOrDefault(metricName, defaultConnectorList), + startTime, + endTime) + ); + } + + return table; + } + + public List getConnectorLatestMetric(Long clusterPhyId, List> connectClusterIdAndConnectorNameList, List metricsNames){ + List connectorMetricPOS = new CopyOnWriteArrayList<>(); + + for(Tuple connectClusterIdAndConnectorName : connectClusterIdAndConnectorNameList){ + queryFuture.runnableTask( + "getConnectorLatestMetric", + 30000, + () -> { + ConnectorMetricPO connectorMetricPO = this.getConnectorLatestMetric(clusterPhyId, connectClusterIdAndConnectorName.getV1(), connectClusterIdAndConnectorName.getV2(), metricsNames); + connectorMetricPOS.add(connectorMetricPO); + }); + } + + queryFuture.waitExecute(); + return connectorMetricPOS; + } + + public ConnectorMetricPO getConnectorLatestMetric(Long clusterPhyId, Long connectClusterId, String connectorName, List metricsNames){ + Long endTime = getLatestMetricTime(); + Long startTime = endTime - FIVE_MIN; + + SearchTerm searchClusterIdTerm = new SearchTerm("connectClusterId", connectClusterId.toString()); + searchClusterIdTerm.setField(true); + + SearchTerm searchClusterNameTerm = new SearchTerm("connectorName", connectorName); + searchClusterNameTerm.setField(true); + + String termDsl = buildTermsDsl(Arrays.asList(searchClusterIdTerm, searchClusterNameTerm)); + StringBuilder appendQueryDsl = new StringBuilder(); + if(!StringUtils.isEmpty(termDsl)){ + appendQueryDsl.append(",").append(termDsl); + } + + String dsl = dslLoaderUtil.getFormatDslByFileName( + DslConstant.GET_CONNECTOR_LATEST_METRICS, connectClusterId, connectorName, startTime, endTime, appendQueryDsl.toString()); + + ConnectorMetricPO connectorMetricPO = esOpClient.performRequestAndTakeFirst( + connectClusterId.toString(), realIndex(startTime, endTime), dsl, ConnectorMetricPO.class); + + return (null == connectorMetricPO) ? new ConnectorMetricPO(clusterPhyId, connectClusterId, connectorName) + : filterMetrics(connectorMetricPO, metricsNames); + } + + /** + * 获取每个 metric 指定个 topic 的指标 + */ + public Table, List> listMetricsByConnectors(Long clusterPhyId, + List metrics, + String aggType, + List> connectorList, + Long startTime, + Long endTime) { + //1、获取需要查下的索引 + String realIndex = realIndex(startTime, endTime); + + //2、根据查询的时间区间大小来确定指标点的聚合区间大小 + String interval = MetricsUtils.getInterval(endTime - startTime); + + //3、构造agg查询条件 + String aggDsl = buildAggsDSL(metrics, aggType); + + final Table, List> table = HashBasedTable.create(); + + //4、构造dsl查询条件 + for(Tuple connector : connectorList) { + try { + queryFuture.runnableTask( + String.format( + "method=listConnectorMetricsByConnectors||ClusterPhyId=%d||connectorName=%s", + clusterPhyId, connector.getV2() ), + 3000, + () -> { + String dsl = dslLoaderUtil.getFormatDslByFileName( + DslConstant.GET_CONNECTOR_AGG_LIST_METRICS, + clusterPhyId, + connector.getV1(), + connector.getV2(), + startTime, + endTime, + interval, + aggDsl + ); + + Map> metricMap = esOpClient.performRequestWithRouting( + connector.getV1().toString(), + realIndex, + dsl, + s -> handleListESQueryResponse(s, metrics, aggType), + 3 + ); + + synchronized (table){ + for(String metric : metricMap.keySet()){ + table.put(metric, connector, metricMap.get(metric)); + } + } + }); + } catch (Exception e) { + LOGGER.error( + "method=listConnectorMetricsByConnectors||clusterPhyId={}||connectorName{}||errMsg=exception!", + clusterPhyId, connector.getV2(), e + ); + } + } + + queryFuture.waitExecute(); + + return table; + } + + //public for test + public Map>> getTopNConnectors(Long clusterPhyId, + List metricNameList, + String aggType, + int topN, + Long startTime, + Long endTime){ + //1、获取需要查下的索引 + String realIndex = realIndex(startTime, endTime); + + //2、根据查询的时间区间大小来确定指标点的聚合区间大小 + String interval = MetricsUtils.getInterval(endTime - startTime); + + //3、构造agg查询条件 + String aggDsl = buildAggsDSL(metricNameList, aggType); + + //4、查询es + String dsl = dslLoaderUtil.getFormatDslByFileName( + DslConstant.GET_CONNECTOR_AGG_TOP_METRICS, + clusterPhyId, + startTime, + endTime, + interval, + aggDsl + ); + + return esOpClient.performRequest( + realIndex, + dsl, + s -> handleTopConnectorESQueryResponse(s, metricNameList, topN), + 3 + ); + } + + /**************************************************** private method ****************************************************/ + + private Table handleSingleESQueryResponse(ESQueryResponse response, List metrics, String aggType){ + Table table = HashBasedTable.create(); + + Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap){return table;} + + for(String metric : metrics){ + esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + try { + if (null != esBucket.getUnusedMap().get(KEY)) { + String topic = esBucket.getUnusedMap().get(KEY).toString(); + String value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE).toString(); + + MetricPointVO metricPoint = new MetricPointVO(); + metricPoint.setAggType(aggType); + metricPoint.setValue(value); + metricPoint.setName(metric); + + table.put(topic, metric, metricPoint); + }else { + LOGGER.debug("method=handleListESQueryResponse||metric={}||errMsg=get topic is null!", metric); + } + }catch (Exception e){ + LOGGER.error("method=handleListESQueryResponse||metric={}||errMsg=exception!", metric, e); + } + }); + } + + return table; + } + + private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ + Map> metricMap = new HashMap<>(); + + Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap){return metricMap;} + + for(String metric : metrics){ + List metricPoints = new ArrayList<>(); + + esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + try { + if (null != esBucket.getUnusedMap().get(KEY)) { + Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); + if(value == null){return;} + + MetricPointVO metricPoint = new MetricPointVO(); + metricPoint.setAggType(aggType); + metricPoint.setTimeStamp(timestamp); + metricPoint.setValue(value.toString()); + metricPoint.setName(metric); + + metricPoints.add(metricPoint); + }else { + LOGGER.info(""); + } + }catch (Exception e){ + LOGGER.error("method=handleListESQueryResponse||metric={}||errMsg=exception!", metric, e); + } + } ); + + metricMap.put(metric, optimizeMetricPoints(metricPoints)); + } + + return metricMap; + } + + private Map>> handleTopConnectorESQueryResponse(ESQueryResponse response, List metricNameList, int topN){ + Map>> ret = new HashMap<>(); + + Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap) { + return ret; + } + + Map>> metricValueMap = new HashMap<>(); + + // 1、先获取每个指标对应的所有 connector 以及指标的值 + for(String metricName : metricNameList) { + esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + try { + if (null != esBucket.getUnusedMap().get(KEY)) { + String connectorNameAndClusterId = esBucket.getUnusedMap().get(KEY).toString(); + Object value = esBucket.getAggrMap().get(HIST).getBucketList().get(0).getAggrMap().get(metricName).getUnusedMap().get(VALUE); + if (value == null) { + return; + } + Double metricValue = Double.valueOf(value.toString()); + + Tuple tuple = splitConnectorNameAndClusterId(connectorNameAndClusterId); + if (null == tuple) { + return; + } + + metricValueMap.putIfAbsent(metricName, new ArrayList<>()); + metricValueMap.get(metricName).add(new Triple<>(tuple.getV2(), tuple.getV1(), metricValue)); + } + } catch (Exception e) { + LOGGER.error("method=handleTopConnectorESQueryResponse||metricName={}||errMsg=exception!", metricName, e); + } + } ); + } + + //2、对每个指标的connector按照指标值排序,并截取前topN个connectors + for(Map.Entry>> entry : metricValueMap.entrySet()){ + entry.getValue().sort((o1, o2) -> { + if(null == o1 || null == o2) { + return 0; + } + + return o2.v3().compareTo(o1.v3()); + } ); + + List> temp = (entry.getValue().size() > topN) ? entry.getValue().subList(0, topN) : entry.getValue(); + + List> connectorList = new ArrayList<>(); + for (Triple triple: temp) { + connectorList.add(new Tuple<>(triple.v1(), triple.v2())); + } + + ret.put(entry.getKey(), connectorList); + } + + return ret; + } + + private Map>> topicMetricMap2MetricTopicMap( + Map>> topicMetricMap){ + Map>> ret = new HashMap<>(); + + for(String topic : topicMetricMap.keySet()){ + Map> metricMap = topicMetricMap.get(topic); + + for(String metric : metricMap.keySet()){ + Map> brokerMap = (null == ret.get(metric)) ? new HashMap<>() : ret.get(metric); + + brokerMap.put(topic, metricMap.get(metric)); + ret.put(metric, brokerMap); + } + } + + return ret; + } + + private Tuple splitConnectorNameAndClusterId(String connectorNameAndClusterId){ + String[] ss = connectorNameAndClusterId.split("#"); + if(null == ss || ss.length != 2){return null;} + + return new Tuple<>(ss[0], Long.valueOf(ss[1])); + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslsConstant.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java similarity index 83% rename from km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslsConstant.java rename to km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java index f15fe7a8..9bd8062a 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslsConstant.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java @@ -13,9 +13,9 @@ package com.xiaojukeji.know.streaming.km.persistence.es.dsls; * 在dslFiles目录下新建以类名为名称的文件夹,以方法名为名称的文件名 * */ -public class DslsConstant { +public class DslConstant { - private DslsConstant() {} + private DslConstant() {} /**************************************************** Base ****************************************************/ public static final String GET_LATEST_METRIC_TIME = "BaseMetricESDAO/getLatestMetricTime"; @@ -82,4 +82,18 @@ public class DslsConstant { /**************************************************** Zookeeper ****************************************************/ public static final String GET_ZOOKEEPER_AGG_LIST_METRICS = "ZookeeperMetricESDAO/getAggListZookeeperMetrics"; + + /**************************************************** Connect-Cluster ****************************************************/ + public static final String GET_CONNECT_CLUSTER_AGG_LIST_METRICS = "ConnectClusterMetricESDAO/getAggListConnectClusterMetrics"; + + public static final String GET_CONNECT_CLUSTER_AGG_TOP_METRICS = "ConnectClusterMetricESDAO/getAggTopMetricsConnectClusters"; + + /**************************************************** Connect-Connector ****************************************************/ + public static final String GET_CONNECTOR_LATEST_METRICS = "ConnectorMetricESDAO/getConnectorLatestMetric"; + + public static final String GET_CONNECTOR_AGG_LIST_METRICS = "ConnectorMetricESDAO/getConnectorAggListMetric"; + + public static final String GET_CONNECTOR_AGG_TOP_METRICS = "ConnectorMetricESDAO/getConnectorAggTopMetric"; + + } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java index 6bdea995..510b2def 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslLoaderUtil.java @@ -1,26 +1,14 @@ package com.xiaojukeji.know.streaming.km.persistence.es.dsls; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.DefaultJSONParser; -import com.alibaba.fastjson.parser.Feature; -import com.alibaba.fastjson.parser.ParserConfig; -import com.alibaba.fastjson.serializer.SerializerFeature; import com.didiglobal.logi.log.ILog; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; +import com.xiaojukeji.know.streaming.km.persistence.es.ESFileLoader; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.util.List; import java.util.Map; /** @@ -33,50 +21,25 @@ import java.util.Map; * */ @Component -public class DslLoaderUtil { +public class DslLoaderUtil extends ESFileLoader { private static final ILog LOGGER = LoggerUtil.getESLogger(); + + private static final String FILE_PATH = "es/dsl/"; + /** * 查询语句容器 + * key : fileRelativePath + * value : dslContent */ - private Map dslsMap = Maps.newHashMap(); + private Map dslsMap = Maps.newHashMap(); @PostConstruct public void init() { - LOGGER.info("method=init||DslLoaderUtil init start."); - List dslFileNames = Lists.newLinkedList(); - - // 反射获取接口中定义的变量中的值 - Field[] fields = DslsConstant.class.getDeclaredFields(); - for (int i = 0; i < fields.length; ++i) { - fields[i].setAccessible(true); - try { - dslFileNames.add(fields[i].get(null).toString()); - } catch (IllegalAccessException e) { - LOGGER.error("method=init||errMsg=fail to read {} error. ", fields[i].getName(), - e); - } - } - - // 加载dsl文件及内容 - for (String fileName : dslFileNames) { - dslsMap.put(fileName, readDslFileInJarFile(fileName)); - } - - // 输出加载的查询语句 - LOGGER.info("method=init||msg=dsl files count {}", dslsMap.size()); - for (Map.Entry entry : dslsMap.entrySet()) { - LOGGER.info("method=init||msg=file name {}, dsl content {}", entry.getKey(), - entry.getValue()); - } - - LOGGER.info("method=init||DslLoaderUtil init finished."); + dslsMap.putAll(loaderFileContext(FILE_PATH, DslConstant.class.getDeclaredFields())); } /** * 获取查询语句 - * - * @param fileName - * @return */ public String getDslByFileName(String fileName) { return dslsMap.get(fileName); @@ -84,10 +47,6 @@ public class DslLoaderUtil { /** * 获取格式化的查询语句 - * - * @param fileName - * @param args - * @return */ public String getFormatDslByFileName(String fileName, Object... args) { String loadDslContent = getDslByFileName(fileName); @@ -107,128 +66,4 @@ public class DslLoaderUtil { return dsl; } - - public String getFormatDslForCatIndexByCondition(String fileName, String boolMustDsl, Object... args) { - String formatDslByFileName = getFormatDslByFileName(fileName, args); - - return formatDslByFileName.replace("\"boolMustDsl\"", boolMustDsl); - } - - public String getFormatDslByFileNameByAggParam(String fileName, String clusterPhyMetrics, String interval, - String aggType, Object... args) { - String formatDslByFileName = getFormatDslByFileName(fileName, args); - - return formatDslByFileName - .replace("{interval}", interval) - .replace("{clusterPhyMetrics}", clusterPhyMetrics) - .replace("{aggType}", aggType); - } - - public String getFormatDslByFileNameAndOtherParam(String fileName, String interval, String aggsDsl, - Object... args) { - String formatDslByFileName = getFormatDslByFileName(fileName, args); - return formatDslByFileName - .replace("{interval}", interval) - .replace("\"aggsDsl\":1", aggsDsl); - } - - - public String getDslByTopNNameInfo(String fileName, String interval, String topNameStr, String aggsDsl, - Object... args) { - String formatDslByFileName = getFormatDslByFileName(fileName, args); - return formatDslByFileName - .replace("{interval}", interval) - .replace("\"aggsDsl\":1", aggsDsl) - .replace("\"topNameListStr\"", topNameStr); - } - - /**************************************************** private method ****************************************************/ - /** - * 去除json中的空格 - * - * @param sourceDsl - * @return - */ - private String trimJsonBank(String sourceDsl) { - List dslList = Lists.newArrayList(); - - DefaultJSONParser parser = null; - Object obj = null; - String dsl = sourceDsl; - - // 解析多个json,直到pos为0 - for (;;) { - try { - // 这里需要Feature.OrderedField.getMask()保持有序 - parser = new DefaultJSONParser(dsl, ParserConfig.getGlobalInstance(), - JSON.DEFAULT_PARSER_FEATURE | Feature.OrderedField.getMask()); - obj = parser.parse(); - } catch (Exception t) { - LOGGER.error("method=trimJsonBank||errMsg=parse json {} error. ", dsl, t); - } - if (obj == null) { - break; - } - - if (obj instanceof JSONObject) { - dslList.add( JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue)); - int pos = parser.getLexer().pos(); - if (pos <= 0) { - break; - } - dsl = dsl.substring(pos); - parser.getLexer().close(); - } else { - parser.getLexer().close(); - break; - } - } - - // 格式化异常或者有多个查询语句,返回原来的查询语句 - if (dslList.isEmpty() || dslList.size() > 1) { - return sourceDsl; - } - - return dslList.get(0); - } - - /** - * 从jar包中读取dsl语句文件 - * - * @param fileName - * @return - */ - private String readDslFileInJarFile(String fileName) { - InputStream inputStream = this.getClass().getClassLoader() - .getResourceAsStream( String.format("dsl/%s", fileName)); - if (inputStream != null) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - String line = null; - List lines = Lists.newLinkedList(); - try { - while ((line = bufferedReader.readLine()) != null) { - lines.add(line); - } - return StringUtils.join(lines, ""); - - } catch (IOException e) { - LOGGER.error("method=readDslFileInJarFile||errMsg=read file {} error. ", fileName, - e); - - return ""; - } finally { - try { - inputStream.close(); - } catch (IOException e) { - LOGGER.error( - "method=readDslFileInJarFile||errMsg=fail to close file {} error. ", - fileName, e); - } - } - } else { - LOGGER.error("method=readDslFileInJarFile||errMsg=fail to read file {} content", - fileName); - return ""; - } - } } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java new file mode 100644 index 00000000..2fc61f38 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java @@ -0,0 +1,19 @@ +package com.xiaojukeji.know.streaming.km.persistence.es.template; + +/** + * @author didi + */ +public class TemplateConstant { + public static final String TOPIC_INDEX = "ks_kafka_topic_metric"; + public static final String CLUSTER_INDEX = "ks_kafka_cluster_metric"; + public static final String BROKER_INDEX = "ks_kafka_broker_metric"; + public static final String PARTITION_INDEX = "ks_kafka_partition_metric"; + public static final String GROUP_INDEX = "ks_kafka_group_metric"; + public static final String REPLICATION_INDEX = "ks_kafka_replication_metric"; + public static final String ZOOKEEPER_INDEX = "ks_kafka_zookeeper_metric"; + public static final String CONNECT_CLUSTER_INDEX = "ks_kafka_connect_cluster_metric"; + public static final String CONNECT_CONNECTOR_INDEX = "ks_kafka_connect_connector_metric"; + + private TemplateConstant() { + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateLoaderUtil.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateLoaderUtil.java new file mode 100644 index 00000000..092506e7 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateLoaderUtil.java @@ -0,0 +1,28 @@ +package com.xiaojukeji.know.streaming.km.persistence.es.template; + +import com.google.common.collect.Maps; +import com.xiaojukeji.know.streaming.km.persistence.es.ESFileLoader; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.Map; + +@Component +public class TemplateLoaderUtil extends ESFileLoader { + + private static final String FILE_PATH = "es/template/"; + + /** + * 查询语句容器 + */ + private Map templateMap = Maps.newHashMap(); + + @PostConstruct + public void init() { + templateMap.putAll(loaderFileContext(FILE_PATH, TemplateConstant.class.getDeclaredFields())); + } + + public String getContextByFileName(String fileName) { + return templateMap.get(fileName); + } +} diff --git a/km-persistence/src/main/resources/dsl/BaseMetricESDAO/getLatestMetricTime b/km-persistence/src/main/resources/es/dsl/BaseMetricESDAO/getLatestMetricTime similarity index 100% rename from km-persistence/src/main/resources/dsl/BaseMetricESDAO/getLatestMetricTime rename to km-persistence/src/main/resources/es/dsl/BaseMetricESDAO/getLatestMetricTime diff --git a/km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getAggListBrokerMetrics b/km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getAggListBrokerMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getAggListBrokerMetrics rename to km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getAggListBrokerMetrics diff --git a/km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getAggSingleBrokerMetrics b/km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getAggSingleBrokerMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getAggSingleBrokerMetrics rename to km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getAggSingleBrokerMetrics diff --git a/km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getAggTopMetricsBrokers b/km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getAggTopMetricsBrokers similarity index 100% rename from km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getAggTopMetricsBrokers rename to km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getAggTopMetricsBrokers diff --git a/km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getBrokerLatestMetrics b/km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getBrokerLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/BrokerMetricESDAO/getBrokerLatestMetrics rename to km-persistence/src/main/resources/es/dsl/BrokerMetricESDAO/getBrokerLatestMetrics diff --git a/km-persistence/src/main/resources/dsl/ClusterMetricESDAO/getAggListClusterMetrics b/km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/getAggListClusterMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ClusterMetricESDAO/getAggListClusterMetrics rename to km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/getAggListClusterMetrics diff --git a/km-persistence/src/main/resources/dsl/ClusterMetricESDAO/getAggSingleClusterMetrics b/km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/getAggSingleClusterMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ClusterMetricESDAO/getAggSingleClusterMetrics rename to km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/getAggSingleClusterMetrics diff --git a/km-persistence/src/main/resources/dsl/ClusterMetricESDAO/getClusterLatestMetrics b/km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/getClusterLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ClusterMetricESDAO/getClusterLatestMetrics rename to km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/getClusterLatestMetrics diff --git a/km-persistence/src/main/resources/dsl/ClusterMetricESDAO/listClusterWithLatestMetrics b/km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/listClusterWithLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ClusterMetricESDAO/listClusterWithLatestMetrics rename to km-persistence/src/main/resources/es/dsl/ClusterMetricESDAO/listClusterWithLatestMetrics diff --git a/km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggListConnectClusterMetrics b/km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggListConnectClusterMetrics new file mode 100644 index 00000000..ebdb7dae --- /dev/null +++ b/km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggListConnectClusterMetrics @@ -0,0 +1,44 @@ +{ + "size": 0, + "query": { + "bool": { + "must": [ + { + "term": { + "clusterPhyId": { + "value": %d + } + } + }, + { + "term": { + "connectClusterId": { + "value": %d + } + } + }, + { + "range": { + "timestamp": { + "gte": %d, + "lte": %d + } + } + } + ] + } + }, + "aggs": { + "hist": { + "date_histogram": { + "field": "timestamp", + "fixed_interval": "%s", + "time_zone": "Asia/Shanghai", + "min_doc_count": 0 + }, + "aggs": { + %s + } + } + } +} \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggTopMetricsConnectClusters b/km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggTopMetricsConnectClusters new file mode 100644 index 00000000..d3f64d83 --- /dev/null +++ b/km-persistence/src/main/resources/es/dsl/ConnectClusterMetricESDAO/getAggTopMetricsConnectClusters @@ -0,0 +1,45 @@ +{ + "size": 0, + "query": { + "bool": { + "must": [ + { + "term": { + "clusterPhyId": { + "value": %d + } + } + }, + { + "range": { + "timestamp": { + "gte": %d, + "lte": %d + } + } + } + ] + } + }, + "aggs": { + "hist": { + "terms": { + "field": "connectClusterId", + "collect_mode": "breadth_first" + }, + "aggs": { + "hist": { + "date_histogram": { + "field": "timestamp", + "fixed_interval": "%s", + "time_zone": "Asia/Shanghai", + "min_doc_count": 0 + }, + "aggs": { + %s + } + } + } + } + } +} \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggListMetric b/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggListMetric new file mode 100644 index 00000000..f46c75f5 --- /dev/null +++ b/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggListMetric @@ -0,0 +1,51 @@ +{ + "size": 0, + "query": { + "bool": { + "must": [ + { + "term": { + "clusterPhyId": { + "value": %d + } + } + }, + { + "term": { + "connectClusterId": { + "value": %d + } + } + }, + { + "term": { + "connectorName": { + "value": "%s" + } + } + }, + { + "range": { + "timestamp": { + "gte": %d, + "lte": %d + } + } + } + ] + } + }, + "aggs": { + "hist": { + "date_histogram": { + "field": "timestamp", + "fixed_interval": "%s", + "time_zone": "Asia/Shanghai", + "min_doc_count": 0 + }, + "aggs": { + %s + } + } + } +} \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggTopMetric b/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggTopMetric new file mode 100644 index 00000000..4dc1643b --- /dev/null +++ b/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorAggTopMetric @@ -0,0 +1,45 @@ +{ + "size": 0, + "query": { + "bool": { + "must": [ + { + "term": { + "clusterPhyId": { + "value": %d + } + } + }, + { + "range": { + "timestamp": { + "gte": %d, + "lte": %d + } + } + } + ] + } + }, + "aggs": { + "hist": { + "terms": { + "field": "connectorNameAndClusterId", + "collect_mode": "breadth_first" + }, + "aggs": { + "hist": { + "date_histogram": { + "field": "timestamp", + "fixed_interval": "%s", + "time_zone": "Asia/Shanghai", + "min_doc_count": 0 + }, + "aggs": { + %s + } + } + } + } + } +} \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorLatestMetric b/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorLatestMetric new file mode 100644 index 00000000..ab1e16eb --- /dev/null +++ b/km-persistence/src/main/resources/es/dsl/ConnectorMetricESDAO/getConnectorLatestMetric @@ -0,0 +1,39 @@ +{ + "size":1000, + "query": { + "bool": { + "must": [ + { + "term": { + "connectClusterId": { + "value": %d + } + } + }, + { + "term": { + "connectorName": { + "value": "%s" + } + } + }, + { + "range": { + "timestamp": { + "gte": %d, + "lte": %d + } + } + } + %s + ] + } + }, + "sort": [ + { + "timestamp": { + "order": "desc" + } + } + ] +} \ No newline at end of file diff --git a/km-persistence/src/main/resources/dsl/GroupMetricESDAO/countGroupMetricValue b/km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/countGroupMetricValue similarity index 100% rename from km-persistence/src/main/resources/dsl/GroupMetricESDAO/countGroupMetricValue rename to km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/countGroupMetricValue diff --git a/km-persistence/src/main/resources/dsl/GroupMetricESDAO/countGroupNotMetricValue b/km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/countGroupNotMetricValue similarity index 100% rename from km-persistence/src/main/resources/dsl/GroupMetricESDAO/countGroupNotMetricValue rename to km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/countGroupNotMetricValue diff --git a/km-persistence/src/main/resources/dsl/GroupMetricESDAO/getTopicPartitionOfGroup b/km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/getTopicPartitionOfGroup similarity index 100% rename from km-persistence/src/main/resources/dsl/GroupMetricESDAO/getTopicPartitionOfGroup rename to km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/getTopicPartitionOfGroup diff --git a/km-persistence/src/main/resources/dsl/GroupMetricESDAO/listGroupMetrics b/km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/listGroupMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/GroupMetricESDAO/listGroupMetrics rename to km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/listGroupMetrics diff --git a/km-persistence/src/main/resources/dsl/GroupMetricESDAO/listLatestMetricsAggByGroupTopic b/km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/listLatestMetricsAggByGroupTopic similarity index 100% rename from km-persistence/src/main/resources/dsl/GroupMetricESDAO/listLatestMetricsAggByGroupTopic rename to km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/listLatestMetricsAggByGroupTopic diff --git a/km-persistence/src/main/resources/dsl/GroupMetricESDAO/listPartitionLatestMetrics b/km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/listPartitionLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/GroupMetricESDAO/listPartitionLatestMetrics rename to km-persistence/src/main/resources/es/dsl/GroupMetricESDAO/listPartitionLatestMetrics diff --git a/km-persistence/src/main/resources/dsl/PartitionMetricESDAO/getPartitionLatestMetrics b/km-persistence/src/main/resources/es/dsl/PartitionMetricESDAO/getPartitionLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/PartitionMetricESDAO/getPartitionLatestMetrics rename to km-persistence/src/main/resources/es/dsl/PartitionMetricESDAO/getPartitionLatestMetrics diff --git a/km-persistence/src/main/resources/dsl/PartitionMetricESDAO/listPartitionLatestMetricsByTopic b/km-persistence/src/main/resources/es/dsl/PartitionMetricESDAO/listPartitionLatestMetricsByTopic similarity index 100% rename from km-persistence/src/main/resources/dsl/PartitionMetricESDAO/listPartitionLatestMetricsByTopic rename to km-persistence/src/main/resources/es/dsl/PartitionMetricESDAO/listPartitionLatestMetricsByTopic diff --git a/km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics b/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics rename to km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics diff --git a/km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics b/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics rename to km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/countTopicMetricValue b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/countTopicMetricValue similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/countTopicMetricValue rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/countTopicMetricValue diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/countTopicNotMetricValue b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/countTopicNotMetricValue similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/countTopicNotMetricValue rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/countTopicNotMetricValue diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/getAggListMetrics b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getAggListMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/getAggListMetrics rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getAggListMetrics diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/getAggSingleMetrics b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getAggSingleMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/getAggSingleMetrics rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getAggSingleMetrics diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/getAggTopMetricsTopics b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getAggTopMetricsTopics similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/getAggTopMetricsTopics rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getAggTopMetricsTopics diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/getMaxOrMinSingleMetric b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getMaxOrMinSingleMetric similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/getMaxOrMinSingleMetric rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getMaxOrMinSingleMetric diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/getTopicLatestMetric b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getTopicLatestMetric similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/getTopicLatestMetric rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getTopicLatestMetric diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/getTopicLatestMetricByBrokerId b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getTopicLatestMetricByBrokerId similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/getTopicLatestMetricByBrokerId rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/getTopicLatestMetricByBrokerId diff --git a/km-persistence/src/main/resources/dsl/TopicMetricESDAO/listTopicWithLatestMetrics b/km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/listTopicWithLatestMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/TopicMetricESDAO/listTopicWithLatestMetrics rename to km-persistence/src/main/resources/es/dsl/TopicMetricESDAO/listTopicWithLatestMetrics diff --git a/km-persistence/src/main/resources/dsl/ZookeeperMetricESDAO/getAggListZookeeperMetrics b/km-persistence/src/main/resources/es/dsl/ZookeeperMetricESDAO/getAggListZookeeperMetrics similarity index 100% rename from km-persistence/src/main/resources/dsl/ZookeeperMetricESDAO/getAggListZookeeperMetrics rename to km-persistence/src/main/resources/es/dsl/ZookeeperMetricESDAO/getAggListZookeeperMetrics diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_broker_metric b/km-persistence/src/main/resources/es/template/ks_kafka_broker_metric new file mode 100644 index 00000000..749b1494 --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_broker_metric @@ -0,0 +1,101 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_broker_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "brokerId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "NetworkProcessorAvgIdle" : { + "type" : "float" + }, + "UnderReplicatedPartitions" : { + "type" : "float" + }, + "BytesIn_min_15" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "RequestHandlerAvgIdle" : { + "type" : "float" + }, + "connectionsCount" : { + "type" : "float" + }, + "BytesIn_min_5" : { + "type" : "float" + }, + "HealthScore" : { + "type" : "float" + }, + "BytesOut" : { + "type" : "float" + }, + "BytesOut_min_15" : { + "type" : "float" + }, + "BytesIn" : { + "type" : "float" + }, + "BytesOut_min_5" : { + "type" : "float" + }, + "TotalRequestQueueSize" : { + "type" : "float" + }, + "MessagesIn" : { + "type" : "float" + }, + "TotalProduceRequests" : { + "type" : "float" + }, + "HealthCheckPassed" : { + "type" : "float" + }, + "TotalResponseQueueSize" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_cluster_metric b/km-persistence/src/main/resources/es/template/ks_kafka_cluster_metric new file mode 100644 index 00000000..942fdce2 --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_cluster_metric @@ -0,0 +1,186 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_cluster_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "Connections" : { + "type" : "double" + }, + "BytesIn_min_15" : { + "type" : "double" + }, + "PartitionURP" : { + "type" : "double" + }, + "HealthScore_Topics" : { + "type" : "double" + }, + "EventQueueSize" : { + "type" : "double" + }, + "ActiveControllerCount" : { + "type" : "double" + }, + "GroupDeads" : { + "type" : "double" + }, + "BytesIn_min_5" : { + "type" : "double" + }, + "HealthCheckTotal_Topics" : { + "type" : "double" + }, + "Partitions" : { + "type" : "double" + }, + "BytesOut" : { + "type" : "double" + }, + "Groups" : { + "type" : "double" + }, + "BytesOut_min_15" : { + "type" : "double" + }, + "TotalRequestQueueSize" : { + "type" : "double" + }, + "HealthCheckPassed_Groups" : { + "type" : "double" + }, + "TotalProduceRequests" : { + "type" : "double" + }, + "HealthCheckPassed" : { + "type" : "double" + }, + "TotalLogSize" : { + "type" : "double" + }, + "GroupEmptys" : { + "type" : "double" + }, + "PartitionNoLeader" : { + "type" : "double" + }, + "HealthScore_Brokers" : { + "type" : "double" + }, + "Messages" : { + "type" : "double" + }, + "Topics" : { + "type" : "double" + }, + "PartitionMinISR_E" : { + "type" : "double" + }, + "HealthCheckTotal" : { + "type" : "double" + }, + "Brokers" : { + "type" : "double" + }, + "Replicas" : { + "type" : "double" + }, + "HealthCheckTotal_Groups" : { + "type" : "double" + }, + "GroupRebalances" : { + "type" : "double" + }, + "MessageIn" : { + "type" : "double" + }, + "HealthScore" : { + "type" : "double" + }, + "HealthCheckPassed_Topics" : { + "type" : "double" + }, + "HealthCheckTotal_Brokers" : { + "type" : "double" + }, + "PartitionMinISR_S" : { + "type" : "double" + }, + "BytesIn" : { + "type" : "double" + }, + "BytesOut_min_5" : { + "type" : "double" + }, + "GroupActives" : { + "type" : "double" + }, + "MessagesIn" : { + "type" : "double" + }, + "GroupReBalances" : { + "type" : "double" + }, + "HealthCheckPassed_Brokers" : { + "type" : "double" + }, + "HealthScore_Groups" : { + "type" : "double" + }, + "TotalResponseQueueSize" : { + "type" : "double" + }, + "Zookeepers" : { + "type" : "double" + }, + "LeaderMessages" : { + "type" : "double" + }, + "HealthScore_Cluster" : { + "type" : "double" + }, + "HealthCheckPassed_Cluster" : { + "type" : "double" + }, + "HealthCheckTotal_Cluster" : { + "type" : "double" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "type" : "date" + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_connect_cluster_metric b/km-persistence/src/main/resources/es/template/ks_kafka_connect_cluster_metric new file mode 100644 index 00000000..7fa4c523 --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_connect_cluster_metric @@ -0,0 +1,86 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_cluster_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "ConnectorCount" : { + "type" : "float" + }, + "TaskCount" : { + "type" : "float" + }, + "ConnectorStartupAttemptsTotal" : { + "type" : "float" + }, + "ConnectorStartupFailurePercentage" : { + "type" : "float" + }, + "ConnectorStartupFailureTotal" : { + "type" : "float" + }, + "ConnectorStartupSuccessPercentage" : { + "type" : "float" + }, + "ConnectorStartupSuccessTotal" : { + "type" : "float" + }, + "TaskStartupAttemptsTotal" : { + "type" : "float" + }, + "TaskStartupFailurePercentage" : { + "type" : "float" + }, + "TaskStartupFailureTotal" : { + "type" : "float" + }, + "TaskStartupSuccessPercentage" : { + "type" : "float" + }, + "TaskStartupSuccessTotal" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_connect_connector_metric b/km-persistence/src/main/resources/es/template/ks_kafka_connect_connector_metric new file mode 100644 index 00000000..b26836a0 --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_connect_connector_metric @@ -0,0 +1,194 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_connector_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "connectorName" : { + "type" : "keyword" + }, + "connectorNameAndClusterId" : { + "type" : "keyword" + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "HealthState" : { + "type" : "float" + }, + "ConnectorTotalTaskCount" : { + "type" : "float" + }, + "HealthCheckPassed" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "ConnectorRunningTaskCount" : { + "type" : "float" + }, + "ConnectorPausedTaskCount" : { + "type" : "float" + }, + "ConnectorFailedTaskCount" : { + "type" : "float" + }, + "ConnectorUnassignedTaskCount" : { + "type" : "float" + }, + "BatchSizeAvg" : { + "type" : "float" + }, + "BatchSizeMax" : { + "type" : "float" + }, + "OffsetCommitAvgTimeMs" : { + "type" : "float" + }, + "OffsetCommitMaxTimeMs" : { + "type" : "float" + }, + "OffsetCommitFailurePercentage" : { + "type" : "float" + }, + "OffsetCommitSuccessPercentage" : { + "type" : "float" + }, + "PollBatchAvgTimeMs" : { + "type" : "float" + }, + "PollBatchMaxTimeMs" : { + "type" : "float" + }, + "SourceRecordActiveCount" : { + "type" : "float" + }, + "SourceRecordActiveCountAvg" : { + "type" : "float" + }, + "SourceRecordActiveCountMax" : { + "type" : "float" + }, + "SourceRecordPollRate" : { + "type" : "float" + }, + "SourceRecordPollTotal" : { + "type" : "float" + }, + "SourceRecordWriteRate" : { + "type" : "float" + }, + "SourceRecordWriteTotal" : { + "type" : "float" + }, + "OffsetCommitCompletionRate" : { + "type" : "float" + }, + "OffsetCommitCompletionTotal" : { + "type" : "float" + }, + "OffsetCommitSkipRate" : { + "type" : "float" + }, + "OffsetCommitSkipTotal" : { + "type" : "float" + }, + "PartitionCount" : { + "type" : "float" + }, + "PutBatchAvgTimeMs" : { + "type" : "float" + }, + "PutBatchMaxTimeMs" : { + "type" : "float" + }, + "SinkRecordActiveCount" : { + "type" : "float" + }, + "SinkRecordActiveCountAvg" : { + "type" : "float" + }, + "SinkRecordActiveCountMax" : { + "type" : "float" + }, + "SinkRecordLagMax" : { + "type" : "float" + }, + "SinkRecordReadRate" : { + "type" : "float" + }, + "SinkRecordReadTotal" : { + "type" : "float" + }, + "SinkRecordSendRate" : { + "type" : "float" + }, + "SinkRecordSendTotal" : { + "type" : "float" + }, + "DeadletterqueueProduceFailures" : { + "type" : "float" + }, + "DeadletterqueueProduceRequests" : { + "type" : "float" + }, + "LastErrorTimestamp" : { + "type" : "float" + }, + "TotalErrorsLogged" : { + "type" : "float" + }, + "TotalRecordErrors" : { + "type" : "float" + }, + "TotalRecordFailures" : { + "type" : "float" + }, + "TotalRecordsSkipped" : { + "type" : "float" + }, + "TotalRetries" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_group_metric b/km-persistence/src/main/resources/es/template/ks_kafka_group_metric new file mode 100644 index 00000000..3f04f16a --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_group_metric @@ -0,0 +1,74 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_group_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "10" + } + }, + "mappings" : { + "properties" : { + "group" : { + "type" : "keyword" + }, + "partitionId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "topic" : { + "type" : "keyword" + }, + "metrics" : { + "properties" : { + "HealthScore" : { + "type" : "float" + }, + "Lag" : { + "type" : "float" + }, + "OffsetConsumed" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "HealthCheckPassed" : { + "type" : "float" + } + } + }, + "groupMetric" : { + "type" : "keyword" + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric b/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric new file mode 100644 index 00000000..68264660 --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric @@ -0,0 +1,65 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_partition_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "10" + } + }, + "mappings" : { + "properties" : { + "brokerId" : { + "type" : "long" + }, + "partitionId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "topic" : { + "type" : "keyword" + }, + "metrics" : { + "properties" : { + "LogStartOffset" : { + "type" : "float" + }, + "Messages" : { + "type" : "float" + }, + "LogEndOffset" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_replication_metric b/km-persistence/src/main/resources/es/template/ks_kafka_replication_metric new file mode 100644 index 00000000..a8ff4b53 --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_replication_metric @@ -0,0 +1,65 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_replication_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "10" + } + }, + "mappings" : { + "properties" : { + "brokerId" : { + "type" : "long" + }, + "partitionId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "topic" : { + "type" : "keyword" + }, + "metrics" : { + "properties" : { + "LogStartOffset" : { + "type" : "float" + }, + "Messages" : { + "type" : "float" + }, + "LogEndOffset" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric b/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric new file mode 100644 index 00000000..b2eed35d --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric @@ -0,0 +1,116 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_topic_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "10" + } + }, + "mappings" : { + "properties" : { + "brokerId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "topic" : { + "type" : "keyword" + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "BytesIn_min_15" : { + "type" : "float" + }, + "Messages" : { + "type" : "float" + }, + "BytesRejected" : { + "type" : "float" + }, + "PartitionURP" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "ReplicationCount" : { + "type" : "float" + }, + "ReplicationBytesOut" : { + "type" : "float" + }, + "ReplicationBytesIn" : { + "type" : "float" + }, + "FailedFetchRequests" : { + "type" : "float" + }, + "BytesIn_min_5" : { + "type" : "float" + }, + "HealthScore" : { + "type" : "float" + }, + "LogSize" : { + "type" : "float" + }, + "BytesOut" : { + "type" : "float" + }, + "BytesOut_min_15" : { + "type" : "float" + }, + "FailedProduceRequests" : { + "type" : "float" + }, + "BytesIn" : { + "type" : "float" + }, + "BytesOut_min_5" : { + "type" : "float" + }, + "MessagesIn" : { + "type" : "float" + }, + "TotalProduceRequests" : { + "type" : "float" + }, + "HealthCheckPassed" : { + "type" : "float" + } + } + }, + "brokerAgg" : { + "type" : "keyword" + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric b/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric new file mode 100644 index 00000000..e2efb41a --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric @@ -0,0 +1,84 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_zookeeper_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "10" + } + }, + "mappings" : { + "properties" : { + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "AvgRequestLatency" : { + "type" : "double" + }, + "MinRequestLatency" : { + "type" : "double" + }, + "MaxRequestLatency" : { + "type" : "double" + }, + "OutstandingRequests" : { + "type" : "double" + }, + "NodeCount" : { + "type" : "double" + }, + "WatchCount" : { + "type" : "double" + }, + "NumAliveConnections" : { + "type" : "double" + }, + "PacketsReceived" : { + "type" : "double" + }, + "PacketsSent" : { + "type" : "double" + }, + "EphemeralsCount" : { + "type" : "double" + }, + "ApproximateDataSize" : { + "type" : "double" + }, + "OpenFileDescriptorCount" : { + "type" : "double" + }, + "MaxFileDescriptorCount" : { + "type" : "double" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "type" : "date" + } + } + }, + "aliases" : { } + } \ No newline at end of file From fb5964af846843951026f86e4fb58d44c61656cc Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 18:00:29 +0800 Subject: [PATCH 057/150] =?UTF-8?q?=E8=A1=A5=E5=85=85kafka-connect?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- km-common/pom.xml | 4 ++++ pom.xml | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/km-common/pom.xml b/km-common/pom.xml index e9e66c22..08b90dd7 100644 --- a/km-common/pom.xml +++ b/km-common/pom.xml @@ -127,5 +127,9 @@ org.apache.kafka kafka_2.13 + + org.apache.kafka + connect-runtime + \ No newline at end of file diff --git a/pom.xml b/pom.xml index e1b7bbc3..dfd612aa 100644 --- a/pom.xml +++ b/pom.xml @@ -25,8 +25,11 @@ 2.9.2 1.5.21 - 2.8.1 + + 2.8.1 + 2.8.1 + 2.8.1 6.6.2 3.4.2 @@ -196,6 +199,7 @@ provided + org.apache.kafka kafka-clients @@ -206,6 +210,25 @@ kafka_2.13 ${kafka.version} + + org.apache.kafka + connect-runtime + ${kafka-connect-runtime.version} + + + org.apache.kafka + connect-transforms + + + org.apache.kafka + connect-api + + + org.slf4j + slf4j-log4j12 + + + com.google.guava From e8652d5db581a88a68a8eca4dbb1bd1ec04b5c8c Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 18:13:44 +0800 Subject: [PATCH 058/150] =?UTF-8?q?Connect=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/biz/group/impl/GroupManagerImpl.java | 77 ++- .../topic/impl/TopicConfigManagerImpl.java | 4 +- .../km/biz/version/VersionControlManager.java | 4 +- .../impl/VersionControlManagerImpl.java | 20 +- .../metric/AbstractMetricCollector.java | 32 +- .../AbstractConnectMetricCollector.java | 50 ++ .../ConnectClusterMetricCollector.java | 83 +++ .../ConnectConnectorMetricCollector.java | 102 +++ .../kafka/AbstractKafkaMetricCollector.java | 50 ++ .../metric/kafka/BrokerMetricCollector.java | 5 +- .../metric/kafka/ClusterMetricCollector.java | 5 +- .../metric/kafka/GroupMetricCollector.java | 7 +- .../kafka/PartitionMetricCollector.java | 5 +- .../metric/kafka/ReplicaMetricCollector.java | 5 +- .../metric/kafka/TopicMetricCollector.java | 5 +- .../kafka/ZookeeperMetricCollector.java | 5 +- .../connect/ConnectClusterMetricESSender.java | 33 + .../sink/connect/ConnectorMetricESSender.java | 33 + .../cluster/ClusterConnectorsOverviewDTO.java | 28 + .../bean/dto/connect/ClusterConnectorDTO.java | 32 + .../connect/cluster/ConnectClusterDTO.java | 29 + .../connect/connector/ConnectorActionDTO.java | 20 + .../connector/ConnectorConfigModifyDTO.java | 21 + .../connect/connector/ConnectorCreateDTO.java | 21 + .../connect/connector/ConnectorDeleteDTO.java | 14 + .../bean/dto/connect/task/TaskActionDTO.java | 20 + .../connect/MetricsConnectClustersDTO.java | 22 + .../connect/MetricsConnectorsDTO.java | 23 + .../km/common/bean/entity/broker/Broker.java | 1 - .../config/metric/UserMetricConfig.java | 2 - .../bean/entity/connect/ConnectCluster.java | 61 ++ .../connect/ConnectClusterMetadata.java | 38 ++ .../bean/entity/connect/ConnectWorker.java | 87 +++ .../bean/entity/connect/WorkerConnector.java | 58 ++ .../connect/config/ConnectConfigInfo.java | 19 + .../connect/config/ConnectConfigInfos.java | 62 ++ .../connect/config/ConnectConfigKeyInfo.java | 38 ++ .../config/ConnectConfigValueInfo.java | 27 + .../connector/KSAbstractConnectState.java | 20 + .../entity/connect/connector/KSConnector.java | 48 ++ .../connect/connector/KSConnectorInfo.java | 26 + .../connect/connector/KSConnectorState.java | 11 + .../connector/KSConnectorStateInfo.java | 21 + .../entity/connect/connector/KSTaskState.java | 12 + .../connect/plugin/ConnectPluginBasic.java | 38 ++ .../km/common/bean/entity/group/Group.java | 10 +- .../param/cluster/ConnectClusterParam.java | 16 + .../entity/param/connect/ConnectorParam.java | 26 + .../connect/ConnectClusterMetricParam.java | 21 + .../metric/connect/ConnectorMetricParam.java | 29 + .../km/common/bean/entity/result/Result.java | 7 + .../bean/entity/result/ResultStatus.java | 2 + .../entity/version/VersionConnectJmxInfo.java | 13 + .../entity/version/VersionControlItem.java | 1 - .../common/bean/entity/zookeeper/Znode.java | 2 - .../ConnectClusterLoadChangedEvent.java | 27 + .../connect/ConnectClusterMetricEvent.java | 21 + .../metric/connect/ConnectorMetricEvent.java | 21 + .../bean/po/connect/ConnectClusterPO.java | 51 ++ .../bean/po/connect/ConnectWorkerPO.java | 55 ++ .../common/bean/po/connect/ConnectorPO.java | 50 ++ .../bean/po/connect/WorkerConnectorPO.java | 45 ++ .../ConnectClusterBasicCombineExistVO.java | 18 + .../connect/ConnectClusterBasicVO.java | 44 ++ .../vo/cluster/connect/ConnectStateVO.java | 42 ++ .../connector/ClusterConnectorOverviewVO.java | 42 ++ .../connector/ClusterWorkerOverviewVO.java | 31 + .../ConnectorBasicCombineExistVO.java | 18 + .../cluster/connector/ConnectorBasicVO.java | 25 + .../connect/connector/ConnectorStateVO.java | 32 + .../connect/plugin/ConnectConfigInfoVO.java | 19 + .../connect/plugin/ConnectConfigInfosVO.java | 25 + .../plugin/ConnectConfigKeyInfoVO.java | 38 ++ .../plugin/ConnectConfigValueInfoVO.java | 27 + .../connect/plugin/ConnectPluginBasicVO.java | 26 + .../vo/connect/task/KCTaskOverviewVO.java | 32 + .../bean/vo/health/HealthCheckConfigVO.java | 3 + .../vo/health/HealthScoreBaseResultVO.java | 3 + .../common/component/RestTemplateConfig.java | 30 +- .../km/common/component/RestTool.java | 74 ++- .../km/common/constant/ApiPrefix.java | 2 + .../km/common/constant/Constant.java | 1 + .../km/common/constant/KafkaConstant.java | 2 + .../km/common/constant/MsgConstant.java | 15 + .../connect/KafkaConnectConstant.java | 15 + .../km/common/converter/ConnectConverter.java | 142 +++++ .../converter/HealthScoreVOConverter.java | 2 + .../km/common/converter/TopicVOConverter.java | 2 - .../enums/connect/ConnectActionEnum.java | 32 + .../enums/connect/ConnectorTypeEnum.java | 32 + .../km/common/enums/group/GroupTypeEnum.java | 24 +- .../health/HealthCheckDimensionEnum.java | 23 +- .../enums/health/HealthCheckNameEnum.java | 27 + .../enums/operaterecord/ModuleEnum.java | 3 + .../enums/operaterecord/OperationEnum.java | 2 + .../enums/version/VersionItemTypeEnum.java | 7 + .../streaming/km/common/jmx/JmxAttribute.java | 110 ++++ .../km/common/jmx/JmxConnectorWrap.java | 18 +- .../know/streaming/km/common/jmx/JmxName.java | 14 + .../km/common/utils/CommonUtils.java | 10 + .../common/utils/PaginationMetricsUtil.java | 101 +++ km-core/pom.xml | 4 + .../cache/CollectedMetricsLocalCache.java | 43 ++ .../service/acl/impl/KafkaAclServiceImpl.java | 3 +- .../acl/impl/OpKafkaAclServiceImpl.java | 3 +- .../broker/impl/BrokerConfigServiceImpl.java | 3 +- .../broker/impl/BrokerServiceImpl.java | 8 +- .../impl/ClusterValidateServiceImpl.java | 2 - .../impl/ControllerChangeLogServiceImpl.java | 2 - .../cluster/ConnectClusterMetricService.java | 27 + .../cluster/ConnectClusterService.java | 34 + .../impl/ConnectClusterMetricServiceImpl.java | 270 ++++++++ .../impl/ConnectClusterServiceImpl.java | 243 ++++++++ .../connector/ConnectorMetricService.java | 36 ++ .../connect/connector/ConnectorService.java | 59 ++ .../impl/ConnectorMetricServiceImpl.java | 443 +++++++++++++ .../connector/impl/ConnectorServiceImpl.java | 581 ++++++++++++++++++ .../service/connect/plugin/PluginService.java | 20 + .../plugin/impl/PluginServiceImpl.java | 112 ++++ .../worker/WorkerConnectorService.java | 23 + .../service/connect/worker/WorkerService.java | 38 ++ .../impl/WorkerConnectorServiceImpl.java | 143 +++++ .../worker/impl/WorkerServiceImpl.java | 114 ++++ .../km/core/service/group/GroupService.java | 9 +- .../service/group/impl/GroupServiceImpl.java | 81 ++- .../checker/AbstractHealthCheckService.java | 2 +- .../HealthCheckConnectClusterService.java | 95 +++ .../connect/HealthCheckConnectorService.java | 122 ++++ .../group/HealthCheckGroupService.java | 3 +- .../topic/HealthCheckTopicService.java | 2 +- .../checkresult/HealthCheckResultService.java | 2 + .../impl/HealthCheckResultServiceImpl.java | 28 + .../health/state/HealthStateService.java | 5 +- .../state/impl/HealthStateServiceImpl.java | 156 ++++- .../kafkauser/impl/KafkaUserServiceImpl.java | 3 +- .../impl/OpPartitionServiceImpl.java | 4 +- .../impl/PartitionMetricServiceImpl.java | 1 - .../partition/impl/PartitionServiceImpl.java | 4 +- .../reassign/impl/ReassignServiceImpl.java | 4 +- .../topic/impl/OpTopicServiceImpl.java | 4 +- .../topic/impl/TopicConfigServiceImpl.java | 4 +- .../version/BaseConnectorMetricService.java | 74 +++ .../BaseConnectorVersionControlService.java | 55 ++ .../BaseKafkaVersionControlService.java | 52 ++ .../service/version/BaseMetricService.java | 2 +- .../version/BaseVersionControlService.java | 22 +- .../version/VersionControlService.java | 22 +- .../impl/VersionControlServiceImpl.java | 61 +- .../metrics/BaseMetricVersionMetric.java | 15 + .../ConnectClusterMetricVersionItems.java | 110 ++++ .../connect/ConnectorMetricVersionItems.java | 310 ++++++++++ .../MirrorMakerMetricVersionItems.java | 27 + .../kafka/ClusterMetricVersionItems.java | 7 + .../common/AbstractMonitorSinkTag.java | 14 - .../component/AbstractMonitorSinkService.java | 23 +- .../AbstractConnectClusterChangeHandler.java | 44 ++ .../persistence/connect/ConnectJMXClient.java | 146 +++++ .../cache/LoadedConnectClusterCache.java | 37 ++ .../ScheduleFlushConnectClusterTask.java | 104 ++++ .../km/persistence/jmx/impl/JmxDAOImpl.java | 2 +- .../km/persistence/kafka/KafkaJMXClient.java | 3 +- .../mysql/connect/ConnectClusterDAO.java | 9 + .../mysql/connect/ConnectWorkerDAO.java | 9 + .../mysql/connect/ConnectorDAO.java | 9 + .../mysql/connect/WorkerConnectorDAO.java | 9 + .../api/v3/health/KafkaHealthController.java | 25 +- .../api/v3/version/VersionController.java | 16 +- .../api/v3/zk/ZookeeperMetricsController.java | 4 - .../kafka/metadata/SyncKafkaGroupTask.java | 4 +- 169 files changed, 6472 insertions(+), 317 deletions(-) create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/AbstractConnectMetricCollector.java create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectClusterMetricCollector.java create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/AbstractKafkaMetricCollector.java create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectClusterMetricESSender.java create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectorMetricESSender.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterConnectorsOverviewDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/cluster/ConnectClusterDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorActionDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorDeleteDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/task/TaskActionDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectClustersDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectorsDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectCluster.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectClusterMetadata.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/WorkerConnector.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfo.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigKeyInfo.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigValueInfo.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSAbstractConnectState.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorInfo.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorState.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorStateInfo.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSTaskState.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/plugin/ConnectPluginBasic.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ConnectClusterParam.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectClusterMetricParam.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectorMetricParam.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionConnectJmxInfo.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/cluster/connect/ConnectClusterLoadChangedEvent.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectClusterMetricEvent.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectorMetricEvent.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectClusterPO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectWorkerPO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/WorkerConnectorPO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicCombineExistVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectStateVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterConnectorOverviewVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterWorkerOverviewVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicCombineExistVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/connector/ConnectorStateVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfoVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfosVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigKeyInfoVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigValueInfoVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectPluginBasicVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/task/KCTaskOverviewVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectActionEnum.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectorTypeEnum.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterMetricService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterMetricServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorMetricService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/PluginService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/impl/PluginServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerConnectorService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerConnectorServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerServiceImpl.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorVersionControlService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseKafkaVersionControlService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectClusterMetricVersionItems.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java delete mode 100644 km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/common/AbstractMonitorSinkTag.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/AbstractConnectClusterChangeHandler.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/ConnectJMXClient.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/cache/LoadedConnectClusterCache.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/schedule/ScheduleFlushConnectClusterTask.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectClusterDAO.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectWorkerDAO.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectorDAO.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/WorkerConnectorDAO.java diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java index 7d8c81ff..17216793 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/impl/GroupManagerImpl.java @@ -8,9 +8,13 @@ import com.xiaojukeji.know.streaming.km.common.bean.dto.group.GroupOffsetResetDT import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationSortDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.partition.PartitionOffsetDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.Group; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.GroupTopic; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.GroupTopicMember; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSGroupDescription; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSMemberConsumerAssignment; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSMemberDescription; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; @@ -35,14 +39,13 @@ import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil; import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; 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.partition.PartitionService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems; import com.xiaojukeji.know.streaming.km.persistence.es.dao.GroupMetricESDAO; -import org.apache.kafka.clients.admin.ConsumerGroupDescription; -import org.apache.kafka.clients.admin.MemberDescription; import org.apache.kafka.common.ConsumerGroupState; import org.apache.kafka.common.TopicPartition; import org.springframework.beans.factory.annotation.Autowired; @@ -51,6 +54,8 @@ import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; +import static com.xiaojukeji.know.streaming.km.common.enums.group.GroupTypeEnum.CONNECT_CLUSTER_PROTOCOL_TYPE; + @Component public class GroupManagerImpl implements GroupManager { private static final ILog log = LogFactory.getLog(GroupManagerImpl.class); @@ -70,6 +75,9 @@ public class GroupManagerImpl implements GroupManager { @Autowired private GroupMetricESDAO groupMetricESDAO; + @Autowired + private ClusterPhyService clusterPhyService; + @Override public PaginationResult pagingGroupMembers(Long clusterPhyId, String topicName, @@ -140,6 +148,11 @@ public class GroupManagerImpl implements GroupManager { String groupName, List latestMetricNames, PaginationSortDTO dto) throws NotExistException, AdminOperateException { + ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(clusterPhyId); + if (clusterPhy == null) { + return PaginationResult.buildFailure(MsgConstant.getClusterPhyNotExist(clusterPhyId), dto); + } + // 获取消费组消费的TopicPartition列表 Map consumedOffsetMap = groupService.getGroupOffsetFromKafka(clusterPhyId, groupName); List partitionList = consumedOffsetMap.keySet() @@ -150,13 +163,18 @@ public class GroupManagerImpl implements GroupManager { Collections.sort(partitionList); // 获取消费组当前运行信息 - ConsumerGroupDescription groupDescription = groupService.getGroupDescriptionFromKafka(clusterPhyId, groupName); + KSGroupDescription groupDescription = groupService.getGroupDescriptionFromKafka(clusterPhy, groupName); // 转换存储格式 - Map tpMemberMap = new HashMap<>(); - for (MemberDescription description: groupDescription.members()) { - for (TopicPartition tp: description.assignment().topicPartitions()) { - tpMemberMap.put(tp, description); + Map tpMemberMap = new HashMap<>(); + + //如果不是connect集群 + if (!groupDescription.protocolType().equals(CONNECT_CLUSTER_PROTOCOL_TYPE)) { + for (KSMemberDescription description : groupDescription.members()) { + KSMemberConsumerAssignment assignment = (KSMemberConsumerAssignment) description.assignment(); + for (TopicPartition tp : assignment.topicPartitions()) { + tpMemberMap.put(tp, description); + } } } @@ -173,11 +191,11 @@ public class GroupManagerImpl implements GroupManager { vo.setTopicName(topicName); vo.setPartitionId(groupMetrics.getPartitionId()); - MemberDescription memberDescription = tpMemberMap.get(new TopicPartition(topicName, groupMetrics.getPartitionId())); - if (memberDescription != null) { - vo.setMemberId(memberDescription.consumerId()); - vo.setHost(memberDescription.host()); - vo.setClientId(memberDescription.clientId()); + KSMemberDescription ksMemberDescription = tpMemberMap.get(new TopicPartition(topicName, groupMetrics.getPartitionId())); + if (ksMemberDescription != null) { + vo.setMemberId(ksMemberDescription.consumerId()); + vo.setHost(ksMemberDescription.host()); + vo.setClientId(ksMemberDescription.clientId()); } vo.setLatestMetrics(groupMetrics); @@ -203,7 +221,12 @@ public class GroupManagerImpl implements GroupManager { return rv; } - ConsumerGroupDescription description = groupService.getGroupDescriptionFromKafka(dto.getClusterId(), dto.getGroupName()); + ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(dto.getClusterId()); + if (clusterPhy == null) { + return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, MsgConstant.getClusterPhyNotExist(dto.getClusterId())); + } + + KSGroupDescription description = groupService.getGroupDescriptionFromKafka(clusterPhy, dto.getGroupName()); if (ConsumerGroupState.DEAD.equals(description.state()) && !dto.isCreateIfNotExist()) { return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, "group不存在, 重置失败"); } @@ -345,32 +368,4 @@ public class GroupManagerImpl implements GroupManager { dto ); } - - private List convert2GroupTopicOverviewVOList(String groupName, String state, List groupTopicList, List metricsList) { - if (metricsList == null) { - metricsList = new ArrayList<>(); - } - - // - Map metricsMap = new HashMap<>(); - for (GroupMetrics metrics : metricsList) { - if (!groupName.equals(metrics.getGroup())) continue; - metricsMap.put(metrics.getTopic(), metrics); - } - - List voList = new ArrayList<>(); - for (GroupTopicMember po : groupTopicList) { - GroupTopicOverviewVO vo = ConvertUtil.obj2Obj(po, GroupTopicOverviewVO.class); - vo.setGroupName(groupName); - vo.setState(state); - GroupMetrics metrics = metricsMap.get(po.getTopicName()); - if (metrics != null) { - vo.setMaxLag(ConvertUtil.Float2Long(metrics.getMetrics().get(GroupMetricVersionItems.GROUP_METRIC_LAG))); - } - - voList.add(vo); - } - return voList; - } - } diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicConfigManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicConfigManagerImpl.java index d52ad657..ccb02cf6 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicConfigManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/topic/impl/TopicConfigManagerImpl.java @@ -16,7 +16,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerConfigService; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicConfigService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -27,7 +27,7 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum.*; @Component -public class TopicConfigManagerImpl extends BaseVersionControlService implements TopicConfigManager { +public class TopicConfigManagerImpl extends BaseKafkaVersionControlService implements TopicConfigManager { private static final ILog log = LogFactory.getLog(TopicConfigManagerImpl.class); private static final String GET_DEFAULT_TOPIC_CONFIG = "getDefaultTopicConfig"; diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/VersionControlManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/VersionControlManager.java index 575a26d3..ea4a9dc2 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/VersionControlManager.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/VersionControlManager.java @@ -20,7 +20,7 @@ public interface VersionControlManager { * 获取当前ks所有支持的kafka版本 * @return */ - Result> listAllVersions(); + Result> listAllKafkaVersions(); /** * 获取全部集群 clusterId 中类型为 type 的指标,不论支持不支持 @@ -28,7 +28,7 @@ public interface VersionControlManager { * @param type * @return */ - Result> listClusterVersionControlItem(Long clusterId, Integer type); + Result> listKafkaClusterVersionControlItem(Long clusterId, Integer type); /** * 获取当前用户设置的用于展示的指标配置 diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java index 3ce527a1..6abfebba 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java @@ -17,6 +17,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.version.VersionItemVO; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.VersionUtil; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -92,6 +93,9 @@ public class VersionControlManagerImpl implements VersionControlManager { defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_BYTES_OUT, true)); } + @Autowired + private ClusterPhyService clusterPhyService; + @Autowired private VersionControlService versionControlService; @@ -107,7 +111,13 @@ public class VersionControlManagerImpl implements VersionControlManager { allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_BROKER.getCode()), VersionItemVO.class)); allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_PARTITION.getCode()), VersionItemVO.class)); allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_REPLICATION.getCode()), VersionItemVO.class)); + allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_ZOOKEEPER.getCode()), VersionItemVO.class)); + + allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_CONNECT_CLUSTER.getCode()), VersionItemVO.class)); + allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_CONNECT_CONNECTOR.getCode()), VersionItemVO.class)); + allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(METRIC_CONNECT_MIRROR_MAKER.getCode()), VersionItemVO.class)); + allVersionItemVO.addAll(ConvertUtil.list2List(versionControlService.listVersionControlItem(WEB_OP.getCode()), VersionItemVO.class)); Map map = allVersionItemVO.stream().collect( @@ -121,18 +131,20 @@ public class VersionControlManagerImpl implements VersionControlManager { } @Override - public Result> listAllVersions() { + public Result> listAllKafkaVersions() { return Result.buildSuc(VersionEnum.allVersionsWithOutMax()); } @Override - public Result> listClusterVersionControlItem(Long clusterId, Integer type) { + public Result> listKafkaClusterVersionControlItem(Long clusterId, Integer type) { List allItem = versionControlService.listVersionControlItem(type); List versionItemVOS = new ArrayList<>(); + String versionStr = clusterPhyService.getVersionFromCacheFirst(clusterId); + for (VersionControlItem item : allItem){ VersionItemVO itemVO = ConvertUtil.obj2Obj(item, VersionItemVO.class); - boolean support = versionControlService.isClusterSupport(clusterId, item); + boolean support = versionControlService.isClusterSupport(versionStr, item); itemVO.setSupport(support); itemVO.setDesc(itemSupportDesc(item, support)); @@ -145,7 +157,7 @@ public class VersionControlManagerImpl implements VersionControlManager { @Override public Result> listUserMetricItem(Long clusterId, Integer type, String operator) { - Result> ret = listClusterVersionControlItem(clusterId, type); + Result> ret = listKafkaClusterVersionControlItem(clusterId, type); if(null == ret || ret.failed()){ return Result.buildFail(); } diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java index 7b6bce9a..ceb1fbff 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/AbstractMetricCollector.java @@ -1,52 +1,26 @@ package com.xiaojukeji.know.streaming.km.collector.metric; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.collector.service.CollectThreadPoolService; -import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; -import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BaseMetricEvent; import com.xiaojukeji.know.streaming.km.common.component.SpringTool; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; -import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; /** * @author didi */ -public abstract class AbstractMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog(AbstractMetricCollector.class); - - protected static final ILog METRIC_COLLECTED_LOGGER = LoggerUtil.getMetricCollectedLogger(); - - public abstract List collectKafkaMetrics(ClusterPhy clusterPhy); +public abstract class AbstractMetricCollector { + public abstract String getClusterVersion(C c); public abstract VersionItemTypeEnum collectorType(); @Autowired private CollectThreadPoolService collectThreadPoolService; - public void collectMetrics(ClusterPhy clusterPhy) { - long startTime = System.currentTimeMillis(); - - // 采集指标 - List metricsList = this.collectKafkaMetrics(clusterPhy); - - // 输出耗时信息 - LOGGER.info( - "metricType={}||clusterPhyId={}||costTimeUnitMs={}", - this.collectorType().getMessage(), clusterPhy.getId(), System.currentTimeMillis() - startTime - ); - - // 输出采集到的指标信息 - METRIC_COLLECTED_LOGGER.debug("metricType={}||clusterPhyId={}||metrics={}!", - this.collectorType().getMessage(), clusterPhy.getId(), ConvertUtil.obj2Json(metricsList) - ); - } + public abstract void collectMetrics(C c); protected FutureWaitUtil getFutureUtilByClusterPhyId(Long clusterPhyId) { return collectThreadPoolService.selectSuitableFutureUtil(clusterPhyId * 1000L + this.collectorType().getCode()); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/AbstractConnectMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/AbstractConnectMetricCollector.java new file mode 100644 index 00000000..78ca717c --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/AbstractConnectMetricCollector.java @@ -0,0 +1,50 @@ +package com.xiaojukeji.know.streaming.km.collector.metric.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + * @author didi + */ +public abstract class AbstractConnectMetricCollector extends AbstractMetricCollector { + private static final ILog LOGGER = LogFactory.getLog(AbstractConnectMetricCollector.class); + + protected static final ILog METRIC_COLLECTED_LOGGER = LoggerUtil.getMetricCollectedLogger(); + + @Autowired + private ConnectClusterService connectClusterService; + + public abstract List collectConnectMetrics(ConnectCluster connectCluster); + + @Override + public String getClusterVersion(ConnectCluster connectCluster){ + return connectClusterService.getClusterVersion(connectCluster.getId()); + } + + @Override + public void collectMetrics(ConnectCluster connectCluster) { + long startTime = System.currentTimeMillis(); + + // 采集指标 + List metricsList = this.collectConnectMetrics(connectCluster); + + // 输出耗时信息 + LOGGER.info( + "metricType={}||connectClusterId={}||costTimeUnitMs={}", + this.collectorType().getMessage(), connectCluster.getId(), System.currentTimeMillis() - startTime + ); + + // 输出采集到的指标信息 + METRIC_COLLECTED_LOGGER.debug("metricType={}||connectClusterId={}||metrics={}!", + this.collectorType().getMessage(), connectCluster.getId(), ConvertUtil.obj2Json(metricsList) + ); + } +} diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectClusterMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectClusterMetricCollector.java new file mode 100644 index 00000000..df463ea1 --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectClusterMetricCollector.java @@ -0,0 +1,83 @@ +package com.xiaojukeji.know.streaming.km.collector.metric.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionControlItem; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.connect.ConnectClusterMetricEvent; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterMetricService; +import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_CLUSTER; + +/** + * @author didi + */ +@Component +public class ConnectClusterMetricCollector extends AbstractConnectMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(ConnectClusterMetricCollector.class); + + @Autowired + private VersionControlService versionControlService; + + @Autowired + private ConnectClusterMetricService connectClusterMetricService; + + @Override + public List collectConnectMetrics(ConnectCluster connectCluster) { + Long startTime = System.currentTimeMillis(); + Long clusterPhyId = connectCluster.getKafkaClusterPhyId(); + Long connectClusterId = connectCluster.getId(); + + ConnectClusterMetrics metrics = new ConnectClusterMetrics(clusterPhyId, connectClusterId); + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); + List items = versionControlService.listVersionControlItem(getClusterVersion(connectCluster), collectorType().getCode()); + FutureWaitUtil future = this.getFutureUtilByClusterPhyId(connectClusterId); + + for (VersionControlItem item : items) { + future.runnableTask( + String.format("class=ConnectClusterMetricCollector||connectClusterId=%d||metricName=%s", connectClusterId, item.getName()), + 30000, + () -> { + try { + Result ret = connectClusterMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, item.getName()); + if (null == ret || !ret.hasData()) { + return null; + } + metrics.putMetric(ret.getData().getMetrics()); + + } catch (Exception e) { + LOGGER.error( + "method=collectConnectMetrics||connectClusterId={}||metricName={}||errMsg=exception!", + connectClusterId, item.getName(), e + ); + } + return null; + } + ); + } + + future.waitExecute(30000); + + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, (System.currentTimeMillis() - startTime) / 1000.0f); + + this.publishMetric(new ConnectClusterMetricEvent(this, Collections.singletonList(metrics))); + + return Collections.singletonList(metrics); + } + + @Override + public VersionItemTypeEnum collectorType() { + return METRIC_CONNECT_CLUSTER; + } +} diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java new file mode 100644 index 00000000..282ce870 --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java @@ -0,0 +1,102 @@ +package com.xiaojukeji.know.streaming.km.collector.metric.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionControlItem; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.connect.ConnectorMetricEvent; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_CONNECTOR; + +/** + * @author didi + */ +@Component +public class ConnectConnectorMetricCollector extends AbstractConnectMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(ConnectConnectorMetricCollector.class); + + @Autowired + private VersionControlService versionControlService; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectorMetricService connectorMetricService; + + @Override + public List collectConnectMetrics(ConnectCluster connectCluster) { + Long clusterPhyId = connectCluster.getKafkaClusterPhyId(); + Long connectClusterId = connectCluster.getId(); + + List items = versionControlService.listVersionControlItem(this.getClusterVersion(connectCluster), collectorType().getCode()); + Result> connectorList = connectorService.listConnectorsFromCluster(connectClusterId); + + FutureWaitUtil future = this.getFutureUtilByClusterPhyId(connectClusterId); + + List metricsList = new ArrayList<>(); + for (String connectorName : connectorList.getData()) { + ConnectorMetrics metrics = new ConnectorMetrics(connectClusterId, connectorName); + metrics.setClusterPhyId(clusterPhyId); + + metricsList.add(metrics); + future.runnableTask( + String.format("class=ConnectConnectorMetricCollector||connectClusterId=%d||connectorName=%s", connectClusterId, connectorName), + 30000, + () -> collectMetrics(connectClusterId, connectorName, metrics, items) + ); + } + future.waitResult(30000); + + this.publishMetric(new ConnectorMetricEvent(this, metricsList)); + + return metricsList; + } + + @Override + public VersionItemTypeEnum collectorType() { + return METRIC_CONNECT_CONNECTOR; + } + + /**************************************************** private method ****************************************************/ + + private void collectMetrics(Long connectClusterId, String connectorName, ConnectorMetrics metrics, List items) { + long startTime = System.currentTimeMillis(); + ConnectorTypeEnum connectorType = connectorService.getConnectorType(connectClusterId, connectorName); + + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); + + for (VersionControlItem v : items) { + try { + Result ret = connectorMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, connectorName, v.getName(), connectorType); + if (null == ret || ret.failed() || null == ret.getData()) { + continue; + } + + metrics.putMetric(ret.getData().getMetrics()); + } catch (Exception e) { + LOGGER.error( + "method=collectMetrics||connectClusterId={}||connectorName={}||metric={}||errMsg=exception!", + connectClusterId, connectorName, v.getName(), e + ); + } + } + + // 记录采集性能 + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, (System.currentTimeMillis() - startTime) / 1000.0f); + } +} diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/AbstractKafkaMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/AbstractKafkaMetricCollector.java new file mode 100644 index 00000000..4c995cfb --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/AbstractKafkaMetricCollector.java @@ -0,0 +1,50 @@ +package com.xiaojukeji.know.streaming.km.collector.metric.kafka; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.LoggerUtil; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + * @author didi + */ +public abstract class AbstractKafkaMetricCollector extends AbstractMetricCollector { + private static final ILog LOGGER = LogFactory.getLog(AbstractMetricCollector.class); + + protected static final ILog METRIC_COLLECTED_LOGGER = LoggerUtil.getMetricCollectedLogger(); + + @Autowired + private ClusterPhyService clusterPhyService; + + public abstract List collectKafkaMetrics(ClusterPhy clusterPhy); + + @Override + public String getClusterVersion(ClusterPhy clusterPhy){ + return clusterPhyService.getVersionFromCacheFirst(clusterPhy.getId()); + } + + @Override + public void collectMetrics(ClusterPhy clusterPhy) { + long startTime = System.currentTimeMillis(); + + // 采集指标 + List metricsList = this.collectKafkaMetrics(clusterPhy); + + // 输出耗时信息 + LOGGER.info( + "metricType={}||clusterPhyId={}||costTimeUnitMs={}", + this.collectorType().getMessage(), clusterPhy.getId(), System.currentTimeMillis() - startTime + ); + + // 输出采集到的指标信息 + METRIC_COLLECTED_LOGGER.debug("metricType={}||clusterPhyId={}||metrics={}!", + this.collectorType().getMessage(), clusterPhy.getId(), ConvertUtil.obj2Json(metricsList) + ); + } +} diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java index 1753a875..6ae2a063 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/BrokerMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics; @@ -27,7 +26,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class BrokerMetricCollector extends AbstractMetricCollector { +public class BrokerMetricCollector extends AbstractKafkaMetricCollector { private static final ILog LOGGER = LogFactory.getLog(BrokerMetricCollector.class); @Autowired @@ -44,7 +43,7 @@ public class BrokerMetricCollector extends AbstractMetricCollector brokers = brokerService.listAliveBrokersFromDB(clusterPhy.getId()); - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java index e70bf6f9..f918a0d5 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ClusterMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -25,7 +24,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class ClusterMetricCollector extends AbstractMetricCollector { +public class ClusterMetricCollector extends AbstractKafkaMetricCollector { protected static final ILog LOGGER = LogFactory.getLog(ClusterMetricCollector.class); @Autowired @@ -38,7 +37,7 @@ public class ClusterMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long startTime = System.currentTimeMillis(); Long clusterPhyId = clusterPhy.getId(); - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); ClusterMetrics metrics = new ClusterMetrics(clusterPhyId, clusterPhy.getKafkaVersion()); metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java index 3c1f0df4..5e04466f 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/GroupMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -28,7 +27,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class GroupMetricCollector extends AbstractMetricCollector { +public class GroupMetricCollector extends AbstractKafkaMetricCollector { protected static final ILog LOGGER = LogFactory.getLog(GroupMetricCollector.class); @Autowired @@ -46,7 +45,7 @@ public class GroupMetricCollector extends AbstractMetricCollector List groupNameList = new ArrayList<>(); try { - groupNameList = groupService.listGroupsFromKafka(clusterPhyId); + groupNameList = groupService.listGroupsFromKafka(clusterPhy); } catch (Exception e) { LOGGER.error("method=collectKafkaMetrics||clusterPhyId={}||msg=exception!", clusterPhyId, e); } @@ -55,7 +54,7 @@ public class GroupMetricCollector extends AbstractMetricCollector return Collections.emptyList(); } - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java index fbb710b9..30d2cf4b 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/PartitionMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -26,7 +25,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class PartitionMetricCollector extends AbstractMetricCollector { +public class PartitionMetricCollector extends AbstractKafkaMetricCollector { protected static final ILog LOGGER = LogFactory.getLog(PartitionMetricCollector.class); @Autowired @@ -42,7 +41,7 @@ public class PartitionMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List topicList = topicService.listTopicsFromCacheFirst(clusterPhyId); - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java index c042ae1d..e6c5efcd 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ReplicationMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; @@ -27,7 +26,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class ReplicaMetricCollector extends AbstractMetricCollector { +public class ReplicaMetricCollector extends AbstractKafkaMetricCollector { protected static final ILog LOGGER = LogFactory.getLog(ReplicaMetricCollector.class); @Autowired @@ -42,8 +41,8 @@ public class ReplicaMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); List partitions = partitionService.listPartitionFromCacheFirst(clusterPhyId); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java index bec9f706..3cd16a20 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/TopicMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -30,7 +29,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class TopicMetricCollector extends AbstractMetricCollector { +public class TopicMetricCollector extends AbstractKafkaMetricCollector { protected static final ILog LOGGER = LogFactory.getLog(TopicMetricCollector.class); @Autowired @@ -48,7 +47,7 @@ public class TopicMetricCollector extends AbstractMetricCollector public List collectKafkaMetrics(ClusterPhy clusterPhy) { Long clusterPhyId = clusterPhy.getId(); List topics = topicService.listTopicsFromCacheFirst(clusterPhyId); - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java index f84457c4..314bf728 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ZookeeperMetricCollector.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.collector.metric.kafka; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.metric.AbstractMetricCollector; 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.kafkacontroller.KafkaController; @@ -34,7 +33,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Component -public class ZookeeperMetricCollector extends AbstractMetricCollector { +public class ZookeeperMetricCollector extends AbstractKafkaMetricCollector { protected static final ILog LOGGER = LogFactory.getLog(ZookeeperMetricCollector.class); @Autowired @@ -53,7 +52,7 @@ public class ZookeeperMetricCollector extends AbstractMetricCollector collectKafkaMetrics(ClusterPhy clusterPhy) { Long startTime = System.currentTimeMillis(); Long clusterPhyId = clusterPhy.getId(); - List items = versionControlService.listVersionControlItem(clusterPhyId, collectorType().getCode()); + List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); List aliveZKList = zookeeperService.listFromDBByCluster(clusterPhyId) .stream() .filter(elem -> Constant.ALIVE.equals(elem.getStatus())) diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectClusterMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectClusterMetricESSender.java new file mode 100644 index 00000000..25bd7a3a --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectClusterMetricESSender.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.collector.sink.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.connect.ConnectClusterMetricEvent; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect.ConnectClusterMetricPO; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_CLUSTER_INDEX; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Component +public class ConnectClusterMetricESSender extends AbstractMetricESSender implements ApplicationListener { + protected static final ILog LOGGER = LogFactory.getLog(ConnectClusterMetricESSender.class); + + @PostConstruct + public void init(){ + LOGGER.info("class=ConnectClusterMetricESSender||method=init||msg=init finished"); + } + + @Override + public void onApplicationEvent(ConnectClusterMetricEvent event) { + send2es(CONNECT_CLUSTER_INDEX, ConvertUtil.list2List(event.getConnectClusterMetrics(), ConnectClusterMetricPO.class)); + } +} diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectorMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectorMetricESSender.java new file mode 100644 index 00000000..4234c974 --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/connect/ConnectorMetricESSender.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.collector.sink.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.connect.ConnectorMetricEvent; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect.ConnectorMetricPO; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_CONNECTOR_INDEX; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Component +public class ConnectorMetricESSender extends AbstractMetricESSender implements ApplicationListener { + protected static final ILog LOGGER = LogFactory.getLog(ConnectorMetricESSender.class); + + @PostConstruct + public void init(){ + LOGGER.info("class=ConnectorMetricESSender||method=init||msg=init finished"); + } + + @Override + public void onApplicationEvent(ConnectorMetricEvent event) { + send2es(CONNECT_CONNECTOR_INDEX, ConvertUtil.list2List(event.getConnectorMetricsList(), ConnectorMetricPO.class)); + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterConnectorsOverviewDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterConnectorsOverviewDTO.java new file mode 100644 index 00000000..75970724 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterConnectorsOverviewDTO.java @@ -0,0 +1,28 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.cluster; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationSortDTO; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + + +/** + * @author zengqiao + * @date 22/02/24 + */ +@Data +public class ClusterConnectorsOverviewDTO extends PaginationSortDTO { + @NotNull(message = "latestMetricNames不允许为空") + @ApiModelProperty("需要指标点的信息") + private List latestMetricNames; + + @NotNull(message = "metricLines不允许为空") + @ApiModelProperty("需要指标曲线的信息") + private MetricDTO metricLines; + + @ApiModelProperty("需要排序的指标名称列表,比较第一个不为空的metric") + private List sortMetricNameList; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java new file mode 100644 index 00000000..71cfcce8 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@NoArgsConstructor +@ApiModel(description = "集群Connector") +public class ClusterConnectorDTO extends BaseDTO { + @NotNull(message = "connectClusterId不允许为空") + @ApiModelProperty(value = "Connector集群ID", example = "1") + private Long connectClusterId; + + @NotBlank(message = "name不允许为空串") + @ApiModelProperty(value = "Connector名称", example = "know-streaming-connector") + private String connectorName; + + public ClusterConnectorDTO(Long connectClusterId, String connectorName) { + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/cluster/ConnectClusterDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/cluster/ConnectClusterDTO.java new file mode 100644 index 00000000..a8ca1ab2 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/cluster/ConnectClusterDTO.java @@ -0,0 +1,29 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.cluster; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@ApiModel(description = "集群Connector") +public class ConnectClusterDTO extends BaseDTO { + @ApiModelProperty(value = "Connect集群ID", example = "1") + private Long id; + + @ApiModelProperty(value = "Connect集群名称", example = "know-streaming") + private String name; + + @ApiModelProperty(value = "Connect集群URL", example = "http://127.0.0.1:8080") + private String clusterUrl; + + @ApiModelProperty(value = "Connect集群版本", example = "2.5.1") + private String version; + + @ApiModelProperty(value = "JMX配置", example = "") + private String jmxProperties; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorActionDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorActionDTO.java new file mode 100644 index 00000000..f4294d68 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorActionDTO.java @@ -0,0 +1,20 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@ApiModel(description = "操作Connector") +public class ConnectorActionDTO extends ClusterConnectorDTO { + @NotBlank(message = "action不允许为空串") + @ApiModelProperty(value = "Connector名称", example = "stop|restart|resume") + private String action; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java new file mode 100644 index 00000000..40f617c8 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.Properties; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@ApiModel(description = "修改Connector配置") +public class ConnectorConfigModifyDTO extends ClusterConnectorDTO { + @NotNull(message = "configs不允许为空") + @ApiModelProperty(value = "配置", example = "") + private Properties configs; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java new file mode 100644 index 00000000..a2272118 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.Properties; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@ApiModel(description = "创建Connector") +public class ConnectorCreateDTO extends ClusterConnectorDTO { + @NotNull(message = "configs不允许为空") + @ApiModelProperty(value = "配置", example = "") + private Properties configs; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorDeleteDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorDeleteDTO.java new file mode 100644 index 00000000..55dce017 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorDeleteDTO.java @@ -0,0 +1,14 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@ApiModel(description = "删除Connector") +public class ConnectorDeleteDTO extends ClusterConnectorDTO { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/task/TaskActionDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/task/TaskActionDTO.java new file mode 100644 index 00000000..a5d99188 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/task/TaskActionDTO.java @@ -0,0 +1,20 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.task; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorActionDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author zengqiao + * @date 2022-10-17 + */ +@Data +@ApiModel(description = "操作Task") +public class TaskActionDTO extends ConnectorActionDTO { + @NotNull(message = "taskId不允许为NULL") + @ApiModelProperty(value = "taskId", example = "123") + private Long taskId; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectClustersDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectClustersDTO.java new file mode 100644 index 00000000..7986553c --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectClustersDTO.java @@ -0,0 +1,22 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author didi + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(description = "Connect集群指标查询信息") +public class MetricsConnectClustersDTO extends MetricDTO { + @ApiModelProperty("Connect集群ID") + private List connectClusterIdList; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectorsDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectorsDTO.java new file mode 100644 index 00000000..a51700f1 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/connect/MetricsConnectorsDTO.java @@ -0,0 +1,23 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author didi + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(description = "Connector指标查询信息") +public class MetricsConnectorsDTO extends MetricDTO { + @ApiModelProperty("Connector列表") + private List connectorNameList; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java index a1e39f34..752aade0 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/broker/Broker.java @@ -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; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/metric/UserMetricConfig.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/metric/UserMetricConfig.java index e244181a..171cd68f 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/metric/UserMetricConfig.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/metric/UserMetricConfig.java @@ -1,7 +1,5 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.config.metric; -import com.xiaojukeji.know.streaming.km.common.constant.Constant; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectCluster.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectCluster.java new file mode 100644 index 00000000..a4c67bbc --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectCluster.java @@ -0,0 +1,61 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.EntityIdInterface; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class ConnectCluster implements Serializable, Comparable, EntityIdInterface { + /** + * 集群ID + */ + private Long id; + + /** + * 集群名字 + */ + private String name; + + /** + * 集群使用的消费组 + */ + private String groupName; + + /** + * 集群使用的消费组状态,也表示集群状态 + * @see com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum + */ + private Integer state; + + /** + * worker中显示的leader url信息 + */ + private String memberLeaderUrl; + + /** + * 版本信息 + */ + private String version; + + /** + * jmx配置 + * @see com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig + */ + private String jmxProperties; + + /** + * Kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * 集群地址 + */ + private String clusterUrl; + + @Override + public int compareTo(ConnectCluster connectCluster) { + return this.id.compareTo(connectCluster.getId()); + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectClusterMetadata.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectClusterMetadata.java new file mode 100644 index 00000000..b3243756 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectClusterMetadata.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect; + +import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class ConnectClusterMetadata implements Serializable { + /** + * Kafka集群名字 + */ + private Long kafkaClusterPhyId; + + /** + * 集群使用的消费组 + */ + private String groupName; + + /** + * 集群使用的消费组状态,也表示集群状态 + */ + private GroupStateEnum state; + + /** + * worker中显示的leader url信息 + */ + private String memberLeaderUrl; + + public ConnectClusterMetadata(Long kafkaClusterPhyId, String groupName, GroupStateEnum state, String memberLeaderUrl) { + this.kafkaClusterPhyId = kafkaClusterPhyId; + this.groupName = groupName; + this.state = state; + this.memberLeaderUrl = memberLeaderUrl; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java new file mode 100644 index 00000000..69a4f747 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java @@ -0,0 +1,87 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.net.URI; + +@Data +@NoArgsConstructor +public class ConnectWorker implements Serializable { + + protected static final ILog LOGGER = LogFactory.getLog(ConnectWorker.class); + + /** + * Kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * 集群ID + */ + private Long connectClusterId; + + /** + * 成员ID + */ + private String memberId; + + /** + * 主机 + */ + private String host; + + /** + * Jmx端口 + */ + private Integer jmxPort; + + /** + * URL + */ + private String url; + + /** + * leader的URL + */ + private String leaderUrl; + + /** + * 1:是leader,0:不是leader + */ + private Integer leader; + + /** + * worker地址 + */ + private String workerId; + + public ConnectWorker(Long kafkaClusterPhyId, + Long connectClusterId, + String memberId, + String host, + Integer jmxPort, + String url, + String leaderUrl, + Integer leader) { + this.kafkaClusterPhyId = kafkaClusterPhyId; + this.connectClusterId = connectClusterId; + this.memberId = memberId; + this.host = host; + this.jmxPort = jmxPort; + this.url = url; + this.leaderUrl = leaderUrl; + this.leader = leader; + String workerId = CommonUtils.getWorkerId(url); + if (workerId == null) { + workerId = memberId; + LOGGER.error("class=ConnectWorker||connectClusterId={}||memberId={}||url={}||msg=analysis url fail" + , connectClusterId, memberId, url); + } + this.workerId = workerId; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/WorkerConnector.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/WorkerConnector.java new file mode 100644 index 00000000..423e21ce --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/WorkerConnector.java @@ -0,0 +1,58 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class WorkerConnector implements Serializable { + /** + * connect集群ID + */ + private Long connectClusterId; + + /** + * kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * connector名称 + */ + private String connectorName; + + private String workerMemberId; + + /** + * 任务状态 + */ + private String state; + + /** + * 任务ID + */ + private Integer taskId; + + /** + * worker信息 + */ + private String workerId; + + /** + * 错误原因 + */ + private String trace; + + public WorkerConnector(Long kafkaClusterPhyId, Long connectClusterId, String connectorName, String workerMemberId, Integer taskId, String state, String workerId, String trace) { + this.kafkaClusterPhyId = kafkaClusterPhyId; + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.workerMemberId = workerMemberId; + this.taskId = taskId; + this.state = state; + this.workerId = workerId; + this.trace = trace; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfo.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfo.java new file mode 100644 index 00000000..ffe8c332 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfo.java @@ -0,0 +1,19 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigInfo; + + +/** + * @see ConfigInfo + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigInfo { + private ConnectConfigKeyInfo definition; + + private ConnectConfigValueInfo value; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java new file mode 100644 index 00000000..bb8b773f --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java @@ -0,0 +1,62 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigInfo; +import org.apache.kafka.connect.runtime.rest.entities.ConfigInfos; + +import java.util.ArrayList; +import java.util.List; + +/** + * @see ConfigInfos + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigInfos { + private String name; + + private int errorCount; + + private List groups; + + private List configs; + + public ConnectConfigInfos(ConfigInfos configInfos) { + this.name = configInfos.name(); + this.errorCount = configInfos.errorCount(); + this.groups = configInfos.groups(); + + this.configs = new ArrayList<>(); + for (ConfigInfo configInfo: configInfos.values()) { + ConnectConfigKeyInfo definition = new ConnectConfigKeyInfo(); + definition.setName(configInfo.configKey().name()); + definition.setType(configInfo.configKey().type()); + definition.setRequired(configInfo.configKey().required()); + definition.setDefaultValue(configInfo.configKey().defaultValue()); + definition.setImportance(configInfo.configKey().importance()); + definition.setDocumentation(configInfo.configKey().documentation()); + definition.setGroup(configInfo.configKey().group()); + definition.setOrderInGroup(configInfo.configKey().orderInGroup()); + definition.setWidth(configInfo.configKey().width()); + definition.setDisplayName(configInfo.configKey().displayName()); + definition.setDependents(configInfo.configKey().dependents()); + + ConnectConfigValueInfo value = new ConnectConfigValueInfo(); + value.setName(configInfo.configValue().name()); + value.setValue(configInfo.configValue().value()); + value.setRecommendedValues(configInfo.configValue().recommendedValues()); + value.setErrors(configInfo.configValue().errors()); + value.setVisible(configInfo.configValue().visible()); + + ConnectConfigInfo connectConfigInfo = new ConnectConfigInfo(); + connectConfigInfo.setDefinition(definition); + connectConfigInfo.setValue(value); + + this.configs.add(connectConfigInfo); + } + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigKeyInfo.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigKeyInfo.java new file mode 100644 index 00000000..13ada833 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigKeyInfo.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigKeyInfo; + +import java.util.List; + +/** + * @see ConfigKeyInfo + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigKeyInfo { + private String name; + + private String type; + + private boolean required; + + private String defaultValue; + + private String importance; + + private String documentation; + + private String group; + + private int orderInGroup; + + private String width; + + private String displayName; + + private List dependents; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigValueInfo.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigValueInfo.java new file mode 100644 index 00000000..af2aecf5 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigValueInfo.java @@ -0,0 +1,27 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigValueInfo; + +import java.util.List; + +/** + * @see ConfigValueInfo + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigValueInfo { + private String name; + + private String value; + + private List recommendedValues; + + private List errors; + + private boolean visible; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSAbstractConnectState.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSAbstractConnectState.java new file mode 100644 index 00000000..a5525768 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSAbstractConnectState.java @@ -0,0 +1,20 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorStateInfo; + +/** + * @see ConnectorStateInfo.AbstractState + */ +@Data +public abstract class KSAbstractConnectState { + private String state; + + private String trace; + + @JSONField(name="worker_id") + @JsonProperty("worker_id") + private String workerId; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java new file mode 100644 index 00000000..b8fab0b6 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java @@ -0,0 +1,48 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class KSConnector implements Serializable { + /** + * Kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * connect集群ID + */ + private Long connectClusterId; + + /** + * connector名称 + */ + private String connectorName; + + /** + * connector类名 + */ + private String connectorClassName; + + /** + * connector类型 + */ + private String connectorType; + + /** + * 访问过的Topic列表 + */ + private String topics; + + /** + * task数 + */ + private Integer taskCount; + + /** + * 状态 + */ + private String state; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorInfo.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorInfo.java new file mode 100644 index 00000000..f1c3ed31 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorInfo.java @@ -0,0 +1,26 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector; + +import lombok.Data; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorType; +import org.apache.kafka.connect.util.ConnectorTaskId; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * copy from: + * @see org.apache.kafka.connect.runtime.rest.entities.ConnectorInfo + */ +@Data +public class KSConnectorInfo implements Serializable { + private Long connectClusterId; + + private String name; + + private Map config; + + private List tasks; + + private ConnectorType type; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorState.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorState.java new file mode 100644 index 00000000..9cd9ea74 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorState.java @@ -0,0 +1,11 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector; + +import lombok.Data; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorStateInfo; + +/** + * @see ConnectorStateInfo.ConnectorState + */ +@Data +public class KSConnectorState extends KSAbstractConnectState { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorStateInfo.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorStateInfo.java new file mode 100644 index 00000000..31d6657b --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnectorStateInfo.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector; + +import lombok.Data; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorStateInfo; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorType; + +import java.util.List; + +/** + * @see ConnectorStateInfo + */ +@Data +public class KSConnectorStateInfo { + private String name; + + private KSConnectorState connector; + + private List tasks; + + private ConnectorType type; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSTaskState.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSTaskState.java new file mode 100644 index 00000000..323291b0 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSTaskState.java @@ -0,0 +1,12 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector; + +import lombok.Data; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorStateInfo; + +/** + * @see ConnectorStateInfo.TaskState + */ +@Data +public class KSTaskState extends KSAbstractConnectState { + private int id; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/plugin/ConnectPluginBasic.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/plugin/ConnectPluginBasic.java new file mode 100644 index 00000000..82c2ad84 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/plugin/ConnectPluginBasic.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.plugin; + +import com.alibaba.fastjson.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Data +@ApiModel(description = "Connect插件信息") +@NoArgsConstructor +public class ConnectPluginBasic implements Serializable { + /** + * Json序列化时对应的字段 + */ + @JSONField(name="class") + @JsonProperty("class") + private String className; + + private String type; + + private String version; + + private String helpDocLink; + + public ConnectPluginBasic(String className, String type, String version, String helpDocLink) { + this.className = className; + this.type = type; + this.version = version; + this.helpDocLink = helpDocLink; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/group/Group.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/group/Group.java index 3b2e22e9..656924b7 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/group/Group.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/group/Group.java @@ -1,12 +1,12 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.group; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSGroupDescription; import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; import com.xiaojukeji.know.streaming.km.common.enums.group.GroupTypeEnum; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.apache.kafka.clients.admin.ConsumerGroupDescription; import java.util.ArrayList; import java.util.List; @@ -61,14 +61,14 @@ public class Group { */ private int coordinatorId; - public Group(Long clusterPhyId, String groupName, ConsumerGroupDescription groupDescription) { + public Group(Long clusterPhyId, String groupName, KSGroupDescription groupDescription) { this.clusterPhyId = clusterPhyId; - this.type = groupDescription.isSimpleConsumerGroup()? GroupTypeEnum.CONSUMER: GroupTypeEnum.CONNECTOR; + this.type = GroupTypeEnum.getTypeByProtocolType(groupDescription.protocolType()); this.name = groupName; this.state = GroupStateEnum.getByRawState(groupDescription.state()); - this.memberCount = groupDescription.members() == null? 0: groupDescription.members().size(); + this.memberCount = groupDescription.members() == null ? 0 : groupDescription.members().size(); this.topicMembers = new ArrayList<>(); this.partitionAssignor = groupDescription.partitionAssignor(); - this.coordinatorId = groupDescription.coordinator() == null? Constant.INVALID_CODE: groupDescription.coordinator().id(); + this.coordinatorId = groupDescription.coordinator() == null ? Constant.INVALID_CODE : groupDescription.coordinator().id(); } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ConnectClusterParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ConnectClusterParam.java new file mode 100644 index 00000000..2f830c55 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/cluster/ConnectClusterParam.java @@ -0,0 +1,16 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author wyb + * @date 2022/11/9 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectClusterParam extends ClusterParam{ + protected Long connectClusterId; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java new file mode 100644 index 00000000..0f5b0a75 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java @@ -0,0 +1,26 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ConnectClusterParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author wyb + * @date 2022/11/8 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectorParam extends ConnectClusterParam { + + private String connectorName; + + public ConnectorParam(Long connectClusterId, String connectorName) { + super(connectClusterId); + this.connectorName = connectorName; + } + +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectClusterMetricParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectClusterMetricParam.java new file mode 100644 index 00000000..92946c5c --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectClusterMetricParam.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.MetricParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author wyb + * @date 2022/11/1 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectClusterMetricParam extends MetricParam { + + private Long connectClusterId; + + private String metric; + +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectorMetricParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectorMetricParam.java new file mode 100644 index 00000000..6cad85eb --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/ConnectorMetricParam.java @@ -0,0 +1,29 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.MetricParam; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author wyb + * @date 2022/11/2 + */ +@Data +@NoArgsConstructor +public class ConnectorMetricParam extends MetricParam { + private Long connectClusterId; + + private String connectorName; + + private String metricName; + + private ConnectorTypeEnum connectorType; + + public ConnectorMetricParam(Long connectClusterId, String connectorName, String metricName, ConnectorTypeEnum connectorType) { + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.metricName = metricName; + this.connectorType = connectorType; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/Result.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/Result.java index bd3b8cc8..54281b40 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/Result.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/Result.java @@ -100,6 +100,13 @@ public class Result extends BaseResult { return result; } + public static Result buildFrom(Result ret) { + Result result = new Result<>(); + result.setCode(ret.getCode()); + result.setMessage(ret.getMessage()); + return result; + } + public static Result buildFrom(ValidateKafkaAddressErrorEnum errorEnum, String msg) { Result result = new Result<>(); result.setCode(errorEnum.getCode()); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/ResultStatus.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/ResultStatus.java index 252146c9..444a7940 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/ResultStatus.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/result/ResultStatus.java @@ -54,6 +54,8 @@ public enum ResultStatus { * 调用错误, [8000, 9000) */ KAFKA_OPERATE_FAILED(8010, "Kafka操作失败"), + KAFKA_CONNECTOR_OPERATE_FAILED(8011, "KafkaConnect操作失败"), + KAFKA_CONNECTOR_READ_FAILED(8012, "KafkaConnect读失败"), MYSQL_OPERATE_FAILED(8020, "MySQL操作失败"), ZK_OPERATE_FAILED(8030, "ZK操作失败"), ZK_FOUR_LETTER_CMD_FORBIDDEN(8031, "ZK四字命令被禁止"), diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionConnectJmxInfo.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionConnectJmxInfo.java new file mode 100644 index 00000000..7c4e257a --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionConnectJmxInfo.java @@ -0,0 +1,13 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.version; + +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; +import lombok.Data; + +/** + * @author wyb + * @date 2022/11/24 + */ +@Data +public class VersionConnectJmxInfo extends VersionJmxInfo{ + private ConnectorTypeEnum type; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionControlItem.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionControlItem.java index f0af86df..56b9169c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionControlItem.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/version/VersionControlItem.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.version; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/Znode.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/Znode.java index fd25df57..24868a93 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/Znode.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/Znode.java @@ -1,7 +1,5 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper; - -import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.apache.zookeeper.data.Stat; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/cluster/connect/ConnectClusterLoadChangedEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/cluster/connect/ConnectClusterLoadChangedEvent.java new file mode 100644 index 00000000..659218c6 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/cluster/connect/ConnectClusterLoadChangedEvent.java @@ -0,0 +1,27 @@ +package com.xiaojukeji.know.streaming.km.common.bean.event.cluster.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Getter +public class ConnectClusterLoadChangedEvent extends ApplicationEvent { + + private ConnectCluster inDBConnectCluster; + + private ConnectCluster inCacheConnectCluster; + + private final OperationEnum operationEnum; + + public ConnectClusterLoadChangedEvent(Object source, ConnectCluster inDBConnectCluster, ConnectCluster inCacheConnectCluster, OperationEnum operationEnum) { + super(source); + this.inDBConnectCluster = inDBConnectCluster; + this.inCacheConnectCluster = inCacheConnectCluster; + this.operationEnum = operationEnum; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectClusterMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectClusterMetricEvent.java new file mode 100644 index 00000000..2e6101d1 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectClusterMetricEvent.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.event.metric.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BaseMetricEvent; +import lombok.Getter; + +import java.util.List; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Getter +public class ConnectClusterMetricEvent extends BaseMetricEvent { + private List connectClusterMetrics; + + public ConnectClusterMetricEvent(Object source, List connectClusterMetrics) { + super(source); + this.connectClusterMetrics = connectClusterMetrics; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectorMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectorMetricEvent.java new file mode 100644 index 00000000..23de77c0 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/connect/ConnectorMetricEvent.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.event.metric.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BaseMetricEvent; +import lombok.Getter; + +import java.util.List; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Getter +public class ConnectorMetricEvent extends BaseMetricEvent { + private List connectorMetricsList; + + public ConnectorMetricEvent(Object source, List connectorMetricsList) { + super(source); + this.connectorMetricsList = connectorMetricsList; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectClusterPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectClusterPO.java new file mode 100644 index 00000000..f0a364e6 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectClusterPO.java @@ -0,0 +1,51 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.connect; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import lombok.Data; + +@Data +@TableName(Constant.MYSQL_KC_TABLE_NAME_PREFIX + "connect_cluster") +public class ConnectClusterPO extends BasePO { + /** + * Kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * 集群名字 + */ + private String name; + + /** + * 集群使用的消费组 + */ + private String groupName; + + /** + * 集群使用的消费组状态,也表示集群状态 + */ + private Integer state; + + /** + * 集群地址 + */ + private String clusterUrl; + + /** + * worker中显示的leader url信息 + */ + private String memberLeaderUrl; + + /** + * 版本信息 + */ + private String version; + + /** + * jmx配置 + * @see com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig + */ + private String jmxProperties; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectWorkerPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectWorkerPO.java new file mode 100644 index 00000000..5eb9a9ef --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectWorkerPO.java @@ -0,0 +1,55 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.connect; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import lombok.Data; + +@Data +@TableName(Constant.MYSQL_KC_TABLE_NAME_PREFIX + "worker") +public class ConnectWorkerPO extends BasePO { + /** + * Kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * 集群ID + */ + private Long connectClusterId; + + /** + * 成员ID + */ + private String memberId; + + /** + * 主机 + */ + private String host; + + /** + * Jmx端口 + */ + private Integer jmxPort; + + /** + * URL + */ + private String url; + + /** + * leader的URL + */ + private String leaderUrl; + + /** + * 1:是leader,0:不是leader + */ + private Integer leader; + + /** + * worker地址 + */ + private String workerId; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java new file mode 100644 index 00000000..1853deef --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java @@ -0,0 +1,50 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.connect; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import lombok.Data; + +@Data +@TableName(Constant.MYSQL_KC_TABLE_NAME_PREFIX + "connector") +public class ConnectorPO extends BasePO { + /** + * Kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * connect集群ID + */ + private Long connectClusterId; + + /** + * connector名称 + */ + private String connectorName; + + /** + * connector类名 + */ + private String connectorClassName; + + /** + * connector类型 + */ + private String connectorType; + + /** + * 访问过的Topic列表 + */ + private String topics; + + /** + * task数 + */ + private Integer taskCount; + + /** + * 状态 + */ + private String state; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/WorkerConnectorPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/WorkerConnectorPO.java new file mode 100644 index 00000000..f30d0ab6 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/WorkerConnectorPO.java @@ -0,0 +1,45 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.connect; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import lombok.Data; + +@Data +@TableName(Constant.MYSQL_KC_TABLE_NAME_PREFIX + "worker_connector") +public class WorkerConnectorPO extends BasePO { + /** + * connect集群ID + */ + private Long connectClusterId; + + /** + * kafka集群ID + */ + private Long kafkaClusterPhyId; + + /** + * connector名称 + */ + private String connectorName; + + /** + * worker成员ID + */ + private String workerMemberId; + + /** + * 任务ID + */ + private Integer taskId; + + /** + * task状态 + */ + private String state; + + /** + * worker信息 + */ + private String workerId; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicCombineExistVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicCombineExistVO.java new file mode 100644 index 00000000..4cb919a7 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicCombineExistVO.java @@ -0,0 +1,18 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 集群的Connect集群信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Connect集群基本信息") +public class ConnectClusterBasicCombineExistVO extends ConnectClusterBasicVO { + @ApiModelProperty(value="是否存在", example = "true") + protected Boolean exist; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicVO.java new file mode 100644 index 00000000..53bdc0ed --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectClusterBasicVO.java @@ -0,0 +1,44 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 集群的Connect集群信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Connect集群基本信息") +public class ConnectClusterBasicVO extends BaseVO { + @ApiModelProperty(value = "Connect集群ID", example = "1") + private Long id; + + @ApiModelProperty(value = "Connect集群名称", example = "know-streaming") + private String name; + + @ApiModelProperty(value = "Connect集群使用的Group", example = "know-streaming") + private String groupName; + + @ApiModelProperty(value = "Connect集群URL", example = "http://127.0.0.1:8080") + private String clusterUrl; + + @ApiModelProperty(value = "Connect集群获取到的URL", example = "http://127.0.0.1:8080") + private String memberLeaderUrl; + + @ApiModelProperty(value = "Connect集群版本", example = "2.5.1") + private String version; + + @ApiModelProperty(value = "JMX配置", example = "") + private String jmxProperties; + + /** + * 集群使用的消费组状态,也表示集群状态 + * @see com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum + */ + @ApiModelProperty(value = "状态,2表示Dead,只有Dead才可以删除", example = "") + private Integer state; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectStateVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectStateVO.java new file mode 100644 index 00000000..e94b0b28 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connect/ConnectStateVO.java @@ -0,0 +1,42 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 集群Connectors状态信息 + * @author zengqiao + * @date 22/10/17 + */ +@Data +@ApiModel(description = "集群Connects状态信息") +public class ConnectStateVO extends BaseVO { + @ApiModelProperty(value = "健康检查状态", example = "1") + private Integer healthState; + + @ApiModelProperty(value = "健康检查通过数", example = "1") + private Integer healthCheckPassed; + + @ApiModelProperty(value = "健康检查总数", example = "1") + private Integer healthCheckTotal; + + @ApiModelProperty(value = "connect集群数", example = "1") + private Integer connectClusterCount; + + @ApiModelProperty(value = "worker数", example = "1") + private Integer workerCount; + + @ApiModelProperty(value = "总Connector数", example = "1") + private Integer totalConnectorCount; + + @ApiModelProperty(value = "存活Connector数", example = "1") + private Integer aliveConnectorCount; + + @ApiModelProperty(value = "总Task数", example = "1") + private Integer totalTaskCount; + + @ApiModelProperty(value = "存活Task数", example = "1") + private Integer aliveTaskCount; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterConnectorOverviewVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterConnectorOverviewVO.java new file mode 100644 index 00000000..8533eec1 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterConnectorOverviewVO.java @@ -0,0 +1,42 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 集群Connector信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Connector概览信息") +public class ClusterConnectorOverviewVO extends ConnectorBasicVO { + @ApiModelProperty(value = "Connector插件名称", example = "know-streaming") + private String connectorClassName; + + @ApiModelProperty(value = "Connector类型", example = "source") + private String connectorType; + + /** + * @see org.apache.kafka.connect.runtime.AbstractStatus.State + */ + @ApiModelProperty(value = "状态", example = "RUNNING") + private String state; + + @ApiModelProperty(value = "Task数", example = "100") + private Integer taskCount; + + @ApiModelProperty(value = "访问的Topic列表", example = "") + private List topicNameList; + + @ApiModelProperty(value = "多个指标的当前值, 包括健康分/LogSize等") + private BaseMetrics latestMetrics; + + @ApiModelProperty(value = "多个指标的历史曲线值,包括LogSize/BytesIn等") + private List metricLines; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterWorkerOverviewVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterWorkerOverviewVO.java new file mode 100644 index 00000000..c14eb4e2 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ClusterWorkerOverviewVO.java @@ -0,0 +1,31 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 集群Worker信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Worker概览信息") +public class ClusterWorkerOverviewVO extends BaseVO { + @ApiModelProperty(value = "Connect集群ID", example = "1") + private Long connectClusterId; + + @ApiModelProperty(value = "Connect集群名称", example = "know-streaming") + private String connectClusterName; + + @ApiModelProperty(value = "worker主机", example = "know-streaming") + private String workerHost; + + @ApiModelProperty(value = "Connector数", example = "10") + private Integer connectorCount; + + @ApiModelProperty(value = "Task数", example = "10") + private Integer taskCount; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicCombineExistVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicCombineExistVO.java new file mode 100644 index 00000000..0bf6042f --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicCombineExistVO.java @@ -0,0 +1,18 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 集群Connector信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Connector基本信息") +public class ConnectorBasicCombineExistVO extends ConnectorBasicVO { + @ApiModelProperty(value="是否存在", example = "true") + protected Boolean exist; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicVO.java new file mode 100644 index 00000000..19217668 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/connector/ConnectorBasicVO.java @@ -0,0 +1,25 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 集群Connector信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Connector基本信息") +public class ConnectorBasicVO extends BaseVO { + @ApiModelProperty(value = "Connect集群ID", example = "1") + private Long connectClusterId; + + @ApiModelProperty(value = "Connect集群名称", example = "know-streaming") + private String connectClusterName; + + @ApiModelProperty(value = "Connector名称", example = "know-streaming") + private String connectorName; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/connector/ConnectorStateVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/connector/ConnectorStateVO.java new file mode 100644 index 00000000..7176efd2 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/connector/ConnectorStateVO.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.connector; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author wyb + * @date 2022/11/15 + */ +@Data +public class ConnectorStateVO { + @ApiModelProperty(value = "connect集群ID", example = "1") + private Long connectClusterId; + + @ApiModelProperty(value = "connector名称", example = "input1") + private String name; + + @ApiModelProperty(value = "connector类型", example = "source") + private String type; + + @ApiModelProperty(value = "connector状态", example = "running") + private String state; + + @ApiModelProperty(value = "总Task数", example = "1") + private Integer totalTaskCount; + + @ApiModelProperty(value = "存活Task数", example = "1") + private Integer aliveTaskCount; + + @ApiModelProperty(value = "总Worker数", example = "1") + private Integer totalWorkerCount; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfoVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfoVO.java new file mode 100644 index 00000000..172ecdc3 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfoVO.java @@ -0,0 +1,19 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigInfo; + + +/** + * @see ConfigInfo + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigInfoVO { + private ConnectConfigKeyInfoVO definition; + + private ConnectConfigValueInfoVO value; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfosVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfosVO.java new file mode 100644 index 00000000..200a32fd --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigInfosVO.java @@ -0,0 +1,25 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigInfos; + +import java.util.List; + +/** + * @see ConfigInfos + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigInfosVO { + private String name; + + private int errorCount; + + private List groups; + + private List configs; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigKeyInfoVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigKeyInfoVO.java new file mode 100644 index 00000000..424cb344 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigKeyInfoVO.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigKeyInfo; + +import java.util.List; + +/** + * @see ConfigKeyInfo + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigKeyInfoVO { + private String name; + + private String type; + + private boolean required; + + private String defaultValue; + + private String importance; + + private String documentation; + + private String group; + + private int orderInGroup; + + private String width; + + private String displayName; + + private List dependents; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigValueInfoVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigValueInfoVO.java new file mode 100644 index 00000000..6363cca0 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectConfigValueInfoVO.java @@ -0,0 +1,27 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.kafka.connect.runtime.rest.entities.ConfigValueInfo; + +import java.util.List; + +/** + * @see ConfigValueInfo + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ConnectConfigValueInfoVO { + private String name; + + private String value; + + private List recommendedValues; + + private List errors; + + private boolean visible; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectPluginBasicVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectPluginBasicVO.java new file mode 100644 index 00000000..1b9c811b --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/plugin/ConnectPluginBasicVO.java @@ -0,0 +1,26 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Data +@ApiModel(description = "Connect插件信息") +public class ConnectPluginBasicVO extends BaseVO { + @ApiModelProperty(value = "指标或操作项名称", example = "org.apache.kafka.connect.file.FileStreamSinkConnector") + private String className; + + @ApiModelProperty(value = "类型", example = "source|sink") + private String type; + + @ApiModelProperty(value = "版本", example = "2.5.1") + private String version; + + @ApiModelProperty(value = "帮助文档地址", example = "") + private String helpDocLink; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/task/KCTaskOverviewVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/task/KCTaskOverviewVO.java new file mode 100644 index 00000000..c82b0ed4 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/connect/task/KCTaskOverviewVO.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * Task信息概览 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "Task信息概览") +public class KCTaskOverviewVO extends BaseVO { + + @ApiModelProperty(value = "connect集群ID", example = "1") + private Long connectClusterId; + + @ApiModelProperty(value = "taskId", example = "1") + private Integer taskId; + + @ApiModelProperty(value = "worker地址", example = "127.0.0.1:8080") + private String workerId; + + @ApiModelProperty(value = "task状态", example = "RUNNING") + private String state; + + @ApiModelProperty(value = "错误原因", example = "asx") + private String trace; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthCheckConfigVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthCheckConfigVO.java index c9857c56..cb889444 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthCheckConfigVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthCheckConfigVO.java @@ -20,6 +20,9 @@ public class HealthCheckConfigVO { @ApiModelProperty(value="检查维度名称", example = "Broker") private String dimensionName; + @ApiModelProperty(value="检查维度前端展示名称", example = "Connector") + private String dimensionDisplayName; + @ApiModelProperty(value="配置组", example = "HEALTH") private String configGroup; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java index 113a74ab..6bb0b584 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java @@ -21,6 +21,9 @@ public class HealthScoreBaseResultVO extends BaseTimeVO { @ApiModelProperty(value="检查维度名称", example = "cluster") private String dimensionName; + @ApiModelProperty(value="检查维度前端显示名称", example = "cluster") + private String dimensionDisplayName; + @ApiModelProperty(value="检查名称", example = "Group延迟") private String configName; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTemplateConfig.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTemplateConfig.java index 12019702..332b6776 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTemplateConfig.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTemplateConfig.java @@ -131,13 +131,22 @@ public class RestTemplateConfig { } } catch (Exception e) { RESP_LOGGER.warn( - "method=traceResponse||code={}||url={}||text={}||headers={}||body={}||error={}||timeCost={}||subFlag={}", - response.getStatusCode(), url, response.getStatusText(), response.getHeaders(), - inputStringBuilder.toString(), e, (System.nanoTime() - nanoTime) / 1000 / 1000, subFlag); + "method=traceResponse||remoteResponse||code={}||url={}||text={}||headers={}||body={}||error={}||timeCost={}||subFlag={}", + response.getStatusCode(), + url, + response.getStatusText(), + response.getHeaders(), + inputStringBuilder.toString(), + e, + (System.nanoTime() - nanoTime) / 1000 / 1000, + subFlag + ); + if (!response.getStatusCode().is2xxSuccessful()) { - throw new ThirdPartRemoteException(e.getMessage(), e, ResultStatus.HTTP_REQ_ERROR); + throw new ThirdPartRemoteException(getResponseBodyAndIgnoreException(response), e, ResultStatus.HTTP_REQ_ERROR); } } + String responseString = inputStringBuilder.toString().replace("\n", ""); responseString = responseString.substring(0, Math.min(responseString.length(), 5000)); @@ -172,6 +181,19 @@ public class RestTemplateConfig { } + private String getResponseBodyAndIgnoreException(ClientHttpResponse response) { + try { + byte[] bytes = new byte[response.getBody().available()]; + response.getBody().read(bytes); + + return new String(bytes); + } catch (Exception e) { + // ignore + } + + return ""; + } + private static String simpleUrl(HttpRequest request) { String url = request.getURI().toString(); int index = url.indexOf("?"); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTool.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTool.java index 7ee45ef5..058e4540 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTool.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/component/RestTool.java @@ -13,6 +13,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.lang.reflect.Type; +import java.util.List; import java.util.Map; /** @@ -22,7 +23,6 @@ import java.util.Map; */ @Component public class RestTool { - private static final ILog LOGGER = LogFactory.getLog(RestTool.class); @Autowired @@ -38,39 +38,38 @@ public class RestTool { * @return */ public T postObjectWithRawContent(String url, Object postBody, HttpHeaders headers, Class resultType) { - ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(postBody, headers), - String.class); + ResponseEntity result = restTemplate.exchange( + url, + HttpMethod.POST, + new HttpEntity<>(postBody, headers), + String.class + ); + return ConvertUtil.toObj(result.getBody(), resultType); } /** * POST请求 * @param url 请求地址 - * @param request 请求内容 - * @param responseType 期望返回的类型 + * @param postBody 请求内容 + * @param resultType 期望返回的类型 * @param 泛型T * @return T */ - public T postObjectWithJsonContent(String url, Object request, Type responseType) { - HttpHeaders jsonHead = getJsonContentHeaders(); - ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST, - new HttpEntity( ConvertUtil.obj2Json(request), jsonHead), String.class); - return ConvertUtil.toObj(result.getBody(), responseType); + public T postObjectWithJsonContent(String url, Object postBody, Class resultType) { + return this.postObjectWithRawContent(url, postBody, this.getJsonContentHeaders(), resultType); } /** * POST请求 * @param url 请求地址 - * @param request 请求内容 - * @param responseType 期望返回的类型 + * @param postBody 请求内容 + * @param resultType 期望返回的类型 * @param 泛型T * @return T */ - public T postObjectWithJsonContentAndHeader(String url, Map headers, Object request, - Type responseType) { - ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST, - new HttpEntity(ConvertUtil.obj2Json(request), getJsonContentHeaders(headers)), String.class); - return ConvertUtil.toObj(result.getBody(), responseType); + public T postObjectWithJsonContentAndHeader(String url, Map headers, Object postBody, Class resultType) { + return this.postObjectWithRawContent(url, postBody, this.getJsonContentHeaders(headers), resultType); } /** @@ -81,8 +80,15 @@ public class RestTool { * @param 泛型T * @return T */ - public T getObjectWithJsonContent(String url, Map params, Type resultType) { - ResponseEntity result = restTemplate.exchange(url, HttpMethod.GET, null, String.class, params); + public T getObjectWithJsonContent(String url, Map params, Class resultType) { + ResponseEntity result = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class, + params + ); + return ConvertUtil.toObj(result.getBody(), resultType); } @@ -95,8 +101,13 @@ public class RestTool { * @return T */ public T getForObject(String url, HttpHeaders headers, Type resultType) { - ResponseEntity result = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, headers), - String.class); + ResponseEntity result = restTemplate.exchange( + url, + HttpMethod.GET, + new HttpEntity<>(null, headers), + String.class + ); + return ConvertUtil.toObj(result.getBody(), resultType); } @@ -169,6 +180,26 @@ public class RestTool { return result.getBody(); } + /** + * GET请求 + * @param url 请求地址 + * @param params 请求参数 + * @param resultType 返回类型 + * @param 泛型T + * @return T + */ + public List getArrayObjectWithJsonContent(String url, Map params, Class resultType) { + ResponseEntity result = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class, + params + ); + + return ConvertUtil.str2ObjArrayByJson(result.getBody(), resultType); + } + /** * 根据map中的参数构建url+queryString * @param url 请求地址 @@ -181,7 +212,6 @@ public class RestTool { } UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url); - return builder.toUriString(); } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java index 4b514998..332f639c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java @@ -12,6 +12,8 @@ public class ApiPrefix { public static final String API_V3_PREFIX = API_PREFIX + "v3/"; + public static final String API_V3_CONNECT_PREFIX = API_V3_PREFIX + "kafka-connect/"; + public static final String API_V3_OPEN_PREFIX = API_V3_PREFIX + "open/"; private ApiPrefix() { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java index 6cfdf338..df7ecce3 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java @@ -45,6 +45,7 @@ public class Constant { public static final int INVALID_CODE = -1; public static final String MYSQL_TABLE_NAME_PREFIX = "ks_km_"; + public static final String MYSQL_KC_TABLE_NAME_PREFIX = "ks_kc_"; public static final String SWAGGER_API_TAG_PREFIX = "KS-KM-"; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java index 9ab0bad1..b7d6ffaf 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java @@ -43,6 +43,8 @@ public class KafkaConstant { public static final String CONTROLLER_ROLE = "controller"; + public static final String DEFAULT_CONNECT_VERSION = "2.5.0"; + public static final Map KAFKA_ALL_CONFIG_DEF_MAP = new ConcurrentHashMap<>(); static { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java index 9072810d..768ebddf 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java @@ -95,4 +95,19 @@ public class MsgConstant { public static String getJobNotExist(Long jobId) { return String.format("jobId:[%d] 不存在", jobId); } + + + /**************************************************** Connect-Cluster ****************************************************/ + + public static String getConnectClusterBizStr(Long clusterId, String clusterName){ + return String.format("Connect集群ID:[%d] 集群名称:[%s]", clusterId, clusterName); + } + + public static String getConnectClusterNotExist(Long clusterId) { + return String.format("Connect集群ID:[%d] 不存在或者未加载", clusterId); + } + + public static String getConnectorBizStr(Long clusterPhyId, String topicName) { + return String.format("Connect集群ID:[%d] Connector名称:[%s]", clusterPhyId, topicName); + } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java new file mode 100644 index 00000000..5746bfd9 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java @@ -0,0 +1,15 @@ +package com.xiaojukeji.know.streaming.km.common.constant.connect; + +/** + * @author zengqiao + * @date 20/5/20 + */ +public class KafkaConnectConstant { + public static final String CONNECTOR_CLASS_FILED_NAME = "connector.class"; + + public static final String CONNECTOR_TOPICS_FILED_NAME = "topics"; + public static final String CONNECTOR_TOPICS_FILED_ERROR_VALUE = "know-streaming-connect-illegal-value"; + + private KafkaConnectConstant() { + } +} \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java new file mode 100644 index 00000000..387c8469 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java @@ -0,0 +1,142 @@ +package com.xiaojukeji.know.streaming.km.common.converter; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorStateInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect.ConnectClusterBasicCombineExistVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterConnectorOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicCombineExistVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; +import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ConnectConverter { + public static ConnectorBasicCombineExistVO convert2BasicVO(ConnectCluster connectCluster, ConnectorPO connectorPO) { + ConnectorBasicCombineExistVO vo = new ConnectorBasicCombineExistVO(); + if (connectCluster == null || connectorPO == null) { + vo.setExist(false); + return vo; + } + + vo.setExist(true); + vo.setConnectClusterId(connectorPO.getConnectClusterId()); + vo.setConnectClusterName(connectCluster.getName()); + vo.setConnectorName(connectorPO.getConnectorName()); + + return vo; + } + + public static List convert2BasicVOList( + List clusterList, + List poList) { + Map clusterMap = new HashMap<>(); + clusterList.stream().forEach(elem -> clusterMap.put(elem.getId(), elem)); + + List voList = new ArrayList<>(); + poList.stream().filter(item -> clusterMap.containsKey(item.getConnectClusterId())).forEach(elem -> { + ConnectorBasicVO vo = new ConnectorBasicVO(); + vo.setConnectClusterId(elem.getConnectClusterId()); + vo.setConnectClusterName(clusterMap.get(elem.getConnectClusterId()).getName()); + vo.setConnectorName(elem.getConnectorName()); + + voList.add(vo); + }); + + return voList; + } + + public static ConnectClusterBasicCombineExistVO convert2ConnectClusterBasicCombineExistVO(ConnectCluster connectCluster) { + if (connectCluster == null) { + ConnectClusterBasicCombineExistVO combineExistVO = new ConnectClusterBasicCombineExistVO(); + combineExistVO.setExist(false); + + return combineExistVO; + } + + ConnectClusterBasicCombineExistVO combineExistVO = ConvertUtil.obj2Obj(connectCluster, ConnectClusterBasicCombineExistVO.class); + combineExistVO.setExist(true); + return combineExistVO; + } + + public static List convert2ClusterConnectorOverviewVOList(List clusterList, + List poList, + List metricsList) { + Map clusterMap = new HashMap<>(); + clusterList.stream().forEach(elem -> clusterMap.put(elem.getId(), elem)); + + Map metricMap = metricsList.stream().collect(Collectors.toMap(elem -> elem.getConnectClusterId() + "@" + elem.getConnectorName(), Function.identity())); + + List voList = new ArrayList<>(); + poList.stream().filter(item -> clusterMap.containsKey(item.getConnectClusterId())).forEach(elem -> { + ClusterConnectorOverviewVO vo = new ClusterConnectorOverviewVO(); + vo.setConnectClusterId(elem.getConnectClusterId()); + vo.setConnectClusterName(clusterMap.get(elem.getConnectClusterId()).getName()); + vo.setConnectorName(elem.getConnectorName()); + vo.setConnectorClassName(elem.getConnectorClassName()); + vo.setConnectorType(elem.getConnectorType()); + vo.setState(elem.getState()); + vo.setTaskCount(elem.getTaskCount()); + vo.setTopicNameList(CommonUtils.string2StrList(elem.getTopics())); + vo.setLatestMetrics(metricMap.getOrDefault(elem.getConnectClusterId() + "@" + elem.getConnectorName(), new ConnectorMetrics(elem.getConnectClusterId(), elem.getConnectorName()))); + voList.add(vo); + }); + + return voList; + } + + public static List supplyData2ClusterConnectorOverviewVOList(List voList, + List metricLineVOList) { + Map> metricLineMap = new HashMap<>(); + if (metricLineVOList != null) { + for (MetricMultiLinesVO metricMultiLinesVO : metricLineVOList) { + metricMultiLinesVO.getMetricLines() + .forEach(metricLineVO -> { + String key = metricLineVO.getName(); + List metricLineVOS = metricLineMap.getOrDefault(key, new ArrayList<>()); + metricLineVOS.add(metricLineVO); + metricLineMap.put(key, metricLineVOS); + }); + } + } + + voList.forEach(elem -> { + elem.setMetricLines(metricLineMap.get(genConnectorKey(elem.getConnectClusterId(), elem.getConnectorName()))); + }); + + return voList; + } + + public static KSConnector convert2KSConnector(Long kafkaClusterPhyId, Long connectClusterId, KSConnectorInfo connectorInfo, KSConnectorStateInfo stateInfo, List topicNameList) { + KSConnector ksConnector = new KSConnector(); + ksConnector.setKafkaClusterPhyId(kafkaClusterPhyId); + ksConnector.setConnectClusterId(connectClusterId); + ksConnector.setConnectorName(connectorInfo.getName()); + ksConnector.setConnectorClassName(connectorInfo.getConfig().getOrDefault(KafkaConnectConstant.CONNECTOR_CLASS_FILED_NAME, "")); + ksConnector.setConnectorType(connectorInfo.getType().name()); + ksConnector.setTopics(topicNameList != null? CommonUtils.strList2String(topicNameList): ""); + ksConnector.setTaskCount(connectorInfo.getTasks() != null? connectorInfo.getTasks().size(): 0); + ksConnector.setState(stateInfo != null? stateInfo.getConnector().getState(): ""); + + return ksConnector; + } + + private static String genConnectorKey(Long connectorId, String connectorName){ + return connectorId + "#" + connectorName; + } + + private ConnectConverter() { + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java index e82960b1..5625c604 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/HealthScoreVOConverter.java @@ -21,6 +21,7 @@ public class HealthScoreVOConverter { HealthScoreResultDetailVO vo = new HealthScoreResultDetailVO(); vo.setDimension(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension()); vo.setDimensionName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getMessage()); + vo.setDimensionDisplayName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimensionDisplayName()); vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName()); vo.setConfigItem(healthScoreResult.getCheckNameEnum().getConfigItem()); vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc()); @@ -63,6 +64,7 @@ public class HealthScoreVOConverter { public static HealthCheckConfigVO convert2HealthCheckConfigVO(String groupName, BaseClusterHealthConfig config) { HealthCheckConfigVO vo = new HealthCheckConfigVO(); vo.setDimensionCode(config.getCheckNameEnum().getDimensionEnum().getDimension()); + vo.setDimensionDisplayName(config.getCheckNameEnum().getDimensionEnum().getDimensionDisplayName()); vo.setDimensionName(config.getCheckNameEnum().getDimensionEnum().name()); vo.setConfigGroup(groupName); vo.setConfigName(config.getCheckNameEnum().getConfigName()); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java index 8f5d5c28..0240fde1 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java @@ -1,6 +1,5 @@ package com.xiaojukeji.know.streaming.km.common.converter; -import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; @@ -14,7 +13,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiL import com.xiaojukeji.know.streaming.km.common.bean.vo.topic.TopicRecordVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.topic.partition.TopicPartitionVO; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; -import io.swagger.annotations.ApiModelProperty; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.common.header.Header; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectActionEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectActionEnum.java new file mode 100644 index 00000000..83ee2505 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectActionEnum.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.common.enums.connect; + +public enum ConnectActionEnum { + /** + * + */ + + STOP(2, "stop"), + + RESUME(3,"resume"), + + RESTART(4,"restart"), + + UNKNOWN(-1, "unknown"); + + ConnectActionEnum(int status, String value) { + this.status = status; + this.value = value; + } + + private final int status; + + private final String value; + + public int getStatus() { + return status; + } + + public String getValue() { + return value; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectorTypeEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectorTypeEnum.java new file mode 100644 index 00000000..ce275b62 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/connect/ConnectorTypeEnum.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.common.enums.connect; + +/** + * @author wyb + * @date 2022/11/25 + */ +public enum ConnectorTypeEnum { + + + UNKNOWN(-1, "unknown"), + SOURCE(1, "source"), + SINK(2, "sink"); + + private final int code; + + private final String value; + + ConnectorTypeEnum(int code, String value) { + this.code = code; + this.value = value; + } + + public static ConnectorTypeEnum getByName(String name) { + for (ConnectorTypeEnum typeEnum : ConnectorTypeEnum.values()) { + if (typeEnum.name().equals(name)) { + return typeEnum; + } + } + return UNKNOWN; + } + +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/group/GroupTypeEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/group/GroupTypeEnum.java index ebb91ea1..e4909dae 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/group/GroupTypeEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/group/GroupTypeEnum.java @@ -2,6 +2,7 @@ package com.xiaojukeji.know.streaming.km.common.enums.group; import lombok.Getter; + /** * @author wyb * @date 2022/10/11 @@ -13,12 +14,18 @@ public enum GroupTypeEnum { CONSUMER(0, "Consumer客户端的消费组"), - CONNECTOR(1, "Connector的消费组"); + CONNECTOR(1, "Connector的消费组"), + + CONNECT_CLUSTER(2, "Connect集群"); private final Integer code; private final String msg; + public static final String CONNECTOR_PROTOCOL_TYPE = "consumer"; + + public static final String CONNECT_CLUSTER_PROTOCOL_TYPE = "connect"; + GroupTypeEnum(Integer code, String msg) { this.code = code; this.msg = msg; @@ -33,4 +40,19 @@ public enum GroupTypeEnum { } return UNKNOWN; } + + public static GroupTypeEnum getTypeByProtocolType(String protocolType) { + if (protocolType == null) { + return UNKNOWN; + } + if (protocolType.isEmpty()) { + return CONSUMER; + } else if (CONNECTOR_PROTOCOL_TYPE.equals(protocolType)) { + return CONNECTOR; + } else if (CONNECT_CLUSTER_PROTOCOL_TYPE.equals(protocolType)) { + return CONNECT_CLUSTER; + } else { + return UNKNOWN; + } + } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java index d1b08181..eaa730f4 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java @@ -8,19 +8,23 @@ import lombok.Getter; */ @Getter public enum HealthCheckDimensionEnum { - UNKNOWN(-1, "未知"), + UNKNOWN(-1, "未知", "未知"), - CLUSTER(0, "Cluster"), + CLUSTER(0, "Cluster", "Cluster"), - BROKER(1, "Broker"), + BROKER(1, "Broker", "Broker"), - TOPIC(2, "Topic"), + TOPIC(2, "Topic", "Topic"), - GROUP(3, "Group"), + GROUP(3, "Group", "Group"), - ZOOKEEPER(4, "Zookeeper"), + ZOOKEEPER(4, "Zookeeper", "Zookeeper"), - MAX_VAL(100, "所有的dimension的值需要小于MAX_VAL") + CONNECT_CLUSTER(5, "ConnectCluster", "Connect"), + + CONNECTOR(6, "Connector", "Connect"), + + MAX_VAL(100, "所有的dimension的值需要小于MAX_VAL", "Ignore") ; @@ -28,9 +32,12 @@ public enum HealthCheckDimensionEnum { private final String message; - HealthCheckDimensionEnum(int dimension, String message) { + private final String dimensionDisplayName; + + HealthCheckDimensionEnum(int dimension, String message, String dimensionDisplayName) { this.dimension = dimension; this.message = message; + this.dimensionDisplayName=dimensionDisplayName; } public static HealthCheckDimensionEnum getByCode(Integer dimension) { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java index 5b294e67..2d6d4133 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java @@ -132,6 +132,33 @@ public enum HealthCheckNameEnum { false ), + CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE( + HealthCheckDimensionEnum.CONNECT_CLUSTER, + "TaskStartupFailurePercentage", + Constant.HC_CONFIG_NAME_PREFIX+"CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE", + "connect集群任务启动失败概率", + HealthCompareValueConfig.class, + false + ), + + CONNECTOR_FAILED_TASK_COUNT( + HealthCheckDimensionEnum.CONNECTOR, + "ConnectorFailedTaskCount", + Constant.HC_CONFIG_NAME_PREFIX+"CONNECTOR_FAILED_TASK_COUNT", + "connector失败状态的任务数量", + HealthCompareValueConfig.class, + false + ), + + CONNECTOR_UNASSIGNED_TASK_COUNT( + HealthCheckDimensionEnum.CONNECTOR, + "ConnectorUnassignedTaskCount", + Constant.HC_CONFIG_NAME_PREFIX+"CONNECTOR_UNASSIGNED_TASK_COUNT", + "connector未被分配的任务数量", + HealthCompareValueConfig.class, + false + ) + ; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/ModuleEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/ModuleEnum.java index 7c07718b..e7fe82c4 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/ModuleEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/ModuleEnum.java @@ -33,6 +33,9 @@ public enum ModuleEnum { KAFKA_CONTROLLER(70, "KafkaController"), + KAFKA_CONNECT_CLUSTER(80, "KafkaConnectCluster"), + KAFKA_CONNECT_CONNECTOR(81, "KafkaConnectConnector"), + PLATFORM_CONFIG(100, "平台配置"), JOB_KAFKA_REPLICA_REASSIGN(110, "Job-KafkaReplica迁移"), diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/OperationEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/OperationEnum.java index 560ff34b..302cb38b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/OperationEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/operaterecord/OperationEnum.java @@ -30,6 +30,8 @@ public enum OperationEnum { CANCEL(10, "取消"), + RESTART(11, "重启"), + ; OperationEnum(int code, String desc) { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java index 7136e114..7bcf3234 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionItemTypeEnum.java @@ -13,6 +13,10 @@ public enum VersionItemTypeEnum { METRIC_ZOOKEEPER(110, "ZookeeperMetric"), + METRIC_CONNECT_CLUSTER(120, "ConnectClusterMetric"), + METRIC_CONNECT_CONNECTOR(121, "ConnectConnectorMetric"), + METRIC_CONNECT_MIRROR_MAKER(122, "ConnectMirrorMakerMetric"), + /** * 服务端查询 */ @@ -37,6 +41,9 @@ public enum VersionItemTypeEnum { SERVICE_OP_REASSIGNMENT(330, "service_reassign_operation"), + SERVICE_OP_CONNECT_CLUSTER(400, "service_connect_cluster_operation"), + SERVICE_OP_CONNECT_CONNECTOR(401, "service_connect_connector_operation"), + SERVICE_OP_CONNECT_PLUGIN(402, "service_connect_plugin_operation"), /** * 前端操作 diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java index a9bea1c3..2a89a08c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java @@ -34,6 +34,116 @@ public class JmxAttribute { public static final String VERSION = "Version"; + /*********************************************************** connect cluster***********************************************************/ + public static final String TASK_COUNT = "task-count"; + + public static final String CONNECTOR_STARTUP_ATTEMPTS_TOTAL = "connector-startup-attempts-total"; + + public static final String CONNECTOR_STARTUP_FAILURE_PERCENTAGE = "connector-startup-failure-percentage"; + + public static final String CONNECTOR_STARTUP_FAILURE_TOTAL = "connector-startup-failure-total"; + + public static final String CONNECTOR_STARTUP_SUCCESS_PERCENTAGE = "connector-startup-success-percentage"; + + public static final String CONNECTOR_STARTUP_SUCCESS_TOTAL = "connector-startup-success-total"; + + public static final String TASK_STARTUP_ATTEMPTS_TOTAL = "task-startup-attempts-total"; + + public static final String TASK_STARTUP_FAILURE_PERCENTAGE = "task-startup-failure-percentage"; + + public static final String TASK_STARTUP_FAILURE_TOTAL = "task-startup-failure-total"; + + public static final String TASK_STARTUP_SUCCESS_PERCENTAGE = "task-startup-success-percentage"; + + public static final String TASK_STARTUP_SUCCESS_TOTAL = "task-startup-success-total"; + + /*********************************************************** connect ***********************************************************/ + public static final String CONNECTOR_TOTAL_TASK_COUNT = "connector-total-task-count"; + + public static final String CONNECTOR_RUNNING_TASK_COUNT = "connector-running-task-count"; + + public static final String CONNECTOR_PAUSED_TASK_COUNT = "connector-paused-task-count"; + + public static final String CONNECTOR_FAILED_TASK_COUNT = "connector-failed-task-count"; + + public static final String CONNECTOR_UNASSIGNED_TASK_COUNT = "connector-unassigned-task-count"; + + public static final String BATCH_SIZE_AVG = "batch-size-avg"; + + public static final String BATCH_SIZE_MAX = "batch-size-max"; + + public static final String OFFSET_COMMIT_AVG_TIME_MS = "offset-commit-avg-time-ms"; + + public static final String OFFSET_COMMIT_MAX_TIME_MS = "offset-commit-max-time-ms"; + + public static final String OFFSET_COMMIT_FAILURE_PERCENTAGE = "offset-commit-failure-percentage"; + + public static final String OFFSET_COMMIT_SUCCESS_PERCENTAGE = "offset-commit-success-percentage"; + + public static final String POLL_BATCH_AVG_TIME_MS = "poll-batch-avg-time-ms"; + + public static final String POLL_BATCH_MAX_TIME_MS = "poll-batch-max-time-ms"; + + public static final String SOURCE_RECORD_ACTIVE_COUNT = "source-record-active-count"; + + public static final String SOURCE_RECORD_ACTIVE_COUNT_AVG = "source-record-active-count-avg"; + + public static final String SOURCE_RECORD_ACTIVE_COUNT_MAX = "source-record-active-count-max"; + + public static final String SOURCE_RECORD_POLL_RATE = "source-record-poll-rate"; + + public static final String SOURCE_RECORD_POLL_TOTAL = "source-record-poll-total"; + + public static final String SOURCE_RECORD_WRITE_RATE = "source-record-write-rate"; + + public static final String SOURCE_RECORD_WRITE_TOTAL = "source-record-write-total"; + + public static final String OFFSET_COMMIT_COMPLETION_RATE = "offset-commit-completion-rate"; + + public static final String OFFSET_COMMIT_COMPLETION_TOTAL = "offset-commit-completion-total"; + + public static final String OFFSET_COMMIT_SKIP_RATE = "offset-commit-skip-rate"; + + public static final String OFFSET_COMMIT_SKIP_TOTAL = "offset-commit-skip-total"; + + public static final String PARTITION_COUNT = "partition-count"; + + public static final String PUT_BATCH_AVG_TIME_MS = "put-batch-avg-time-ms"; + + public static final String PUT_BATCH_MAX_TIME_MS = "put-batch-max-time-ms"; + + public static final String SINK_RECORD_ACTIVE_COUNT = "sink-record-active-count"; + + public static final String SINK_RECORD_ACTIVE_COUNT_AVG = "sink-record-active-count-avg"; + + public static final String SINK_RECORD_ACTIVE_COUNT_MAX = "sink-record-active-count-max"; + + public static final String SINK_RECORD_LAG_MAX = "sink-record-lag-max"; + + public static final String SINK_RECORD_READ_RATE = "sink-record-read-rate"; + + public static final String SINK_RECORD_READ_TOTAL = "sink-record-read-total"; + + public static final String SINK_RECORD_SEND_RATE = "sink-record-send-rate"; + + public static final String SINK_RECORD_SEND_TOTAL = "sink-record-send-total"; + + public static final String DEADLETTERQUEUE_PRODUCE_FAILURES = "deadletterqueue-produce-failures"; + + public static final String DEADLETTERQUEUE_PRODUCE_REQUESTS = "deadletterqueue-produce-requests"; + + public static final String LAST_ERROR_TIMESTAMP = "last-error-timestamp"; + + public static final String TOTAL_ERRORS_LOGGED = "total-errors-logged"; + + public static final String TOTAL_RECORD_ERRORS = "total-record-errors"; + + public static final String TOTAL_RECORD_FAILURES = "total-record-failures"; + + public static final String TOTAL_RECORDS_SKIPPED = "total-records-skipped"; + + public static final String TOTAL_RETRIES = "total-retries"; + private JmxAttribute() { } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxConnectorWrap.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxConnectorWrap.java index ca7c01c4..d9cfb082 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxConnectorWrap.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxConnectorWrap.java @@ -28,9 +28,8 @@ import java.util.concurrent.atomic.AtomicInteger; public class JmxConnectorWrap { private static final Logger LOGGER = LoggerFactory.getLogger(JmxConnectorWrap.class); - private final Long physicalClusterId; - - private final Integer brokerId; + //jmx打印日志时的附带信息 + private final String clientLogIdent; private final Long brokerStartupTime; @@ -44,9 +43,8 @@ public class JmxConnectorWrap { private JmxConfig jmxConfig; - public JmxConnectorWrap(Long physicalClusterId, Integer brokerId, Long brokerStartupTime, String host, Integer port, JmxConfig jmxConfig) { - this.physicalClusterId = physicalClusterId; - this.brokerId = brokerId; + public JmxConnectorWrap(String clientLogIdent, Long brokerStartupTime, String host, Integer port, JmxConfig jmxConfig) { + this.clientLogIdent=clientLogIdent; this.brokerStartupTime = brokerStartupTime; this.host = host; @@ -93,7 +91,7 @@ public class JmxConnectorWrap { jmxConnector = null; } catch (IOException e) { - LOGGER.warn("close JmxConnector exception, physicalClusterId:{} brokerId:{} host:{} port:{}.", physicalClusterId, brokerId, host, port, e); + LOGGER.warn("close JmxConnector exception, clientLogIdent:{} host:{} port:{}.", clientLogIdent, host, port, e); } } @@ -176,12 +174,12 @@ public class JmxConnectorWrap { } jmxConnector = JMXConnectorFactory.connect(new JMXServiceURL(jmxUrl), environment); - LOGGER.info("JMX connect success, physicalClusterId:{} brokerId:{} host:{} port:{}.", physicalClusterId, brokerId, host, port); + LOGGER.info("JMX connect success, clientLogIdent:{} host:{} port:{}.", clientLogIdent, host, port); return true; } catch (MalformedURLException e) { - LOGGER.error("JMX url exception, physicalClusterId:{} brokerId:{} host:{} port:{} jmxUrl:{}", physicalClusterId, brokerId, host, port, jmxUrl, e); + LOGGER.error("JMX url exception, clientLogIdent:{} host:{} port:{} jmxUrl:{}", clientLogIdent, host, port, jmxUrl, e); } catch (Exception e) { - LOGGER.error("JMX connect exception, physicalClusterId:{} brokerId:{} host:{} port:{}.", physicalClusterId, brokerId, host, port, e); + LOGGER.error("JMX connect exception, clientLogIdent:{} host:{} port:{}.", clientLogIdent, host, port, e); } return false; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java index db8b3197..5e11e271 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java @@ -69,6 +69,20 @@ public class JmxName { public static final String JMX_ZK_SYNC_CONNECTS_PER_SEC = "kafka.server:type=SessionExpireListener,name=ZooKeeperSyncConnectsPerSec"; public static final String JMX_ZK_DISCONNECTORS_PER_SEC = "kafka.server:type=SessionExpireListener,name=ZooKeeperDisconnectsPerSec"; + /*********************************************************** connect ***********************************************************/ + public static final String JMX_CONNECT_WORKER_METRIC = "kafka.connect:type=connect-worker-metrics"; + + public static final String JMX_CONNECT_WORKER_CONNECTOR_METRIC = "kafka.connect:type=connect-worker-metrics,connector=%s"; + + public static final String JMX_CONNECTOR_TASK_CONNECTOR_METRIC = "kafka.connect:type=connector-task-metrics,connector=%s,task=%s"; + + public static final String JMX_CONNECTOR_SOURCE_TASK_METRICS = "kafka.connect:type=source-task-metrics,connector=%s,task=%s"; + + public static final String JMX_CONNECTOR_SINK_TASK_METRICS = "kafka.connect:type=sink-task-metrics,connector=%s,task=%s"; + + public static final String JMX_CONNECTOR_TASK_ERROR_METRICS = "kafka.connect:type=task-error-metrics,connector=%s,task=%s"; + + private JmxName() { } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java index 1c451609..f3d2b357 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java @@ -7,6 +7,7 @@ import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.ArrayList; @@ -251,4 +252,13 @@ public class CommonUtils { return true; } + + public static String getWorkerId(String url){ + try { + URI uri = new URI(url); + return uri.getHost() + ":" + uri.getPort(); + } catch (Exception e) { + return null; + } + } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java index c7fcad77..430ea9c4 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/PaginationMetricsUtil.java @@ -37,6 +37,11 @@ public class PaginationMetricsUtil { return allDataList; } + //比较metricNameList中第一个不为空的metric值。 + public static void sortMetrics(List allDataList, String metricField, List metricNameList, String defaultSortField, String sortType) { + sortMetricList(allDataList, metricField, metricNameList, defaultSortField, sortType); + } + public static List sortMetrics(List allDataList, String metricName, String defaultSortField, String sortType) { sortMetricList(allDataList, metricName, defaultSortField, sortType); @@ -151,6 +156,102 @@ public class PaginationMetricsUtil { return allDataList; } + private static List sortMetricList(List allDataList, String metricFieldName, List metricNameList, String defaultFieldName, String sortType) { + if (ValidateUtils.anyBlank(defaultFieldName, sortType) || ValidateUtils.isEmptyList(allDataList)||ValidateUtils.isEmptyList(metricNameList)) { + return allDataList; + } + + try { + Field metricField = FieldUtils.getField(allDataList.get(0).getClass(), metricFieldName, true); + Field defaultField = FieldUtils.getField(allDataList.get(0).getClass(), defaultFieldName, true); + if(ValidateUtils.anyNull(defaultField, metricField)) { + log.debug("method=sortMetrics||className={}||metricFieldName={}||metricNameList={}||defaultFieldName={}||metricSortType={}||msg=field not exist.", + allDataList.get(0).getClass().getSimpleName(), metricFieldName, metricNameList, defaultFieldName, sortType); + + // 字段不存在,则排序失效,直接返回 + return allDataList; + } + + Collections.sort(allDataList, (a1, a2) -> { + try { + Object m1 = FieldUtils.readField(a1, metricField.getName(), true); + Object m2 = FieldUtils.readField(a2, metricField.getName(), true); + + return compareFirstNotNullMetricValue((BaseMetrics)m1, (BaseMetrics)m2, metricNameList, defaultField); + } catch (Exception e) { + log.error("method=sortMetrics||className={}||metricFieldName={}||metricNameList={}||defaultFieldName={}||metricSortType={}||errMsg=exception.", + allDataList.get(0).getClass().getSimpleName(), metricFieldName, metricNameList, defaultFieldName, sortType, e); + } + + return 0; + }); + } catch (Exception e) { + log.error("method=sortMetrics||className={}||metricFieldName={}||metricNameList={}||defaultFieldName={}||metricSortType={}||errMsg=exception.", + allDataList.get(0).getClass().getSimpleName(), metricFieldName, metricNameList, defaultFieldName, sortType, e); + } + + if (!SortTypeEnum.DESC.getSortType().equals(sortType)) { + Collections.reverse(allDataList); + } + return allDataList; + } + + private static int compareFirstNotNullMetricValue(BaseMetrics a1, BaseMetrics a2, List metricNameList, Field defaultField) { + try { + // 指标数据排序 + Float m1 = null; + Float m2 = null; + + //获取第一个非空指标 + for (String metric : metricNameList) { + m1 = a1.getMetric(metric); + if (m1 != null) { + break; + } + } + for (String metric : metricNameList) { + m2 = a2.getMetric(metric); + if (m2 != null) { + break; + } + } + + if (m1 != null && m2 == null) { + return -1; + } else if (m1 == null && m2 != null) { + return 1; + } else if (m1 != null && m2 != null) { + // 两个都不为空,则进行大小比较 + int val = compareObject(m2, m1); + if (val != 0) { + return val; + } + } + + // 默认字段排序 + Object f1 = FieldUtils.readField(a1, defaultField.getName(), true); + Object f2 = FieldUtils.readField(a2, defaultField.getName(), true); + if (f1 != null && f2 != null) { + // 两个都不为空,则进行大小比较 + return compareObject(f2, f1); + } + if (f1 != null) { + return -1; + } else if (f2 != null) { + return 1; + } + + return 0; + } catch (Exception e) { + log.debug("method=sortMetricsObject||metricsA={}||metricsB={}||metricNameList={}||defaultFieldName={}||errMsg=exception.", + a1, a2, metricNameList, defaultField.getName(), e); + } + + return 0; + } + + + private static List sortMetricList(List allDataList, String metricName, String defaultSortField, String sortType) { if (ValidateUtils.anyBlank(metricName, defaultSortField, sortType) || ValidateUtils.isEmptyList(allDataList)) { return allDataList; diff --git a/km-core/pom.xml b/km-core/pom.xml index 578a0666..031b591a 100644 --- a/km-core/pom.xml +++ b/km-core/pom.xml @@ -120,5 +120,9 @@ org.apache.kafka kafka_2.13 + + org.apache.kafka + connect-runtime + \ No newline at end of file diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java index 2fc0a4ff..7b12b0dc 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java @@ -24,6 +24,17 @@ public class CollectedMetricsLocalCache { .maximumSize(10000) .build(); + private static final Cache connectClusterMetricsCache = Caffeine.newBuilder() + .expireAfterWrite(90, TimeUnit.SECONDS) + .maximumSize(10000) + .build(); + + private static final Cache connectorMetricsCache = Caffeine.newBuilder() + .expireAfterWrite(90, TimeUnit.SECONDS) + .maximumSize(10000) + .build(); + + public static Float getBrokerMetrics(String brokerMetricKey) { return brokerMetricsCache.getIfPresent(brokerMetricKey); } @@ -59,6 +70,28 @@ public class CollectedMetricsLocalCache { partitionMetricsCache.put(partitionMetricsKey, metricsList); } + public static void putConnectClusterMetrics(String connectClusterMetricKey, Float value) { + if (value == null) { + return; + } + connectClusterMetricsCache.put(connectClusterMetricKey, value); + } + + public static Float getConnectClusterMetrics(String connectClusterMetricKey) { + return connectClusterMetricsCache.getIfPresent(connectClusterMetricKey); + } + + public static void putConnectorMetrics(String connectClusterMetricKey, Float value) { + if (value == null) { + return; + } + connectorMetricsCache.put(connectClusterMetricKey, value); + } + + public static Float getConnectorMetrics(String connectClusterMetricKey) { + return connectorMetricsCache.getIfPresent(connectClusterMetricKey); + } + public static String genBrokerMetricKey(Long clusterPhyId, Integer brokerId, String metricName) { return clusterPhyId + "@" + brokerId + "@" + metricName; } @@ -71,6 +104,16 @@ public class CollectedMetricsLocalCache { return clusterPhyId + "@" + brokerId + "@" + topicName + "@" + partitionId + "@" + metricName; } + public static String genConnectClusterMetricCacheKey(Long connectClusterId, String metricName) { + return connectClusterId + "@" + metricName; + } + + public static String genConnectorMetricCacheKey(Long connectClusterId, String connectorName, String metricName) { + return connectClusterId + "@" + connectorName + '@' + metricName; + } + /**************************************************** private method ****************************************************/ + private CollectedMetricsLocalCache() { + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/KafkaAclServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/KafkaAclServiceImpl.java index 836c7d56..8f1473cd 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/KafkaAclServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/KafkaAclServiceImpl.java @@ -17,6 +17,7 @@ import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistExcept import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.acl.KafkaAclService; import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; @@ -47,7 +48,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. @Service -public class KafkaAclServiceImpl extends BaseVersionControlService implements KafkaAclService { +public class KafkaAclServiceImpl extends BaseKafkaVersionControlService implements KafkaAclService { private static final ILog log = LogFactory.getLog(KafkaAclServiceImpl.class); private static final String ACL_GET_FROM_KAFKA = "getAclFromKafka"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/OpKafkaAclServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/OpKafkaAclServiceImpl.java index 41c0bcdf..a8fab1f1 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/OpKafkaAclServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/acl/impl/OpKafkaAclServiceImpl.java @@ -19,6 +19,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.core.service.acl.OpKafkaAclService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; @@ -47,7 +48,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. @Service -public class OpKafkaAclServiceImpl extends BaseVersionControlService implements OpKafkaAclService { +public class OpKafkaAclServiceImpl extends BaseKafkaVersionControlService implements OpKafkaAclService { private static final ILog log = LogFactory.getLog(OpKafkaAclServiceImpl.class); private static final String ACL_CREATE = "createKafkaAcl"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java index ecfbfcde..f47a3fa5 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java @@ -22,6 +22,7 @@ import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistExcept import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerConfigService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; @@ -42,7 +43,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. @Service -public class BrokerConfigServiceImpl extends BaseVersionControlService implements BrokerConfigService { +public class BrokerConfigServiceImpl extends BaseKafkaVersionControlService implements BrokerConfigService { private static final ILog log = LogFactory.getLog(BrokerConfigServiceImpl.class); private static final String GET_BROKER_CONFIG = "getBrokerConfig"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java index 0bd7f364..97dc00c8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerServiceImpl.java @@ -8,8 +8,8 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.broker.BrokerParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.broker.BrokerParam; 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; @@ -26,12 +26,12 @@ import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.jmx.JmxDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; -import com.xiaojukeji.know.streaming.km.persistence.mysql.broker.BrokerDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.service.KafkaZKDAO; +import com.xiaojukeji.know.streaming.km.persistence.mysql.broker.BrokerDAO; import kafka.zk.BrokerIdsZNode; import org.apache.kafka.clients.admin.*; import org.apache.kafka.common.Node; @@ -54,7 +54,7 @@ import static com.xiaojukeji.know.streaming.km.common.jmx.JmxAttribute.VERSION; import static com.xiaojukeji.know.streaming.km.common.jmx.JmxName.JMX_SERVER_APP_INFO; @Service -public class BrokerServiceImpl extends BaseVersionControlService implements BrokerService { +public class BrokerServiceImpl extends BaseKafkaVersionControlService implements BrokerService { private static final ILog log = LogFactory.getLog(BrokerServiceImpl.class); private static final String BROKER_LOG_DIR = "getLogDir"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterValidateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterValidateServiceImpl.java index ba72d2fe..50d54553 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterValidateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterValidateServiceImpl.java @@ -16,7 +16,6 @@ import com.xiaojukeji.know.streaming.km.persistence.jmx.JmxDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.service.KafkaZKDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.service.impl.KafkaZKDAOImpl; import kafka.server.KafkaConfig; -import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.*; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.KafkaConsumer; @@ -35,7 +34,6 @@ import java.util.*; * @author zengqiao * @date 22/02/28 */ -@Slf4j @Service public class ClusterValidateServiceImpl implements ClusterValidateService { private static final ILog logger = LogFactory.getLog(KafkaZKDAOImpl.class); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ControllerChangeLogServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ControllerChangeLogServiceImpl.java index 57640e4f..5cec3851 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ControllerChangeLogServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ControllerChangeLogServiceImpl.java @@ -4,7 +4,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationSor import com.xiaojukeji.know.streaming.km.common.bean.po.ControllerChangeLogPO; import com.xiaojukeji.know.streaming.km.core.service.cluster.ControllerChangeLogService; import com.xiaojukeji.know.streaming.km.persistence.mysql.ControllerChangeLogDAO; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -12,7 +11,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -@Slf4j @Service public class ControllerChangeLogServiceImpl implements ControllerChangeLogService { diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterMetricService.java new file mode 100644 index 00000000..92ed79f0 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterMetricService.java @@ -0,0 +1,27 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.cluster; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectClustersDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; + +import java.util.List; + +/** + * @author didi + */ +public interface ConnectClusterMetricService { + + /** + * 从Kafka获取指标 + */ + Result collectConnectClusterMetricsFromKafkaWithCacheFirst(Long connectClusterPhyId, String metricName); + Result collectConnectClusterMetricsFromKafka(Long connectClusterPhyId, String metricName); + + /** + * 从ES中获取一段时间内聚合计算之后的指标线 + */ + Result> listConnectClusterMetricsFromES(Long clusterPhyId, MetricsConnectClustersDTO dto); + + boolean isMetricName(String str); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterService.java new file mode 100644 index 00000000..e6ad3929 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/ConnectClusterService.java @@ -0,0 +1,34 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.cluster; + + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.cluster.ConnectClusterDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectClusterMetadata; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; + +import java.util.List; + +/** + * Connect-Cluster + */ +public interface ConnectClusterService { + Long replaceAndReturnIdInDB(ConnectClusterMetadata metadata); + + List listByKafkaCluster(Long kafkaClusterPhyId); + + List listAllClusters(); + + ConnectCluster getById(Long connectClusterId); + + ConnectCluster getByName(Long clusterPhyId, String connectClusterName); + + String getClusterVersion(Long connectClusterId); + + String getClusterName(Long connectClusterId); + + Result deleteInDB(Long connectClusterId, String operator); + + Result batchModifyInDB(List dtoList, String operator); + + Boolean existConnectClusterDown(Long kafkaClusterPhyId); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterMetricServiceImpl.java new file mode 100644 index 00000000..5ed5af64 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterMetricServiceImpl.java @@ -0,0 +1,270 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.cluster.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectClustersDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectWorkerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.connect.ConnectClusterMetricParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionJmxInfo; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BrokerMetricPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; +import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; +import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil; +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.connect.cluster.ConnectClusterMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseConnectorMetricService; +import com.xiaojukeji.know.streaming.km.persistence.connect.ConnectJMXClient; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.ConnectClusterMetricESDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.management.ObjectName; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; + +/** + * @author didi + */ +@Service +public class ConnectClusterMetricServiceImpl extends BaseConnectorMetricService implements ConnectClusterMetricService { + protected static final ILog LOGGER = LogFactory.getLog(ConnectClusterMetricServiceImpl.class); + + public static final String CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_AVG = "getWorkerMetricAvg"; + + public static final String CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM = "getWorkerMetricSum"; + + public static final String CONNECT_CLUSTER_METHOD_DO_NOTHING = "doNothing"; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private ConnectClusterMetricESDAO connectClusterMetricESDAO; + + @Autowired + private ConnectJMXClient connectJMXClient; + + @Autowired + private WorkerService workerService; + + @Override + protected VersionItemTypeEnum getVersionItemType() { + return VersionItemTypeEnum.METRIC_CONNECT_CLUSTER; + } + + @Override + protected List listMetricPOFields() { + return BeanUtil.listBeanFields(BrokerMetricPO.class); + } + + @Override + protected void initRegisterVCHandler() { + registerVCHandler(CONNECT_CLUSTER_METHOD_DO_NOTHING, this::doNothing); + registerVCHandler(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_AVG, this::getConnectWorkerMetricAvg); + registerVCHandler(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM, this::getConnectWorkerMetricSum); + } + + @Override + public Result collectConnectClusterMetricsFromKafkaWithCacheFirst(Long connectClusterPhyId, String metric) { + String connectClusterMetricKey = CollectedMetricsLocalCache.genConnectClusterMetricCacheKey(connectClusterPhyId, metric); + Float keyValue = CollectedMetricsLocalCache.getConnectClusterMetrics(connectClusterMetricKey); + if (keyValue != null) { + ConnectClusterMetrics connectClusterMetrics = ConnectClusterMetrics.initWithMetric(connectClusterPhyId,metric,keyValue); + return Result.buildSuc(connectClusterMetrics); + } + + Result ret = this.collectConnectClusterMetricsFromKafka(connectClusterPhyId, metric); + if (ret == null || !ret.hasData()) { + return ret; + } + + Map metricsMap = ret.getData().getMetrics(); + for (Map.Entry entry : metricsMap.entrySet()) { + CollectedMetricsLocalCache.putConnectClusterMetrics(entry.getKey(), entry.getValue()); + } + return ret; + } + + @Override + public Result collectConnectClusterMetricsFromKafka( Long connectClusterPhyId, String metric) { + try { + ConnectClusterMetricParam metricParam = new ConnectClusterMetricParam(connectClusterPhyId, metric); + return (Result) doVCHandler(connectClusterPhyId, metric, metricParam); + } catch (VCHandlerNotExistException e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result> listConnectClusterMetricsFromES(Long clusterPhyId, MetricsConnectClustersDTO dto) { + Long startTime = dto.getStartTime(); + Long endTime = dto.getEndTime(); + Integer topN = dto.getTopNu(); + String aggType = dto.getAggType(); + List connectClusterIdList = dto.getConnectClusterIdList(); + List metricNameList = dto.getMetricsNames(); + + Table> retTable; + if (ValidateUtils.isEmptyList(connectClusterIdList)) { + // 按照TopN的方式去获取 + List defaultConnectClusterIdList = this.listTopNConnectClusterIdList(clusterPhyId, topN); + + retTable = connectClusterMetricESDAO.listMetricsByTop(clusterPhyId, defaultConnectClusterIdList, metricNameList, aggType, topN, startTime, endTime); + } else { + // 制定集群ID去获取 + retTable = connectClusterMetricESDAO.listMetricsByConnectClusterIdList(clusterPhyId, metricNameList, aggType, connectClusterIdList, startTime, endTime); + } + + return Result.buildSuc(this.metricMap2VO(clusterPhyId, retTable.rowMap())); + } + + @Override + public boolean isMetricName(String str) { + return super.isMetricName(str); + } + + /**************************************************** private method ****************************************************/ + private Result doNothing(VersionItemParam metricParam) { + ConnectClusterMetricParam param = (ConnectClusterMetricParam) metricParam; + return Result.buildSuc(new ConnectClusterMetrics(null, param.getConnectClusterId())); + } + + private Result getConnectWorkerMetricAvg(VersionItemParam metricParam) { + ConnectClusterMetricParam param = (ConnectClusterMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String metric = param.getMetric(); + + Result> ret = this.getConnectWorkerMetricsByJMX(connectClusterId, metric); + if (ret == null || !ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + //求均值 + Float value = ret.getData().stream().map(elem -> elem.getMetric(metric) == null ? 0 : elem.getMetric(metric)).reduce(Float::sum).get(); + ConnectClusterMetrics connectClusterMetrics = new ConnectClusterMetrics(null, connectClusterId); + connectClusterMetrics.putMetric(metric, value / ret.getData().size()); + return Result.buildSuc(connectClusterMetrics); + } + + private Result getConnectWorkerMetricSum(VersionItemParam metricParam) { + ConnectClusterMetricParam param = (ConnectClusterMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String metric = param.getMetric(); + + Result> ret = this.getConnectWorkerMetricsByJMX(connectClusterId, metric); + if (ret == null || !ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + //求和 + Float value = ret.getData().stream().map(elem -> elem.getMetric(metric) == null ? 0 : elem.getMetric(metric)).reduce(Float::sum).get(); + ConnectClusterMetrics connectClusterMetrics = new ConnectClusterMetrics(null, connectClusterId); + connectClusterMetrics.putMetric(metric, value); + return Result.buildSuc(connectClusterMetrics); + } + + //获取workermetric列表 + private Result> getConnectWorkerMetricsByJMX(Long connectClusterId, String metric) { + + List workerIdList = workerService.listFromDB(connectClusterId).stream().map(elem -> elem.getWorkerId()).collect(Collectors.toList()); + List workerMetricsList = new ArrayList<>(); + + for (String workerId : workerIdList) { + Result ret = this.getConnectWorkerMetricByJMX(connectClusterId, workerId, metric); + if (ret == null || !ret.hasData() || ret.getData().getMetric(metric) == null) { + continue; + } + workerMetricsList.add(ret.getData()); + } + return Result.buildSuc(workerMetricsList); + } + + private Result getConnectWorkerMetricByJMX(Long connectClusterId, String workerId, String metric) { + VersionJmxInfo jmxInfo = getJMXInfo(connectClusterId, metric); + if (null == jmxInfo) { + return Result.buildFailure(VC_ITEM_JMX_NOT_EXIST); + } + + JmxConnectorWrap jmxConnectorWrap = connectJMXClient.getClientWithCheck(connectClusterId, workerId); + if (ValidateUtils.isNull(jmxConnectorWrap)) { + return Result.buildFailure(VC_JMX_INIT_ERROR); + } + try { + //2、获取jmx指标 + String value = jmxConnectorWrap.getAttribute(new ObjectName(jmxInfo.getJmxObjectName()), jmxInfo.getJmxAttribute()).toString(); + ConnectWorkerMetrics connectWorkerMetrics = ConnectWorkerMetrics.initWithMetric(connectClusterId, workerId, metric, Float.valueOf(value)); + return Result.buildSuc(connectWorkerMetrics); + } catch (Exception e) { + LOGGER.error("method=getConnectWorkerMetricsByJMX||connectClusterId={}||workerId={}||metrics={}||jmx={}||msg={}", + connectClusterId, workerId, metric, jmxInfo.getJmxObjectName(), e.getClass().getName()); + return Result.buildFailure(VC_JMX_CONNECT_ERROR); + } + } + + private List listTopNConnectClusterIdList(Long clusterPhyId, Integer topN) { + List connectClusters = connectClusterService.listByKafkaCluster(clusterPhyId); + + if (CollectionUtils.isEmpty(connectClusters)) { + return new ArrayList<>(); + } + + return connectClusters.subList(0, Math.min(topN, connectClusters.size())) + .stream() + .map(b -> b.getId().longValue()) + .collect(Collectors.toList()); + } + + protected List metricMap2VO(Long connectClusterId, + Map>> map){ + List multiLinesVOS = new ArrayList<>(); + if (map == null || map.isEmpty()) { + // 如果为空,则直接返回 + return multiLinesVOS; + } + + for(String metric : map.keySet()){ + try { + MetricMultiLinesVO multiLinesVO = new MetricMultiLinesVO(); + multiLinesVO.setMetricName(metric); + + List metricLines = new ArrayList<>(); + + Map> metricPointMap = map.get(metric); + if(null == metricPointMap || metricPointMap.isEmpty()){continue;} + + for(Map.Entry> entry : metricPointMap.entrySet()){ + MetricLineVO metricLineVO = new MetricLineVO(); + metricLineVO.setName(entry.getKey().toString()); + metricLineVO.setMetricName(metric); + metricLineVO.setMetricPoints(entry.getValue()); + + metricLines.add(metricLineVO); + } + + multiLinesVO.setMetricLines(metricLines); + multiLinesVOS.add(multiLinesVO); + }catch (Exception e){ + LOGGER.error("method=metricMap2VO||connectClusterId={}||msg=exception!", connectClusterId, e); + } + } + + return multiLinesVOS; + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java new file mode 100644 index 00000000..6597b8ec --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java @@ -0,0 +1,243 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.cluster.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.didiglobal.logi.security.common.dto.oplog.OplogDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.cluster.ConnectClusterDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectClusterMetadata; +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.po.connect.ConnectClusterPO; +import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; +import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; +import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; +import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.ModuleEnum; +import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectClusterDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.interceptor.TransactionAspectSupport; + +import java.util.List; + +@Service +public class ConnectClusterServiceImpl implements ConnectClusterService { + private static final ILog LOGGER = LogFactory.getLog(ConnectClusterServiceImpl.class); + + @Autowired + private ConnectClusterDAO connectClusterDAO; + + @Autowired + private OpLogWrapService opLogWrapService; + + @Override + public Long replaceAndReturnIdInDB(ConnectClusterMetadata metadata) { + //url去斜杠 + String clusterUrl = metadata.getMemberLeaderUrl(); + if (clusterUrl.charAt(clusterUrl.length() - 1) == '/') { + clusterUrl = clusterUrl.substring(0, clusterUrl.length() - 1); + } + + ConnectClusterPO oldPO = this.getPOFromDB(metadata.getKafkaClusterPhyId(), metadata.getGroupName()); + if (oldPO == null) { + oldPO = new ConnectClusterPO(); + oldPO.setKafkaClusterPhyId(metadata.getKafkaClusterPhyId()); + oldPO.setGroupName(metadata.getGroupName()); + oldPO.setName(metadata.getGroupName()); + oldPO.setState(metadata.getState().getCode()); + oldPO.setMemberLeaderUrl(metadata.getMemberLeaderUrl()); + oldPO.setClusterUrl(clusterUrl); + oldPO.setVersion(KafkaConstant.DEFAULT_CONNECT_VERSION); + connectClusterDAO.insert(oldPO); + + oldPO = this.getPOFromDB(metadata.getKafkaClusterPhyId(), metadata.getGroupName()); + return oldPO == null? null: oldPO.getId(); + } + + oldPO.setKafkaClusterPhyId(metadata.getKafkaClusterPhyId()); + oldPO.setGroupName(metadata.getGroupName()); + oldPO.setState(metadata.getState().getCode()); + oldPO.setMemberLeaderUrl(metadata.getMemberLeaderUrl()); + if (ValidateUtils.isBlank(oldPO.getVersion())) { + oldPO.setVersion(KafkaConstant.DEFAULT_CONNECT_VERSION); + } + if (ValidateUtils.isBlank(oldPO.getClusterUrl())) { + oldPO.setClusterUrl(metadata.getMemberLeaderUrl()); + } + connectClusterDAO.updateById(oldPO); + + return oldPO.getId(); + } + + @Override + public List listByKafkaCluster(Long kafkaClusterPhyId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectClusterPO::getKafkaClusterPhyId, kafkaClusterPhyId); + + return ConvertUtil.list2List(connectClusterDAO.selectList(lambdaQueryWrapper), ConnectCluster.class); + } + + @Override + public List listAllClusters() { + List connectClusterPOList = connectClusterDAO.selectList(null); + return ConvertUtil.list2List(connectClusterPOList, ConnectCluster.class); + } + + @Override + public ConnectCluster getById(Long connectClusterId) { + return ConvertUtil.obj2Obj(connectClusterDAO.selectById(connectClusterId), ConnectCluster.class); + } + + @Override + public ConnectCluster getByName(Long clusterPhyId, String connectClusterName) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectClusterPO::getKafkaClusterPhyId, clusterPhyId); + lambdaQueryWrapper.eq(ConnectClusterPO::getName, connectClusterName); + + return ConvertUtil.obj2Obj(connectClusterDAO.selectOne(lambdaQueryWrapper), ConnectCluster.class); + } + + @Override + public String getClusterVersion(Long connectClusterId) { + ConnectClusterPO connectClusterPO = connectClusterDAO.selectById(connectClusterId); + return null != connectClusterPO ? connectClusterPO.getVersion() : ""; + } + + @Override + public String getClusterName(Long connectClusterId) { + ConnectClusterPO connectClusterPO = connectClusterDAO.selectById(connectClusterId); + return null != connectClusterPO ? connectClusterPO.getName() : ""; + } + + @Override + public Result deleteInDB(Long connectClusterId, String operator) { + ConnectCluster connectCluster = this.getById(connectClusterId); + if (connectCluster == null) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + if (!GroupStateEnum.DEAD.getCode().equals(connectCluster.getState())) { + return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FORBIDDEN, "只有集群处于Dead状态,才允许删除"); + } + + connectClusterDAO.deleteById(connectClusterId); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.DELETE.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectClusterBizStr(connectCluster.getId(), connectCluster.getName()), + ConvertUtil.obj2Json(connectCluster) + )); + + return Result.buildSuc(); + } + + @Override + @Transactional + public Result batchModifyInDB(List dtoList, String operator) { + LOGGER.info("method=batchModifyInDB||data={}||operator={}", dtoList, operator); + + for (ConnectClusterDTO dto: dtoList) { + if (!dto.getClusterUrl().startsWith("http://") && !dto.getClusterUrl().startsWith("https://")) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "clusterUrl必须以http或者https开头"); + } + } + + for (ConnectClusterDTO dto: dtoList) { + try { + ConnectClusterPO po = this.getRowById(dto.getId()); + if (po == null) { + // 回滚事务 + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(dto.getId())); + } + + if (!ValidateUtils.isNull(dto.getName())) { + po.setName(dto.getName()); + } + + if (!ValidateUtils.isNull(dto.getClusterUrl())) { + String clusterUrl = dto.getClusterUrl(); + if (clusterUrl.charAt(clusterUrl.length() - 1) == '/') { + clusterUrl = clusterUrl.substring(0, clusterUrl.length() - 1); + } + po.setClusterUrl(clusterUrl); + } + if (!ValidateUtils.isNull(dto.getVersion())) { + po.setVersion(dto.getVersion()); + } + if (!ValidateUtils.isNull(dto.getJmxProperties())) { + po.setJmxProperties(dto.getJmxProperties()); + } + + connectClusterDAO.updateById(po); + + // 记录操作 + opLogWrapService.saveOplogAndIgnoreException( + new OplogDTO( + operator, + OperationEnum.EDIT.getDesc(), + ModuleEnum.KAFKA_CONNECT_CLUSTER.getDesc(), + MsgConstant.getConnectClusterBizStr(dto.getId(), dto.getName()), + ConvertUtil.obj2Json(po) + ) + ); + } catch (DuplicateKeyException dke) { + LOGGER.error( + "method=batchModifyInDB||data={}||operator={}||errMsg=connectCluster name duplicate", + dtoList, operator + ); + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "connect集群name重复"); + + } catch (Exception e) { + LOGGER.error( + "method=batchModifyInDB||data={}||operator={}||errMsg=exception", + dtoList, operator, e + ); + + // 回滚事务 + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + + return Result.buildFromRSAndMsg(ResultStatus.MYSQL_OPERATE_FAILED, e.getMessage()); + } + } + + return Result.buildSuc(); + } + + @Override + public Boolean existConnectClusterDown(Long kafkaClusterPhyId) { + List connectClusters = this.listByKafkaCluster(kafkaClusterPhyId); + for (ConnectCluster connectCluster : connectClusters) { + if (GroupStateEnum.getByState(String.valueOf(connectCluster.getState())) == GroupStateEnum.DEAD) + return true; + } + return false; + } + + /**************************************************** private method ****************************************************/ + + private ConnectClusterPO getPOFromDB(Long kafkaClusterPhyId, String groupName) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectClusterPO::getGroupName, groupName); + lambdaQueryWrapper.eq(ConnectClusterPO::getKafkaClusterPhyId, kafkaClusterPhyId); + + return connectClusterDAO.selectOne(lambdaQueryWrapper); + } + + public ConnectClusterPO getRowById(Long connectClusterId) { + return connectClusterDAO.selectById(connectClusterId); + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorMetricService.java new file mode 100644 index 00000000..67879088 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorMetricService.java @@ -0,0 +1,36 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectorsDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; + +import java.util.List; + +/** + * @author didi + */ +public interface ConnectorMetricService { + + /** + * 从Kafka获取指标 + */ + Result collectConnectClusterMetricsFromKafkaWithCacheFirst(Long connectClusterPhyId, String connectorName, String metricName); + + Result collectConnectClusterMetricsFromKafka(Long connectClusterPhyId, String connectorName, String metricName); + + Result collectConnectClusterMetricsFromKafka(Long connectClusterPhyId, String connectorName, String metricName, ConnectorTypeEnum connectorType); + + /** + * 从ES中获取一段时间内聚合计算之后的指标线 + */ + Result> listConnectClusterMetricsFromES(Long clusterPhyId, MetricsConnectorsDTO dto); + + Result> getLatestMetricsFromES(Long clusterPhyId, List connectorNameList, List metricNameList); + + Result getLatestMetricsFromES(Long connectClusterId, String connectorName, List metricsNames); + + boolean isMetricName(String str); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java new file mode 100644 index 00000000..076f5c11 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java @@ -0,0 +1,59 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorStateInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; + +import java.util.List; +import java.util.Properties; +import java.util.Set; + +/** + * 查看Connector + */ +public interface ConnectorService { + Result createConnector(Long connectClusterId, String connectorName, Properties configs, String operator); + + /** + * 获取所有的连接器名称列表 + */ + Result> listConnectorsFromCluster(Long connectClusterId); + + /** + * 获取单个连接器信息 + */ + Result getConnectorInfoFromCluster(Long connectClusterId, String connectorName); + + Result> getConnectorTopicsFromCluster(Long connectClusterId, String connectorName); + + Result getConnectorStateInfoFromCluster(Long connectClusterId, String connectorName); + + Result getAllConnectorInfoFromCluster(Long connectClusterId, String connectorName); + + Result resumeConnector(Long connectClusterId, String connectorName, String operator); + + Result restartConnector(Long connectClusterId, String connectorName, String operator); + + Result stopConnector(Long connectClusterId, String connectorName, String operator); + + Result deleteConnector(Long connectClusterId, String connectorName, String operator); + + Result updateConnectorConfig(Long connectClusterId, String connectorName, Properties configs, String operator); + + void batchReplace(Long kafkaClusterPhyId, Long connectClusterId, List connectorList, Set allConnectorNameSet); + + void addNewToDB(KSConnector connector); + + List listByKafkaClusterIdFromDB(Long kafkaClusterPhyId); + + List listByConnectClusterIdFromDB(Long connectClusterId); + + int countByConnectClusterIdFromDB(Long connectClusterId); + + ConnectorPO getConnectorFromDB(Long connectClusterId, String connectorName); + + ConnectorTypeEnum getConnectorType(Long connectClusterId, String connectorName); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java new file mode 100644 index 00000000..10325e02 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java @@ -0,0 +1,443 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.connector.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectorsDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorTaskMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.connect.ConnectorMetricParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionConnectJmxInfo; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BrokerMetricPO; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect.ConnectorMetricPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; +import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; +import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil; +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.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.cache.CollectedMetricsLocalCache; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseConnectorMetricService; +import com.xiaojukeji.know.streaming.km.persistence.connect.ConnectJMXClient; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.ConnectorMetricESDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; + +/** + * @author didi + */ +@Service +public class ConnectorMetricServiceImpl extends BaseConnectorMetricService implements ConnectorMetricService { + protected static final ILog LOGGER = LogFactory.getLog(ConnectorMetricServiceImpl.class); + + public static final String CONNECTOR_METHOD_DO_NOTHING = "getConnectWorkerMetricSum"; + + public static final String CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM = "getConnectWorkerMetricSum"; + + public static final String CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG = "getConnectorTaskMetricsAvg"; + + public static final String CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX = "getConnectorTaskMetricsMax"; + + public static final String CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM = "getConnectorTaskMetricsSum"; + + public static final String CONNECTOR_METHOD_GET_METRIC_HEALTH_SCORE = "getMetricHealthScore"; + + @Autowired + private ConnectorMetricESDAO connectorMetricESDAO; + + @Autowired + private ConnectJMXClient connectJMXClient; + + @Autowired + private WorkerService workerService; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Autowired + private HealthStateService healthStateService; + + @Override + protected VersionItemTypeEnum getVersionItemType() { + return VersionItemTypeEnum.METRIC_CONNECT_CONNECTOR; + } + + @Override + protected List listMetricPOFields() { + return BeanUtil.listBeanFields(BrokerMetricPO.class); + } + + @Override + protected void initRegisterVCHandler() { + registerVCHandler(CONNECTOR_METHOD_DO_NOTHING, this::doNothing); + registerVCHandler(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM, this::getConnectWorkerMetricSum); + registerVCHandler(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG, this::getConnectorTaskMetricsAvg); + registerVCHandler(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX, this::getConnectorTaskMetricsMax); + registerVCHandler(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, this::getConnectorTaskMetricsSum); + registerVCHandler(CONNECTOR_METHOD_GET_METRIC_HEALTH_SCORE, this::getMetricHealthScore); + } + + @Override + public Result collectConnectClusterMetricsFromKafkaWithCacheFirst(Long connectClusterPhyId, String connectorName, String metric) { + String connectorMetricKey = CollectedMetricsLocalCache.genConnectorMetricCacheKey(connectClusterPhyId, connectorName, metric); + Float keyValue = CollectedMetricsLocalCache.getConnectorMetrics(connectorMetricKey); + + if (null != keyValue) { + ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterPhyId, connectorName, metric, keyValue); + return Result.buildSuc(connectorMetrics); + } + + Result ret = this.collectConnectClusterMetricsFromKafka(connectClusterPhyId, connectorName, metric); + if (ret == null || !ret.hasData()) { + return ret; + } + + Map metricMap = ret.getData().getMetrics(); + for (Map.Entry entry : metricMap.entrySet()) { + CollectedMetricsLocalCache.putConnectorMetrics(entry.getKey(), entry.getValue()); + } + return ret; + } + + @Override + public Result collectConnectClusterMetricsFromKafka(Long connectClusterPhyId, String connectorName, String metricName) { + try { + ConnectorMetricParam metricParam = new ConnectorMetricParam(connectClusterPhyId, connectorName, metricName, null); + return (Result) doVCHandler(connectClusterPhyId, metricName, metricParam); + } catch (VCHandlerNotExistException e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result collectConnectClusterMetricsFromKafka(Long connectClusterPhyId, String connectorName, String metricName, ConnectorTypeEnum connectorType) { + try { + ConnectorMetricParam metricParam = new ConnectorMetricParam(connectClusterPhyId, connectorName, metricName, connectorType); + return (Result) doVCHandler(connectClusterPhyId, metricName, metricParam); + } catch (VCHandlerNotExistException e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result> listConnectClusterMetricsFromES(Long clusterPhyId, MetricsConnectorsDTO dto) { + Long startTime = dto.getStartTime(); + Long endTime = dto.getEndTime(); + Integer topN = dto.getTopNu(); + String aggType = dto.getAggType(); + List metricNameList = dto.getMetricsNames(); + + List> connectorList = new ArrayList<>(); + if(!CollectionUtils.isEmpty(dto.getConnectorNameList())){ + connectorList = dto.getConnectorNameList().stream() + .map(c -> new Tuple<>(c.getConnectClusterId(), c.getConnectorName())) + .collect(Collectors.toList()); + } + + Table, List> retTable; + if(ValidateUtils.isEmptyList(connectorList)) { + // 按照TopN的方式去获取 + List> defaultConnectorList = this.listTopNConnectorList(clusterPhyId, topN); + + retTable = connectorMetricESDAO.listMetricsByTopN(clusterPhyId, defaultConnectorList, metricNameList, aggType, topN, startTime, endTime); + } else { + // 制定集群ID去获取 + retTable = connectorMetricESDAO.listMetricsByConnectors(clusterPhyId, metricNameList, aggType, connectorList, startTime, endTime); + } + + return Result.buildSuc(this.metricMap2VO(clusterPhyId, retTable.rowMap())); + } + + @Override + public Result> getLatestMetricsFromES(Long clusterPhyId, List connectorNameList, List metricsNames) { + List> connectClusterIdAndConnectorNameList = connectorNameList + .stream() + .map(elem -> new Tuple<>(elem.getConnectClusterId(), elem.getConnectorName())) + .collect(Collectors.toList()); + + List poList = + connectorMetricESDAO.getConnectorLatestMetric(clusterPhyId, connectClusterIdAndConnectorNameList, metricsNames); + + return Result.buildSuc(ConvertUtil.list2List(poList, ConnectorMetrics.class)); + } + + @Override + public Result getLatestMetricsFromES(Long connectClusterId, String connectorName, List metricsNames) { + ConnectorMetricPO connectorMetricPO = connectorMetricESDAO.getConnectorLatestMetric( + null, connectClusterId, connectorName, metricsNames); + return Result.buildSuc(ConvertUtil.obj2Obj(connectorMetricPO, ConnectorMetrics.class)); + } + + @Override + public boolean isMetricName(String str) { + return super.isMetricName(str); + } + + /**************************************************** private method ****************************************************/ + private Result doNothing(VersionItemParam metricParam){ + ConnectorMetricParam param = (ConnectorMetricParam) metricParam; + return Result.buildSuc(new ConnectorMetrics(param.getConnectClusterId(), param.getConnectorName())); + } + + private Result getMetricHealthScore(VersionItemParam metricParam) { + ConnectorMetricParam param = (ConnectorMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + + ConnectorMetrics metrics = healthStateService.calConnectorHealthMetrics(connectClusterId, connectorName); + return Result.buildSuc(metrics); + } + + private Result getConnectWorkerMetricSum(VersionItemParam metricParam) { + ConnectorMetricParam param = (ConnectorMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + String metric = param.getMetricName(); + ConnectorTypeEnum connectorType = param.getConnectorType(); + + float sum = 0; + boolean isCollected = false; + //根据connectClusterId获取connectMemberId列表 + List workerIdList = workerService.listFromDB(connectClusterId).stream().map(elem -> elem.getWorkerId()).collect(Collectors.toList()); + for (String workerId : workerIdList) { + Result ret = this.getConnectorMetric(connectClusterId, workerId, connectorName, metric, connectorType); + + if (ret == null || !ret.hasData() || ret.getData().getMetric(metric) == null) { + continue; + } + + isCollected = true; + sum += ret.getData().getMetric(metric); + } + if (!isCollected) { + return Result.buildFailure(NOT_EXIST); + } + return Result.buildSuc(ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, sum)); + } + + //kafka.connect:type=connect-worker-metrics,connector="{connector}" 指标 + private Result getConnectorMetric(Long connectClusterId, String workerId, String connectorName, String metric, ConnectorTypeEnum connectorType) { + VersionConnectJmxInfo jmxInfo = getJMXInfo(connectClusterId, metric); + + if (jmxInfo.getType() != null) { + if (connectorType == null) { + connectorType = connectorService.getConnectorType(connectClusterId, connectorName); + } + + if (connectorType != jmxInfo.getType()) { + return Result.buildFailure(VC_JMX_INSTANCE_NOT_FOUND); + } + } + + if (null == jmxInfo) { + return Result.buildFailure(VC_ITEM_JMX_NOT_EXIST); + } + String jmxObjectName = String.format(jmxInfo.getJmxObjectName(), connectorName); + + JmxConnectorWrap jmxConnectorWrap = connectJMXClient.getClientWithCheck(connectClusterId, workerId); + if (ValidateUtils.isNull(jmxConnectorWrap)) { + return Result.buildFailure(VC_JMX_INIT_ERROR); + } + + try { + //2、获取jmx指标 + String value = jmxConnectorWrap.getAttribute(new ObjectName(jmxObjectName), jmxInfo.getJmxAttribute()).toString(); + ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, Float.valueOf(value)); + return Result.buildSuc(connectorMetrics); + } catch (InstanceNotFoundException e) { + // 忽略该错误,该错误出现的原因是该指标在JMX中不存在 + return Result.buildSuc(new ConnectorMetrics(connectClusterId, connectorName)); + } catch (Exception e) { + LOGGER.error("method=getConnectorMetric||connectClusterId={}||workerId={}||connectorName={}||metrics={}||jmx={}||msg={}", + connectClusterId, workerId, connectorName, metric, jmxObjectName, e.getClass().getName()); + return Result.buildFailure(VC_JMX_CONNECT_ERROR); + } + } + + + private Result getConnectorTaskMetricsAvg(VersionItemParam metricParam){ + ConnectorMetricParam param = (ConnectorMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + String metric = param.getMetricName(); + ConnectorTypeEnum connectorType = param.getConnectorType(); + + Result> ret = this.getConnectorTaskMetricList(connectClusterId, connectorName, metric, connectorType); + if (ret == null || !ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + Float sum = ret.getData().stream().map(elem -> elem.getMetric(metric)).reduce(Float::sum).get(); + ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, sum / ret.getData().size()); + return Result.buildSuc(connectorMetrics); + } + + private Result getConnectorTaskMetricsMax(VersionItemParam metricParam){ + ConnectorMetricParam param = (ConnectorMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + String metric = param.getMetricName(); + ConnectorTypeEnum connectorType = param.getConnectorType(); + + Result> ret = this.getConnectorTaskMetricList(connectClusterId, connectorName, metric, connectorType); + if (ret == null || !ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + Float sum = ret.getData().stream().max((a, b) -> a.getMetric(metric).compareTo(b.getMetric(metric))).get().getMetric(metric); + ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, sum / ret.getData().size()); + return Result.buildSuc(connectorMetrics); + } + + private Result getConnectorTaskMetricsSum(VersionItemParam metricParam){ + ConnectorMetricParam param = (ConnectorMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + String metric = param.getMetricName(); + ConnectorTypeEnum connectorType = param.getConnectorType(); + + Result> ret = this.getConnectorTaskMetricList(connectClusterId, connectorName, metric, connectorType); + if (ret == null || !ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + Float sum = ret.getData().stream().map(elem -> elem.getMetric(metric)).reduce(Float::sum).get(); + ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, sum); + return Result.buildSuc(connectorMetrics); + } + + + private Result> getConnectorTaskMetricList(Long connectClusterId, String connectorName, String metricName, ConnectorTypeEnum connectorType) { + List connectorTaskMetricsList = new ArrayList<>(); + List workerConnectorList = workerConnectorService.listFromDB(connectClusterId).stream().filter(elem -> elem.getConnectorName().equals(connectorName)).collect(Collectors.toList()); + + if (workerConnectorList.isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + for (WorkerConnector workerConnector : workerConnectorList) { + Result ret = getConnectorTaskMetric(connectClusterId, workerConnector.getWorkerId(), connectorName, workerConnector.getTaskId(), metricName, connectorType); + + if (ret == null || !ret.hasData() || ret.getData().getMetric(metricName) == null) { + continue; + } + + connectorTaskMetricsList.add(ret.getData()); + } + return Result.buildSuc(connectorTaskMetricsList); + } + + + private Result getConnectorTaskMetric(Long connectClusterId, String workerId, String connectorName, Integer taskId, String metric, ConnectorTypeEnum connectorType) { + VersionConnectJmxInfo jmxInfo = getJMXInfo(connectClusterId, metric); + + if (jmxInfo.getType() != null) { + if (connectorType == null) { + connectorType = connectorService.getConnectorType(connectClusterId, connectorName); + } + + if (connectorType != jmxInfo.getType()) { + return Result.buildFailure(VC_JMX_INSTANCE_NOT_FOUND); + } + } + + if (null == jmxInfo) { + return Result.buildFailure(VC_ITEM_JMX_NOT_EXIST); + } + String jmxObjectName=String.format(jmxInfo.getJmxObjectName(), connectorName, taskId); + + JmxConnectorWrap jmxConnectorWrap = connectJMXClient.getClientWithCheck(connectClusterId, workerId); + if (ValidateUtils.isNull(jmxConnectorWrap)) { + return Result.buildFailure(VC_JMX_INIT_ERROR); + } + + try { + //2、获取jmx指标 + String value = jmxConnectorWrap.getAttribute(new ObjectName(jmxObjectName), jmxInfo.getJmxAttribute()).toString(); + ConnectorTaskMetrics connectorTaskMetrics = ConnectorTaskMetrics.initWithMetric(connectClusterId, connectorName, taskId, metric, Float.valueOf(value)); + return Result.buildSuc(connectorTaskMetrics); + } catch (Exception e) { + LOGGER.error("method=getConnectorTaskMetric||connectClusterId={}||workerId={}||connectorName={}||taskId={}||metrics={}||jmx={}||msg={}", + connectClusterId, workerId, connectorName, taskId, metric, jmxObjectName, e.getClass().getName()); + return Result.buildFailure(VC_JMX_CONNECT_ERROR); + } + } + + private List> listTopNConnectorList(Long clusterPhyId, Integer topN) { + List connectorPOS = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); + + if (CollectionUtils.isEmpty(connectorPOS)) { + return new ArrayList<>(); + } + + return connectorPOS.subList(0, Math.min(topN, connectorPOS.size())) + .stream() + .map( c -> new Tuple<>(c.getId(), c.getConnectorName()) ) + .collect(Collectors.toList()); + } + + protected List metricMap2VO(Long connectClusterId, + Map, List>> map){ + List multiLinesVOS = new ArrayList<>(); + if (map == null || map.isEmpty()) { + // 如果为空,则直接返回 + return multiLinesVOS; + } + + for(String metric : map.keySet()){ + try { + MetricMultiLinesVO multiLinesVO = new MetricMultiLinesVO(); + multiLinesVO.setMetricName(metric); + + List metricLines = new ArrayList<>(); + + Map, List> metricPointMap = map.get(metric); + if(null == metricPointMap || metricPointMap.isEmpty()){continue;} + + for(Map.Entry, List> entry : metricPointMap.entrySet()){ + MetricLineVO metricLineVO = new MetricLineVO(); + metricLineVO.setName(entry.getKey().getV1() + "#" + entry.getKey().getV2()); + metricLineVO.setMetricName(metric); + metricLineVO.setMetricPoints(entry.getValue()); + + metricLines.add(metricLineVO); + } + + multiLinesVO.setMetricLines(metricLines); + multiLinesVOS.add(multiLinesVO); + }catch (Exception e){ + LOGGER.error("method=metricMap2VO||connectClusterId={}||msg=exception!", connectClusterId, e); + } + } + + return multiLinesVOS; + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java new file mode 100644 index 00000000..9d2136a9 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java @@ -0,0 +1,581 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.connector.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.didiglobal.logi.security.common.dto.oplog.OplogDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorStateInfo; +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.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.component.RestTool; +import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; +import com.xiaojukeji.know.streaming.km.common.converter.ConnectConverter; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; +import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.ModuleEnum; +import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.BackoffUtils; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectorDAO; +import org.apache.kafka.connect.runtime.rest.entities.ActiveTopicsInfo; +import org.apache.kafka.connect.runtime.rest.entities.ConnectorInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.util.*; + +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.SERVICE_OP_CONNECT_CONNECTOR; + +@Service +public class ConnectorServiceImpl extends BaseVersionControlService implements ConnectorService { + private static final ILog LOGGER = LogFactory.getLog(ConnectorServiceImpl.class); + + @Autowired + private RestTool restTool; + + @Autowired + private ConnectorDAO connectorDAO; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private OpLogWrapService opLogWrapService; + + private static final String LIST_CONNECTORS_URI = "/connectors"; + private static final String GET_CONNECTOR_INFO_PREFIX_URI = "/connectors"; + private static final String GET_CONNECTOR_TOPICS_URI = "/connectors/%s/topics"; + private static final String GET_CONNECTOR_STATUS_URI = "/connectors/%s/status"; + + private static final String CREATE_CONNECTOR_URI = "/connectors"; + private static final String RESUME_CONNECTOR_URI = "/connectors/%s/resume"; + private static final String RESTART_CONNECTOR_URI = "/connectors/%s/restart"; + private static final String PAUSE_CONNECTOR_URI = "/connectors/%s/pause"; + private static final String DELETE_CONNECTOR_URI = "/connectors/%s"; + private static final String UPDATE_CONNECTOR_CONFIG_URI = "/connectors/%s/config"; + + @Override + protected VersionItemTypeEnum getVersionItemType() { + return SERVICE_OP_CONNECT_CONNECTOR; + } + + @Override + public Result createConnector(Long connectClusterId, String connectorName, Properties configs, String operator) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + // 构造参数 + Properties props = new Properties(); + props.put("name", connectorName); + props.put("config", configs); + + ConnectorInfo connectorInfo = restTool.postObjectWithJsonContent( + connectCluster.getClusterUrl() + CREATE_CONNECTOR_URI, + props, + ConnectorInfo.class + ); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.ADD.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectorBizStr(connectClusterId, connectorName), + ConvertUtil.obj2Json(configs) + )); + + KSConnectorInfo connector = new KSConnectorInfo(); + connector.setConnectClusterId(connectClusterId); + connector.setConfig(connectorInfo.config()); + connector.setName(connectorInfo.name()); + connector.setTasks(connectorInfo.tasks()); + connector.setType(connectorInfo.type()); + + return Result.buildSuc(connector); + } catch (Exception e) { + LOGGER.error( + "method=createConnector||connectClusterId={}||connectorName={}||configs={}||operator={}||errMsg=exception", + connectClusterId, connectorName, configs, operator, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result> listConnectorsFromCluster(Long connectClusterId) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + List nameList = restTool.getArrayObjectWithJsonContent( + connectCluster.getClusterUrl() + LIST_CONNECTORS_URI, + new HashMap<>(), + String.class + ); + + return Result.buildSuc(nameList); + } catch (Exception e) { + LOGGER.error( + "method=listConnectorsFromCluster||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result getConnectorInfoFromCluster(Long connectClusterId, String connectorName) { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + return this.getConnectorInfoFromCluster(connectCluster, connectorName); + } + + @Override + public Result> getConnectorTopicsFromCluster(Long connectClusterId, String connectorName) { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + return this.getConnectorTopicsFromCluster(connectCluster, connectorName); + } + + @Override + public Result getConnectorStateInfoFromCluster(Long connectClusterId, String connectorName) { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + return this.getConnectorStateInfoFromCluster(connectCluster, connectorName); + } + + @Override + public Result getAllConnectorInfoFromCluster(Long connectClusterId, String connectorName) { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + Result connectorResult = this.getConnectorInfoFromCluster(connectCluster, connectorName); + if (connectorResult.failed()) { + LOGGER.error( + "method=getAllConnectorInfoFromCluster||connectClusterId={}||connectorName={}||result={}", + connectClusterId, connectorName, connectorResult + ); + + return Result.buildFromIgnoreData(connectorResult); + } + + Result> topicNameListResult = this.getConnectorTopicsFromCluster(connectCluster, connectorName); + if (topicNameListResult.failed()) { + LOGGER.error( + "method=getAllConnectorInfoFromCluster||connectClusterId={}||connectorName={}||result={}", + connectClusterId, connectorName, connectorResult + ); + } + + Result stateInfoResult = this.getConnectorStateInfoFromCluster(connectCluster, connectorName); + if (stateInfoResult.failed()) { + LOGGER.error( + "method=getAllConnectorInfoFromCluster||connectClusterId={}||connectorName={}||result={}", + connectClusterId, connectorName, connectorResult + ); + } + + return Result.buildSuc(ConnectConverter.convert2KSConnector( + connectCluster.getKafkaClusterPhyId(), + connectCluster.getId(), + connectorResult.getData(), + stateInfoResult.getData(), + topicNameListResult.getData() + )); + } + + @Override + public Result resumeConnector(Long connectClusterId, String connectorName, String operator) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + restTool.putJsonForObject( + connectCluster.getClusterUrl() + String.format(RESUME_CONNECTOR_URI, connectorName), + new HashMap<>(), + String.class + ); + + this.updateStatus(connectCluster, connectClusterId, connectorName); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.ENABLE.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectorBizStr(connectClusterId, connectorName), + "" + )); + + return Result.buildSuc(); + } catch (Exception e) { + LOGGER.error( + "class=ConnectorServiceImpl||method=resumeConnector||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result restartConnector(Long connectClusterId, String connectorName, String operator) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + restTool.postObjectWithJsonContent( + connectCluster.getClusterUrl() + String.format(RESTART_CONNECTOR_URI, connectorName), + new HashMap<>(), + String.class + ); + + this.updateStatus(connectCluster, connectClusterId, connectorName); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.RESTART.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectorBizStr(connectClusterId, connectorName), + "" + )); + + return Result.buildSuc(); + } catch (Exception e) { + LOGGER.error( + "method=restartConnector||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result stopConnector(Long connectClusterId, String connectorName, String operator) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + restTool.putJsonForObject( + connectCluster.getClusterUrl() + String.format(PAUSE_CONNECTOR_URI, connectorName), + new HashMap<>(), + String.class + ); + + this.updateStatus(connectCluster, connectClusterId, connectorName); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.DISABLE.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectorBizStr(connectClusterId, connectorName), + "" + )); + + return Result.buildSuc(); + } catch (Exception e) { + LOGGER.error( + "method=stopConnector||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result deleteConnector(Long connectClusterId, String connectorName, String operator) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + restTool.deleteWithParamsAndHeader( + connectCluster.getClusterUrl() + String.format(DELETE_CONNECTOR_URI, connectorName), + new HashMap<>(), + new HashMap<>(), + String.class + ); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.DELETE.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectorBizStr(connectClusterId, connectorName), + "" + )); + + this.deleteConnectorInDB(connectClusterId, connectorName); + + return Result.buildSuc(); + } catch (Exception e) { + LOGGER.error( + "method=deleteConnector||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result updateConnectorConfig(Long connectClusterId, String connectorName, Properties configs, String operator) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + ConnectorInfo connectorInfo = restTool.putJsonForObject( + connectCluster.getClusterUrl() + String.format(UPDATE_CONNECTOR_CONFIG_URI, connectorName), + configs, + org.apache.kafka.connect.runtime.rest.entities.ConnectorInfo.class + ); + + this.updateStatus(connectCluster, connectClusterId, connectorName); + + opLogWrapService.saveOplogAndIgnoreException(new OplogDTO( + operator, + OperationEnum.EDIT.getDesc(), + ModuleEnum.KAFKA_CONNECT_CONNECTOR.getDesc(), + MsgConstant.getConnectorBizStr(connectClusterId, connectorName), + ConvertUtil.obj2Json(configs) + )); + + return Result.buildSuc(); + } catch (Exception e) { + LOGGER.error( + "method=updateConnectorConfig||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public void batchReplace(Long kafkaClusterPhyId, Long connectClusterId, List connectorList, Set allConnectorNameSet) { + List poList = this.listByConnectClusterIdFromDB(connectClusterId); + + Map oldPOMap = new HashMap<>(); + poList.forEach(elem -> oldPOMap.put(elem.getConnectorName(), elem)); + + for (KSConnector connector: connectorList) { + try { + ConnectorPO oldPO = oldPOMap.remove(connector.getConnectorName()); + if (oldPO == null) { + oldPO = ConvertUtil.obj2Obj(connector, ConnectorPO.class); + connectorDAO.insert(oldPO); + } else { + ConnectorPO newPO = ConvertUtil.obj2Obj(connector, ConnectorPO.class); + newPO.setId(oldPO.getId()); + connectorDAO.updateById(newPO); + } + } catch (DuplicateKeyException dke) { + // ignore + } + } + + try { + oldPOMap.values().forEach(elem -> { + if (allConnectorNameSet.contains(elem.getConnectorName())) { + // 当前connector还存在 + return; + } + + // 当前connector不存在了,则进行删除 + connectorDAO.deleteById(elem.getId()); + }); + } catch (Exception e) { + // ignore + } + } + + @Override + public void addNewToDB(KSConnector connector) { + try { + connectorDAO.insert(ConvertUtil.obj2Obj(connector, ConnectorPO.class)); + } catch (DuplicateKeyException dke) { + // ignore + } + } + + @Override + public List listByKafkaClusterIdFromDB(Long kafkaClusterPhyId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectorPO::getKafkaClusterPhyId, kafkaClusterPhyId); + + return connectorDAO.selectList(lambdaQueryWrapper); + } + + @Override + public List listByConnectClusterIdFromDB(Long connectClusterId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectorPO::getConnectClusterId, connectClusterId); + + return connectorDAO.selectList(lambdaQueryWrapper); + } + + @Override + public int countByConnectClusterIdFromDB(Long connectClusterId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectorPO::getConnectClusterId, connectClusterId); + + return connectorDAO.selectCount(lambdaQueryWrapper); + } + + @Override + public ConnectorPO getConnectorFromDB(Long connectClusterId, String connectorName) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectorPO::getConnectClusterId, connectClusterId); + lambdaQueryWrapper.eq(ConnectorPO::getConnectorName, connectorName); + + return connectorDAO.selectOne(lambdaQueryWrapper); + } + + @Override + public ConnectorTypeEnum getConnectorType(Long connectClusterId, String connectorName) { + ConnectorTypeEnum connectorType = ConnectorTypeEnum.UNKNOWN; + ConnectorPO connector = this.getConnectorFromDB(connectClusterId, connectorName); + if (connector != null) { + connectorType = ConnectorTypeEnum.getByName(connector.getConnectorType()); + } + return connectorType; + } + + /**************************************************** private method ****************************************************/ + private int deleteConnectorInDB(Long connectClusterId, String connectorName) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectorPO::getConnectClusterId, connectClusterId); + lambdaQueryWrapper.eq(ConnectorPO::getConnectorName, connectorName); + + return connectorDAO.delete(lambdaQueryWrapper); + } + + private Result getConnectorInfoFromCluster(ConnectCluster connectCluster, String connectorName) { + try { + ConnectorInfo connectorInfo = restTool.getForObject( + connectCluster.getClusterUrl() + GET_CONNECTOR_INFO_PREFIX_URI + "/" + connectorName, + new HashMap<>(), + ConnectorInfo.class + ); + + KSConnectorInfo connector = new KSConnectorInfo(); + connector.setConnectClusterId(connectCluster.getId()); + connector.setConfig(connectorInfo.config()); + connector.setName(connectorInfo.name()); + connector.setTasks(connectorInfo.tasks()); + connector.setType(connectorInfo.type()); + + return Result.buildSuc(connector); + } catch (Exception e) { + LOGGER.error( + "method=getConnectorInfoFromCluster||connectClusterId={}||connectorName={}||errMsg=exception", + connectCluster.getId(), connectorName, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + private Result> getConnectorTopicsFromCluster(ConnectCluster connectCluster, String connectorName) { + try { + Properties properties = restTool.getForObject( + connectCluster.getClusterUrl() + String.format(GET_CONNECTOR_TOPICS_URI, connectorName), + new HashMap<>(), + Properties.class + ); + + ActiveTopicsInfo activeTopicsInfo = ConvertUtil.toObj(ConvertUtil.obj2Json(properties.get(connectorName)), ActiveTopicsInfo.class); + return Result.buildSuc(new ArrayList<>(activeTopicsInfo.topics())); + } catch (Exception e) { + LOGGER.error( + "method=getConnectorTopicsFromCluster||connectClusterId={}||connectorName={}||errMsg=exception", + connectCluster.getId(), connectorName, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + private Result getConnectorStateInfoFromCluster(ConnectCluster connectCluster, String connectorName) { + try { + KSConnectorStateInfo connectorStateInfo = restTool.getForObject( + connectCluster.getClusterUrl() + String.format(GET_CONNECTOR_STATUS_URI, connectorName), + new HashMap<>(), + KSConnectorStateInfo.class + ); + + return Result.buildSuc(connectorStateInfo); + } catch (Exception e) { + LOGGER.error( + "method=getConnectorStateInfoFromCluster||connectClusterId={}||connectorName={}||errMsg=exception", + connectCluster.getId(), connectorName, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + private void updateStatus(ConnectCluster connectCluster, Long connectClusterId, String connectorName) { + try { + // 延迟3秒 + BackoffUtils.backoff(2000); + + Result stateInfoResult = this.getConnectorStateInfoFromCluster(connectCluster, connectorName); + if (stateInfoResult.failed()) { + return; + } + + ConnectorPO po = new ConnectorPO(); + po.setConnectClusterId(connectClusterId); + po.setConnectorName(connectorName); + po.setState(stateInfoResult.getData().getConnector().getState()); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectorPO::getConnectClusterId, connectClusterId); + lambdaQueryWrapper.eq(ConnectorPO::getConnectorName, connectorName); + + connectorDAO.update(po, lambdaQueryWrapper); + } catch (Exception e) { + LOGGER.error( + "method=updateStatus||connectClusterId={}||connectorName={}||errMsg=exception", + connectClusterId, connectorName, e + ); + } + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/PluginService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/PluginService.java new file mode 100644 index 00000000..d842d940 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/PluginService.java @@ -0,0 +1,20 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.plugin; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.plugin.ConnectPluginBasic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; + +import java.util.List; +import java.util.Properties; + + +/** + * 查看Connector + */ +public interface PluginService { + Result getConfig(Long connectClusterId, String pluginName); + + Result validateConfig(Long connectClusterId, Properties props); + + Result> listPluginsFromCluster(Long connectClusterId); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/impl/PluginServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/impl/PluginServiceImpl.java new file mode 100644 index 00000000..fa6f1394 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/plugin/impl/PluginServiceImpl.java @@ -0,0 +1,112 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.plugin.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.plugin.ConnectPluginBasic; +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.component.RestTool; +import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.plugin.PluginService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import org.apache.kafka.connect.runtime.rest.entities.ConfigInfos; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.SERVICE_OP_CONNECT_PLUGIN; + +@Service +public class PluginServiceImpl extends BaseVersionControlService implements PluginService { + private static final ILog LOGGER = LogFactory.getLog(PluginServiceImpl.class); + + @Autowired + private RestTool restTool; + + @Autowired + private ConnectClusterService connectClusterService; + + private static final String GET_PLUGIN_CONFIG_DESC_URI = "/connector-plugins/%s/config/validate"; + private static final String GET_ALL_PLUGINS_URI = "/connector-plugins"; + + @Override + protected VersionItemTypeEnum getVersionItemType() { + return SERVICE_OP_CONNECT_PLUGIN; + } + + @Override + public Result getConfig(Long connectClusterId, String pluginName) { + Properties props = new Properties(); + props.put(KafkaConnectConstant.CONNECTOR_CLASS_FILED_NAME, pluginName); + props.put(KafkaConnectConstant.CONNECTOR_TOPICS_FILED_NAME, KafkaConnectConstant.CONNECTOR_TOPICS_FILED_ERROR_VALUE); + + return this.validateConfig(connectClusterId, props); + } + + @Override + public Result validateConfig(Long connectClusterId, Properties props) { + try { + if (ValidateUtils.isBlank(props.getProperty(KafkaConnectConstant.CONNECTOR_CLASS_FILED_NAME))) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "参数错误, connector.class字段数据不允许不存在或者为空"); + } + + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + // 通过参数检查接口,获取插件配置 + ConfigInfos configInfos = restTool.putJsonForObject( + connectCluster.getClusterUrl() + String.format(GET_PLUGIN_CONFIG_DESC_URI, props.getProperty(KafkaConnectConstant.CONNECTOR_CLASS_FILED_NAME)), + props, + ConfigInfos.class + ); + + return Result.buildSuc(new ConnectConfigInfos(configInfos)); + } catch (Exception e) { + LOGGER.error( + "method=validateConfig||connectClusterId={}||pluginName={}||errMsg=exception", + connectClusterId, + props.getProperty(KafkaConnectConstant.CONNECTOR_CLASS_FILED_NAME), + e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } + + @Override + public Result> listPluginsFromCluster(Long connectClusterId) { + try { + ConnectCluster connectCluster = connectClusterService.getById(connectClusterId); + if (ValidateUtils.isNull(connectCluster)) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectClusterNotExist(connectClusterId)); + } + + // 通过参数检查接口,获取插件配置 + List pluginList = restTool.getArrayObjectWithJsonContent( + connectCluster.getClusterUrl() + GET_ALL_PLUGINS_URI, + new HashMap<>(), + ConnectPluginBasic.class + ); + + return Result.buildSuc(pluginList); + } catch (Exception e) { + LOGGER.error( + "method=listPluginsFromCluster||connectClusterId={}||errMsg=exception", + connectClusterId, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_CONNECTOR_READ_FAILED, e.getMessage()); + } + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerConnectorService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerConnectorService.java new file mode 100644 index 00000000..f5a5b598 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerConnectorService.java @@ -0,0 +1,23 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.worker; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.task.TaskActionDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; + +import java.util.List; + +/** + * Worker + */ +public interface WorkerConnectorService { + void batchReplaceInDB(Long connectClusterId, List workerList); + + List listFromDB(Long connectClusterId); + + List listByKafkaClusterIdFromDB(Long kafkaClusterPhyId); + + Result actionTask(TaskActionDTO dto); + + List getWorkerConnectorListFromCluster(ConnectCluster connectCluster, String connectorName); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerService.java new file mode 100644 index 00000000..067a33d8 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/WorkerService.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.worker; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterWorkerOverviewVO; + +import java.util.List; + +/** + * Worker + * @author didi + */ +public interface WorkerService { + /** + * 批量插入数据库 + * @param connectClusterId + * @param workerList + */ + void batchReplaceInDB(Long connectClusterId, List workerList); + + /** + * 从数据库中获取 + * @param connectClusterId + * @return + */ + List listFromDB(Long connectClusterId); + + /** + * 分页获取 + * @param kafkaClusterPhyId + * @param dto + * @return + */ + PaginationResult pageWorkByKafkaClusterPhy(Long kafkaClusterPhyId, PaginationBaseDTO dto); + + List listByKafkaClusterIdFromDB(Long kafkaClusterPhyId); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerConnectorServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerConnectorServiceImpl.java new file mode 100644 index 00000000..99fb9ba2 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerConnectorServiceImpl.java @@ -0,0 +1,143 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.worker.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.task.TaskActionDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorStateInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSTaskState; +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.po.connect.WorkerConnectorPO; +import com.xiaojukeji.know.streaming.km.common.component.RestTool; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.WorkerConnectorDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectActionEnum.RESTART; + +@Service +public class WorkerConnectorServiceImpl implements WorkerConnectorService { + + protected static final ILog LOGGER = LogFactory.getLog(WorkerConnectorServiceImpl.class); + @Autowired + private WorkerConnectorDAO workerConnectorDAO; + + @Autowired + private RestTool restTool; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private WorkerService workerService; + + + private static final String RESTART_TASK_URI = "%s/connectors/%s/tasks/%d/restart"; + + @Override + public void batchReplaceInDB(Long connectClusterId, List workerList) { + Map oldMap = new HashMap<>(); + for (WorkerConnectorPO oldPO : this.listPOSFromDB(connectClusterId)) { + oldMap.put(oldPO.getConnectorName() + oldPO.getWorkerId() + oldPO.getTaskId() + oldPO.getState(), oldPO); + } + + for (WorkerConnector workerConnector : workerList) { + try { + String key = workerConnector.getConnectorName() + workerConnector.getWorkerId() + workerConnector.getTaskId() + workerConnector.getState(); + + WorkerConnectorPO oldPO = oldMap.remove(key); + if (oldPO == null) { + workerConnectorDAO.insert(ConvertUtil.obj2Obj(workerConnector, WorkerConnectorPO.class)); + } else { + // 如果该数据已经存在,则不需要进行操作 + } + } catch (DuplicateKeyException dke) { + // ignore + } + } + + try { + oldMap.values().forEach(elem -> workerConnectorDAO.deleteById(elem.getId())); + } catch (Exception e) { + // ignore + } + } + + @Override + public List listFromDB(Long connectClusterId) { + return ConvertUtil.list2List(this.listPOSFromDB(connectClusterId), WorkerConnector.class); + } + + @Override + public List listByKafkaClusterIdFromDB(Long kafkaClusterPhyId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(WorkerConnectorPO::getKafkaClusterPhyId, kafkaClusterPhyId); + return ConvertUtil.list2List(workerConnectorDAO.selectList(lambdaQueryWrapper), WorkerConnector.class); + } + + + @Override + public Result actionTask(TaskActionDTO dto) { + if (!dto.getAction().equals(RESTART.getValue())) { + return Result.buildFailure(ResultStatus.OPERATION_FORBIDDEN); + } + + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(dto.getConnectClusterId()); + + if (connectCluster == null) { + return Result.buildFailure(ResultStatus.NOT_EXIST); + } + + String url = String.format(RESTART_TASK_URI, connectCluster.getClusterUrl(), dto.getConnectorName(), dto.getTaskId()); + try { + restTool.postObjectWithJsonContent(url, null, String.class); + } catch (Exception e) { + LOGGER.error("method=actionTask||connectClusterId={}||connectorName={}||taskId={}||restart failed||msg=exception", + dto.getConnectClusterId(), dto.getConnectorName(), dto.getTaskId(), e); + } + return Result.buildSuc(); + } + + @Override + public List getWorkerConnectorListFromCluster(ConnectCluster connectCluster, String connectorName) { + Map workerMap = workerService.listFromDB(connectCluster.getId()).stream().collect(Collectors.toMap(elem -> elem.getWorkerId(), Function.identity())); + List workerConnectorList = new ArrayList<>(); + Result ret = connectorService.getConnectorStateInfoFromCluster(connectCluster.getId(), connectorName); + if (!ret.hasData()) { + return workerConnectorList; + } + + KSConnectorStateInfo ksConnectorStateInfo = ret.getData(); + for (KSTaskState task : ksConnectorStateInfo.getTasks()) { + WorkerConnector workerConnector = new WorkerConnector(connectCluster.getKafkaClusterPhyId(), connectCluster.getId(), ksConnectorStateInfo.getName(), workerMap.get(task.getWorkerId()).getMemberId(), task.getId(), task.getState(), task.getWorkerId(), task.getTrace()); + workerConnectorList.add(workerConnector); + } + return workerConnectorList; + } + + + + private List listPOSFromDB(Long connectClusterId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(WorkerConnectorPO::getConnectClusterId, connectClusterId); + + return workerConnectorDAO.selectList(lambdaQueryWrapper); + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerServiceImpl.java new file mode 100644 index 00000000..c52998f1 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/worker/impl/WorkerServiceImpl.java @@ -0,0 +1,114 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.worker.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectWorkerPO; +import com.xiaojukeji.know.streaming.km.common.bean.po.group.GroupMemberPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterWorkerOverviewVO; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectWorkerDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class WorkerServiceImpl implements WorkerService { + @Autowired + private ConnectWorkerDAO connectWorkerDAO; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectClusterService connectClusterService; + + @Override + public void batchReplaceInDB(Long connectClusterId, List workerList) { + Map oldMap = new HashMap<>(); + for (ConnectWorkerPO oldPO: this.listPOSFromDB(connectClusterId)) { + oldMap.put(oldPO.getMemberId(), oldPO); + } + + for (ConnectWorker worker: workerList) { + try { + ConnectWorkerPO newPO = ConvertUtil.obj2Obj(worker, ConnectWorkerPO.class); + ConnectWorkerPO oldPO = oldMap.remove(newPO.getMemberId()); + if (oldPO == null) { + connectWorkerDAO.insert(newPO); + } else { + newPO.setId(oldPO.getId()); + connectWorkerDAO.updateById(newPO); + } + } catch (DuplicateKeyException dke) { + // ignore + } + } + + try { + oldMap.values().forEach(elem -> connectWorkerDAO.deleteById(elem.getId())); + } catch (Exception e) { + // ignore + } + } + + @Override + public List listFromDB(Long connectClusterId) { + return ConvertUtil.list2List(this.listPOSFromDB(connectClusterId), ConnectWorker.class); + } + + @Override + public PaginationResult pageWorkByKafkaClusterPhy(Long kafkaClusterPhyId, PaginationBaseDTO dto) { + IPage pageInfo = new Page<>(dto.getPageNo(), dto.getPageSize()); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectWorkerPO::getKafkaClusterPhyId, kafkaClusterPhyId); + lambdaQueryWrapper.like(!ValidateUtils.isBlank(dto.getSearchKeywords()), ConnectWorkerPO::getHost, dto.getSearchKeywords()); + connectWorkerDAO.selectPage(pageInfo, lambdaQueryWrapper); + + List connectWorkerPOS = pageInfo.getRecords(); + List clusterWorkerOverviewVOS = new ArrayList<>(); + + for(ConnectWorkerPO connectWorkerPO : connectWorkerPOS){ + Long connectClusterId = connectWorkerPO.getConnectClusterId(); + + ClusterWorkerOverviewVO clusterWorkerOverviewVO = new ClusterWorkerOverviewVO(); + clusterWorkerOverviewVO.setConnectClusterId(connectClusterId); + clusterWorkerOverviewVO.setWorkerHost(connectWorkerPO.getHost()); + clusterWorkerOverviewVO.setConnectorCount(connectorService.countByConnectClusterIdFromDB(connectClusterId)); + clusterWorkerOverviewVO.setConnectClusterName(connectClusterService.getClusterName(connectClusterId)); + clusterWorkerOverviewVO.setTaskCount(1); + + clusterWorkerOverviewVOS.add(clusterWorkerOverviewVO); + } + + return PaginationResult.buildSuc(clusterWorkerOverviewVOS, pageInfo); + } + + @Override + public List listByKafkaClusterIdFromDB(Long kafkaClusterPhyId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectWorkerPO::getKafkaClusterPhyId, kafkaClusterPhyId); + return ConvertUtil.list2List(connectWorkerDAO.selectList(lambdaQueryWrapper), ConnectWorker.class); + } + + /**************************************************** private method ****************************************************/ + private List listPOSFromDB(Long connectClusterId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectWorkerPO::getConnectClusterId, connectClusterId); + + return connectWorkerDAO.selectList(lambdaQueryWrapper); + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java index 8dc1c535..3f56a0b3 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java @@ -1,14 +1,15 @@ package com.xiaojukeji.know.streaming.km.core.service.group; import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.Group; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSGroupDescription; 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.po.group.GroupMemberPO; import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; import com.xiaojukeji.know.streaming.km.common.exception.AdminOperateException; import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; -import org.apache.kafka.clients.admin.ConsumerGroupDescription; import org.apache.kafka.common.TopicPartition; import java.util.Date; @@ -19,16 +20,16 @@ public interface GroupService { /** * 从Kafka中获取消费组名称列表 */ - List listGroupsFromKafka(Long clusterPhyId) throws NotExistException, AdminOperateException; + List listGroupsFromKafka(ClusterPhy clusterPhy) throws AdminOperateException; /** * 从Kafka中获取消费组详细信息 */ - Group getGroupFromKafka(Long clusterPhyId, String groupName) throws NotExistException, AdminOperateException; + Group getGroupFromKafka(ClusterPhy clusterPhy, String groupName) throws NotExistException, AdminOperateException; Map getGroupOffsetFromKafka(Long clusterPhyId, String groupName) throws NotExistException, AdminOperateException; - ConsumerGroupDescription getGroupDescriptionFromKafka(Long clusterPhyId, String groupName) throws NotExistException, AdminOperateException; + KSGroupDescription getGroupDescriptionFromKafka(ClusterPhy clusterPhy, String groupName) throws AdminOperateException; Result resetGroupOffsets(Long clusterPhyId, String groupName, Map offsetMap, String operator) throws NotExistException, AdminOperateException; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java index 1a923f21..c80652ee 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java @@ -7,8 +7,10 @@ import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.didiglobal.logi.security.common.dto.oplog.OplogDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.Group; import com.xiaojukeji.know.streaming.km.common.bean.entity.group.GroupTopicMember; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.*; 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; @@ -17,6 +19,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.po.group.GroupPO; import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.converter.GroupConverter; import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; +import com.xiaojukeji.know.streaming.km.common.enums.group.GroupTypeEnum; import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.ModuleEnum; import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; @@ -24,9 +27,10 @@ import com.xiaojukeji.know.streaming.km.common.exception.AdminOperateException; import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.common.utils.kafka.KSPartialKafkaAdminClient; import com.xiaojukeji.know.streaming.km.core.service.group.GroupService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.mysql.group.GroupDAO; import com.xiaojukeji.know.streaming.km.persistence.mysql.group.GroupMemberDAO; @@ -36,6 +40,7 @@ import org.apache.kafka.common.TopicPartition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.Duration; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -43,7 +48,7 @@ import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.SERVICE_SEARCH_GROUP; @Service -public class GroupServiceImpl extends BaseVersionControlService implements GroupService { +public class GroupServiceImpl extends BaseKafkaVersionControlService implements GroupService { private static final ILog log = LogFactory.getLog(GroupServiceImpl.class); @Autowired @@ -64,11 +69,18 @@ public class GroupServiceImpl extends BaseVersionControlService implements Group } @Override - public List listGroupsFromKafka(Long clusterPhyId) throws NotExistException, AdminOperateException { - AdminClient adminClient = kafkaAdminClient.getClient(clusterPhyId); - + public List listGroupsFromKafka(ClusterPhy clusterPhy) throws AdminOperateException { + KSPartialKafkaAdminClient adminClient = null; try { - ListConsumerGroupsResult listConsumerGroupsResult = adminClient.listConsumerGroups( + Properties props = ConvertUtil.str2ObjByJson(clusterPhy.getClientProperties(), Properties.class); + if (props == null) { + props = new Properties(); + } + + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, clusterPhy.getBootstrapServers()); + + adminClient = KSPartialKafkaAdminClient.create(props); + KSListGroupsResult listConsumerGroupsResult = adminClient.listConsumerGroups( new ListConsumerGroupsOptions() .timeoutMs(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS) ); @@ -80,33 +92,46 @@ public class GroupServiceImpl extends BaseVersionControlService implements Group return groupNameList; } catch (Exception e) { - log.error("method=getGroupsFromKafka||clusterPhyId={}||errMsg=exception!", clusterPhyId, e); + log.error("method=listGroupsFromKafka||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); throw new AdminOperateException(e.getMessage(), e, ResultStatus.KAFKA_OPERATE_FAILED); + } finally { + if (adminClient != null) { + try { + adminClient.close(Duration.ofSeconds(10)); + } catch (Exception e) { + // ignore + } + } } } @Override - public Group getGroupFromKafka(Long clusterPhyId, String groupName) throws NotExistException, AdminOperateException { + public Group getGroupFromKafka(ClusterPhy clusterPhy, String groupName) throws NotExistException, AdminOperateException { // 获取消费组的详细信息 - ConsumerGroupDescription groupDescription = this.getGroupDescriptionFromKafka(clusterPhyId, groupName); + KSGroupDescription groupDescription = this.getGroupDescriptionFromKafka(clusterPhy, groupName); if (groupDescription == null) { return null; } - Group group = new Group(clusterPhyId, groupName, groupDescription); + Group group = new Group(clusterPhy.getId(), groupName, groupDescription); // 获取消费组消费过哪些Topic Map memberMap = new HashMap<>(); - for (TopicPartition tp : this.getGroupOffsetFromKafka(clusterPhyId, groupName).keySet()) { + for (TopicPartition tp : this.getGroupOffsetFromKafka(clusterPhy.getId(), groupName).keySet()) { memberMap.putIfAbsent(tp.topic(), new GroupTopicMember(tp.topic(), 0)); } // 记录成员信息 - for (MemberDescription memberDescription : groupDescription.members()) { + for (KSMemberDescription memberDescription : groupDescription.members()) { + if (group.getType() == GroupTypeEnum.CONNECT_CLUSTER) { + continue; + } Set partitionList = new HashSet<>(); - if (!ValidateUtils.isNull(memberDescription.assignment().topicPartitions())) { - partitionList = memberDescription.assignment().topicPartitions(); + + KSMemberConsumerAssignment assignment = (KSMemberConsumerAssignment) memberDescription.assignment(); + if (!ValidateUtils.isNull(assignment.topicPartitions())) { + partitionList = assignment.topicPartitions(); } Set topicNameSet = partitionList.stream().map(elem -> elem.topic()).collect(Collectors.toSet()); @@ -143,20 +168,36 @@ public class GroupServiceImpl extends BaseVersionControlService implements Group } @Override - public ConsumerGroupDescription getGroupDescriptionFromKafka(Long clusterPhyId, String groupName) throws NotExistException, AdminOperateException { - AdminClient adminClient = kafkaAdminClient.getClient(clusterPhyId); - + public KSGroupDescription getGroupDescriptionFromKafka(ClusterPhy clusterPhy, String groupName) throws AdminOperateException { + KSPartialKafkaAdminClient adminClient = null; try { - DescribeConsumerGroupsResult describeConsumerGroupsResult = adminClient.describeConsumerGroups( + Properties props = ConvertUtil.str2ObjByJson(clusterPhy.getClientProperties(), Properties.class); + if (props == null) { + props = new Properties(); + } + + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, clusterPhy.getBootstrapServers()); + + adminClient = KSPartialKafkaAdminClient.create(props); + + KSDescribeGroupsResult describeGroupsResult = adminClient.describeConsumerGroups( Arrays.asList(groupName), new DescribeConsumerGroupsOptions().timeoutMs(KafkaConstant.ADMIN_CLIENT_REQUEST_TIME_OUT_UNIT_MS).includeAuthorizedOperations(false) ); - return describeConsumerGroupsResult.all().get().get(groupName); + return describeGroupsResult.all().get().get(groupName); } catch(Exception e){ - log.error("method=getGroupDescription||clusterPhyId={}|groupName={}||errMsg=exception!", clusterPhyId, groupName, e); + log.error("method=getGroupDescription||clusterPhyId={}|groupName={}||errMsg=exception!", clusterPhy.getId(), groupName, e); throw new AdminOperateException(e.getMessage(), e, ResultStatus.KAFKA_OPERATE_FAILED); + } finally { + if (adminClient != null) { + try { + adminClient.close(Duration.ofSeconds(10)); + } catch (Exception e) { + // ignore + } + } } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java index c6b2cf3f..b01b8832 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java @@ -24,7 +24,7 @@ public abstract class AbstractHealthCheckService { Function, HealthCheckResult> > functionMap = new ConcurrentHashMap<>(); - public abstract List getResList(Long clusterPhyId); + public abstract List getResList(Long clusterId); public abstract HealthCheckDimensionEnum getHealthCheckDimensionEnum(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java new file mode 100644 index 00000000..566399b6 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java @@ -0,0 +1,95 @@ +package com.xiaojukeji.know.streaming.km.core.service.health.checker.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectClusterMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ConnectClusterParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +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.utils.Tuple; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterMetricService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectClusterMetricVersionItems.CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_FAILURE_PERCENTAGE; + +/** + * @author wyb + * @date 2022/11/9 + */ +@Service +public class HealthCheckConnectClusterService extends AbstractHealthCheckService { + + private static final ILog log = LogFactory.getLog(HealthCheckConnectClusterService.class); + + @Autowired + private ConnectClusterMetricService connectClusterMetricService; + + @PostConstruct + private void init() { + functionMap.putIfAbsent(HealthCheckNameEnum.CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE.getConfigName(), this::checkStartupFailurePercentage); + } + + @Override + public List getResList(Long connectClusterId) { + List paramList = new ArrayList<>(); + if (LoadedConnectClusterCache.containsByPhyId(connectClusterId)) { + paramList.add(new ConnectClusterParam(connectClusterId)); + } + return paramList; + } + + @Override + public HealthCheckDimensionEnum getHealthCheckDimensionEnum() { + return HealthCheckDimensionEnum.CONNECT_CLUSTER; + } + + private HealthCheckResult checkStartupFailurePercentage(Tuple paramTuple) { + ConnectClusterParam param = (ConnectClusterParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String metricName = CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_FAILURE_PERCENTAGE; + + Result ret = connectClusterMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, metricName); + + if (!ret.hasData()) { + log.error("method=checkStartupFailurePercentage||connectClusterId={}||metricName={}||errMsg=get metrics failed", + param.getConnectClusterId(), metricName); + return null; + } + + Float value = ret.getData().getMetric(metricName); + + if (value == null) { + log.error("method=checkStartupFailurePercentage||connectClusterId={}||metricName={}||errMsg=get metrics failed", + param.getConnectClusterId(), metricName); + return null; + } + + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectClusterId); + + HealthCheckResult checkResult = new HealthCheckResult( + HealthCheckDimensionEnum.CONNECT_CLUSTER.getDimension(), + HealthCheckNameEnum.CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE.getConfigName(), + connectCluster.getKafkaClusterPhyId(), + String.valueOf(connectClusterId) + ); + checkResult.setPassed(value <= compareConfig.getValue() ? Constant.YES : Constant.NO); + return checkResult; + + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java new file mode 100644 index 00000000..8f30ade8 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java @@ -0,0 +1,122 @@ +package com.xiaojukeji.know.streaming.km.core.service.health.checker.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +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.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.connect.ConnectorParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +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.utils.Tuple; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectorMetricVersionItems.CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectorMetricVersionItems.CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT; + +/** + * @author wyb + * @date 2022/11/8 + */ +@Service +public class HealthCheckConnectorService extends AbstractHealthCheckService { + + private static final ILog log = LogFactory.getLog(HealthCheckConnectorService.class); + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectorMetricService connectorMetricService; + + @PostConstruct + private void init() { + functionMap.putIfAbsent(HealthCheckNameEnum.CONNECTOR_FAILED_TASK_COUNT.getConfigName(), this::checkFailedTaskCount); + functionMap.putIfAbsent(HealthCheckNameEnum.CONNECTOR_UNASSIGNED_TASK_COUNT.getConfigName(), this::checkUnassignedTaskCount); + } + + @Override + public List getResList(Long connectClusterId) { + List paramList = new ArrayList<>(); + Result> ret = connectorService.listConnectorsFromCluster(connectClusterId); + if (!ret.hasData()) { + return paramList; + } + + for (String connectorName : ret.getData()) { + paramList.add(new ConnectorParam(connectClusterId, connectorName)); + } + + return paramList; + } + + @Override + public HealthCheckDimensionEnum getHealthCheckDimensionEnum() { + return HealthCheckDimensionEnum.CONNECTOR; + } + + private HealthCheckResult checkFailedTaskCount(Tuple paramTuple) { + ConnectorParam param = (ConnectorParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + Double compareValue = compareConfig.getValue(); + + return this.getHealthCompareResult(connectClusterId, connectorName, CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT, HealthCheckNameEnum.CONNECTOR_FAILED_TASK_COUNT, compareValue); + } + + private HealthCheckResult checkUnassignedTaskCount(Tuple paramTuple) { + ConnectorParam param = (ConnectorParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String connectorName = param.getConnectorName(); + Double compareValue = compareConfig.getValue(); + + return this.getHealthCompareResult(connectClusterId, connectorName, CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT, HealthCheckNameEnum.CONNECTOR_UNASSIGNED_TASK_COUNT, compareValue); + + } + + private HealthCheckResult getHealthCompareResult(Long connectClusterId, String connectorName, String metricName, HealthCheckNameEnum healthCheckNameEnum, Double compareValue) { + + Result ret = connectorMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, connectorName, metricName); + + if (!ret.hasData()) { + log.error("method=getHealthCompareResult||connectClusterId={}||connectorName={}||metricName={}||errMsg=get metrics failed", + connectClusterId, connectorName, metricName); + return null; + } + + Float value = ret.getData().getMetric(metricName); + + if (value == null) { + log.error("method=getHealthCompareResult||connectClusterId={}||connectorName={}||metricName={}||errMsg=get metrics failed", + connectClusterId, connectorName, metricName); + return null; + } + + HealthCheckResult checkResult = new HealthCheckResult( + HealthCheckDimensionEnum.CONNECTOR.getDimension(), + healthCheckNameEnum.getConfigName(), + connectClusterId, + connectorName + ); + checkResult.setPassed(compareValue >= value ? Constant.YES : Constant.NO); + return checkResult; + + } + + +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java index d5022cfc..62cf8d13 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java @@ -9,6 +9,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.Cluster import com.xiaojukeji.know.streaming.km.common.bean.entity.param.group.GroupParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchTerm; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; @@ -78,7 +79,7 @@ public class HealthCheckGroupService extends AbstractHealthCheckService { return null; } - checkResult.setPassed(countResult.getData() >= singleConfig.getDetectedTimes()? 0: 1); + checkResult.setPassed(countResult.getData() >= singleConfig.getDetectedTimes() ? Constant.NO : Constant.YES); return checkResult; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java index 2557fd5b..57537bf4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java @@ -109,7 +109,7 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { param.getTopicName() ); - checkResult.setPassed(partitionList.stream().filter(elem -> elem.getLeaderBrokerId().equals(Constant.INVALID_CODE)).count() >= valueConfig.getValue()? 0: 1); + checkResult.setPassed(partitionList.stream().filter(elem -> elem.getLeaderBrokerId().equals(Constant.INVALID_CODE)).count() >= valueConfig.getValue() ? Constant.NO : Constant.YES); return checkResult; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java index 66a48904..05346d0d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java @@ -24,4 +24,6 @@ public interface HealthCheckResultService { Map getClusterHealthConfig(Long clusterPhyId); void batchReplace(Long clusterPhyId, Integer dimension, List healthCheckResults); + + List getConnectorHealthCheckResult(Long clusterPhyId); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java index 2689c50c..4f0640c2 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java @@ -7,20 +7,26 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.Ba import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAggResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.po.config.PlatformClusterConfigPO; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectClusterPO; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; import com.xiaojukeji.know.streaming.km.common.enums.config.ConfigGroupEnum; 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.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.config.PlatformClusterConfigService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectClusterDAO; import com.xiaojukeji.know.streaming.km.persistence.mysql.health.HealthCheckResultDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import java.util.*; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum.CONNECTOR; @Service public class HealthCheckResultServiceImpl implements HealthCheckResultService { @@ -29,6 +35,9 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { @Autowired private HealthCheckResultDAO healthCheckResultDAO; + @Autowired + private ConnectClusterDAO connectClusterDAO; + @Autowired private PlatformClusterConfigService platformClusterConfigService; @@ -122,6 +131,25 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { return configMap; } + @Override + public List getConnectorHealthCheckResult(Long clusterPhyId) { + List resultPOList = new ArrayList<>(); + + //查找connect集群 + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectClusterPO::getKafkaClusterPhyId, clusterPhyId); + List connectClusterIdList = connectClusterDAO.selectList(lambdaQueryWrapper).stream().map(elem -> elem.getId()).collect(Collectors.toList()); + if (ValidateUtils.isEmptyList(connectClusterIdList)) { + return resultPOList; + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(HealthCheckResultPO::getDimension, CONNECTOR.getDimension()); + wrapper.in(HealthCheckResultPO::getClusterPhyId, connectClusterIdList); + resultPOList.addAll(healthCheckResultDAO.selectList(wrapper)); + return resultPOList; + } + @Override public void batchReplace(Long clusterPhyId, Integer dimension, List healthCheckResults) { List inDBList = this.listCheckResult(clusterPhyId, dimension); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java index 35692cb8..bfe9a1bb 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java @@ -2,6 +2,7 @@ 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.bean.entity.metrics.connect.ConnectorMetrics; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; import java.util.List; @@ -16,11 +17,13 @@ public interface HealthStateService { TopicMetrics calTopicHealthMetrics(Long clusterPhyId, String topicName); GroupMetrics calGroupHealthMetrics(Long clusterPhyId, String groupName); ZookeeperMetrics calZookeeperHealthMetrics(Long clusterPhyId); + ConnectorMetrics calConnectorHealthMetrics(Long connectClusterId, String connectorName); /** * 获取集群健康检查结果 */ List getClusterHealthResult(Long clusterPhyId); List getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum); - List getResHealthResult(Long clusterPhyId, Integer dimension, String resNme); + List getDimensionHealthResult(Long clusterPhyId, List dimensionCodeList); + List getResHealthResult(Long clusterPhyId, Long clusterId, Integer dimension, String resNme); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java index 5669f300..8193af86 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java @@ -2,24 +2,31 @@ package com.xiaojukeji.know.streaming.km.core.service.health.state.impl; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAggResult; 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.bean.entity.metrics.connect.ConnectorMetrics; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; 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.health.HealthStateEnum; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; +import java.util.List; +import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectorMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; @@ -38,6 +45,9 @@ public class HealthStateServiceImpl implements HealthStateService { @Autowired private BrokerService brokerService; + @Autowired + private ConnectClusterService connectClusterService; + @Override public ClusterMetrics calClusterHealthMetrics(Long clusterPhyId) { ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); @@ -59,6 +69,7 @@ public class HealthStateServiceImpl implements HealthStateService { metrics.putMetric(this.calClusterTopicsHealthMetrics(clusterPhyId).getMetrics()); metrics.putMetric(this.calClusterGroupsHealthMetrics(clusterPhyId).getMetrics()); metrics.putMetric(this.calZookeeperHealthMetrics(clusterPhyId).getMetrics()); + metrics.putMetric(this.calClusterConnectsHealthMetrics(clusterPhyId).getMetrics()); // 统计最终结果 Float passed = 0.0f; @@ -67,6 +78,7 @@ public class HealthStateServiceImpl implements HealthStateService { passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS); passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS); passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER); + passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CONNECTOR); Float total = 0.0f; total += metrics.getMetric(ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL); @@ -74,6 +86,7 @@ public class HealthStateServiceImpl implements HealthStateService { total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS); total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS); total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER); + total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CONNECTOR); // 状态 Float state = 0.0f; @@ -82,6 +95,7 @@ public class HealthStateServiceImpl implements HealthStateService { state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_BROKERS)); state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_GROUPS)); state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_CLUSTER)); + state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_CONNECTOR)); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED, passed); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL, total); @@ -184,6 +198,31 @@ public class HealthStateServiceImpl implements HealthStateService { return metrics; } + @Override + public ConnectorMetrics calConnectorHealthMetrics(Long connectClusterId, String connectorName) { + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectClusterId); + ConnectorMetrics metrics = new ConnectorMetrics(connectClusterId, connectorName); + + // 找不到connect集群 + if (connectCluster == null) { + metrics.putMetric(CONNECTOR_METRIC_HEALTH_STATE, (float) HealthStateEnum.DEAD.getDimension()); + return metrics; + } + + List resultList = healthCheckResultService.getHealthCheckAggResult(connectClusterId, HealthCheckDimensionEnum.CONNECTOR, connectorName); + + if (ValidateUtils.isEmptyList(resultList)) { + metrics.getMetrics().put(CONNECTOR_METRIC_HEALTH_CHECK_PASSED, 0.0f); + metrics.getMetrics().put(CONNECTOR_METRIC_HEALTH_CHECK_TOTAL, 0.0f); + } else { + metrics.getMetrics().put(CONNECTOR_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(resultList)); + metrics.getMetrics().put(CONNECTOR_METRIC_HEALTH_CHECK_TOTAL, (float) resultList.size()); + } + + metrics.putMetric(CONNECTOR_METRIC_HEALTH_STATE, (float) this.calHealthState(resultList).getDimension()); + return metrics; + } + @Override public List getClusterHealthResult(Long clusterPhyId) { List poList = healthCheckResultService.listCheckResult(clusterPhyId); @@ -199,8 +238,36 @@ public class HealthStateServiceImpl implements HealthStateService { } @Override - public List getResHealthResult(Long clusterPhyId, Integer dimension, String resNme) { - List poList = healthCheckResultService.listCheckResult(clusterPhyId, dimension, resNme); + public List getDimensionHealthResult(Long clusterPhyId, List dimensionCodeList) { + //查找健康巡查结果 + List poList = new ArrayList<>(); + for (Integer dimensionCode : dimensionCodeList) { + HealthCheckDimensionEnum dimensionEnum = HealthCheckDimensionEnum.getByCode(dimensionCode); + + if (dimensionEnum.equals(HealthCheckDimensionEnum.UNKNOWN)) { + continue; + } + + if (dimensionEnum.equals(HealthCheckDimensionEnum.CONNECTOR)) { + poList.addAll(healthCheckResultService.getConnectorHealthCheckResult(clusterPhyId)); + } else { + poList.addAll(healthCheckResultService.listCheckResult(clusterPhyId, dimensionEnum.getDimension())); + } + } + + List resultList = this.getResHealthResult(clusterPhyId, dimensionCodeList, poList); + return resultList; + + } + + @Override + public List getResHealthResult(Long clusterPhyId, Long clusterId, Integer dimension, String resNme) { + List poList = healthCheckResultService.listCheckResult(clusterId, dimension, resNme); + Map> checkResultMap = new HashMap<>(); + for (HealthCheckResultPO po: poList) { + checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); + checkResultMap.get(po.getConfigName()).add(po); + } return this.convert2HealthScoreResultList(clusterPhyId, poList, dimension); } @@ -272,6 +339,36 @@ public class HealthStateServiceImpl implements HealthStateService { return metrics; } + private ClusterMetrics calClusterConnectsHealthMetrics(Long clusterPhyId) { + //获取健康巡检结果 + List connectHealthCheckResult = healthCheckResultService.getConnectorHealthCheckResult(clusterPhyId); + + connectHealthCheckResult.addAll(healthCheckResultService.listCheckResult(clusterPhyId, CONNECT_CLUSTER.getDimension())); + + List dimensionCodeList = Arrays.asList(CONNECTOR.getDimension(), CONNECT_CLUSTER.getDimension()); + + List resultList = this.getDimensionHealthCheckAggResult(connectHealthCheckResult, dimensionCodeList); + + ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); + + if (ValidateUtils.isEmptyList(resultList)) { + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CONNECTOR, 0.0f); + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CONNECTOR, 0.0f); + } else { + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CONNECTOR, this.getHealthCheckPassed(resultList)); + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CONNECTOR, (float) resultList.size()); + } + + // 先根据connect集群状态判断 + if (connectClusterService.existConnectClusterDown(clusterPhyId)) { + metrics.putMetric(CLUSTER_METRIC_HEALTH_STATE_CONNECTOR, (float) HealthStateEnum.POOR.getDimension()); + return metrics; + } + + metrics.putMetric(CLUSTER_METRIC_HEALTH_STATE_CONNECTOR, (float) this.calHealthState(resultList).getDimension()); + return metrics; + } + /**************************************************** 聚合数据 ****************************************************/ @@ -305,6 +402,61 @@ public class HealthStateServiceImpl implements HealthStateService { /**************************************************** 计算指标 ****************************************************/ + private List getDimensionHealthCheckAggResult(List poList, List dimensionCodeList) { + Map /*检查结果列表*/> checkResultMap = new HashMap<>(); + + for (HealthCheckResultPO po : poList) { + checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); + checkResultMap.get(po.getConfigName()).add(po); + } + + List stateList = new ArrayList<>(); + for (Integer dimensionCode : dimensionCodeList) { + HealthCheckDimensionEnum dimensionEnum = HealthCheckDimensionEnum.getByCode(dimensionCode); + + if (dimensionEnum.equals(UNKNOWN)) { + continue; + } + + for (HealthCheckNameEnum nameEnum : HealthCheckNameEnum.getByDimension(dimensionEnum)) { + stateList.add(new HealthCheckAggResult(nameEnum, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); + } + } + return stateList; + } + + private List getResHealthResult(Long clusterPhyId, List dimensionCodeList, List poList) { + Map /*检查结果列表*/> checkResultMap = new HashMap<>(); + + for (HealthCheckResultPO po : poList) { + checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>()); + checkResultMap.get(po.getConfigName()).add(po); + } + + Map configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId); + + List healthScoreResultList = new ArrayList<>(); + for (Integer dimensionCode : dimensionCodeList) { + HealthCheckDimensionEnum dimensionEnum = HealthCheckDimensionEnum.getByCode(dimensionCode); + + //该维度不存在,则跳过 + if (dimensionEnum.equals(HealthCheckDimensionEnum.UNKNOWN)){ + continue; + } + + for (HealthCheckNameEnum nameEnum : HealthCheckNameEnum.getByDimension(dimensionEnum)) { + BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName()); + if (baseConfig == null) { + continue; + } + + healthScoreResultList.add(new HealthScoreResult(nameEnum, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))); + + } + } + return healthScoreResultList; + } + private float getHealthCheckPassed(List aggResultList){ if(ValidateUtils.isEmptyList(aggResultList)) { return 0f; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkauser/impl/KafkaUserServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkauser/impl/KafkaUserServiceImpl.java index e939f00d..3916ac9d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkauser/impl/KafkaUserServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkauser/impl/KafkaUserServiceImpl.java @@ -27,6 +27,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; import com.xiaojukeji.know.streaming.km.core.service.kafkauser.KafkaUserService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; @@ -54,7 +55,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. @Service -public class KafkaUserServiceImpl extends BaseVersionControlService implements KafkaUserService { +public class KafkaUserServiceImpl extends BaseKafkaVersionControlService implements KafkaUserService { private static final ILog log = LogFactory.getLog(KafkaUserServiceImpl.class); private static final String KAFKA_USER_REPLACE = "replaceKafkaUser"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java index 6dbb5816..838ac594 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/OpPartitionServiceImpl.java @@ -10,7 +10,7 @@ import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.core.service.partition.OpPartitionService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; import kafka.zk.KafkaZkClient; @@ -36,7 +36,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Service -public class OpPartitionServiceImpl extends BaseVersionControlService implements OpPartitionService { +public class OpPartitionServiceImpl extends BaseKafkaVersionControlService implements OpPartitionService { private static final ILog LOGGER = LogFactory.getLog(OpPartitionServiceImpl.class); @Autowired diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java index 63e9bc36..39a95c31 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionMetricServiceImpl.java @@ -3,7 +3,6 @@ package com.xiaojukeji.know.streaming.km.core.service.partition.impl; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.PartitionMetrics; -import com.xiaojukeji.know.streaming.km.common.bean.entity.offset.KSOffsetSpec; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.TopicMetricParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java index a2094028..83222090 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java @@ -26,7 +26,7 @@ import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.znode.brokers.PartitionMap; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.znode.brokers.PartitionState; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaConsumerClient; import com.xiaojukeji.know.streaming.km.persistence.mysql.partition.PartitionDAO; @@ -57,7 +57,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Service -public class PartitionServiceImpl extends BaseVersionControlService implements PartitionService { +public class PartitionServiceImpl extends BaseKafkaVersionControlService implements PartitionService { private static final ILog log = LogFactory.getLog(PartitionServiceImpl.class); private static final String PARTITION_OFFSET_GET = "getPartitionOffset"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignServiceImpl.java index 05ae8fb4..f406e10b 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignServiceImpl.java @@ -19,7 +19,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.reassign.ReassignService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; import kafka.admin.ReassignPartitionsCommand; @@ -42,7 +42,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.SERVICE_OP_REASSIGNMENT; @Service -public class ReassignServiceImpl extends BaseVersionControlService implements ReassignService { +public class ReassignServiceImpl extends BaseKafkaVersionControlService implements ReassignService { private static final ILog log = LogFactory.getLog(ReassignServiceImpl.class); private static final String EXECUTE_TASK = "executeTask"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java index 7cd017f4..5a7b0f76 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java @@ -20,7 +20,7 @@ import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistExcept import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; import com.xiaojukeji.know.streaming.km.core.service.topic.OpTopicService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.service.KafkaZKDAO; @@ -48,7 +48,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemT * @author didi */ @Service -public class OpTopicServiceImpl extends BaseVersionControlService implements OpTopicService { +public class OpTopicServiceImpl extends BaseKafkaVersionControlService implements OpTopicService { private static final ILog log = LogFactory.getLog(TopicConfigServiceImpl.class); private static final String TOPIC_CREATE = "createTopic"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java index 0149b5d4..735487b2 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicConfigServiceImpl.java @@ -26,7 +26,7 @@ import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicConfigService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.service.KafkaZKDAO; @@ -46,7 +46,7 @@ import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum. @Service -public class TopicConfigServiceImpl extends BaseVersionControlService implements TopicConfigService { +public class TopicConfigServiceImpl extends BaseKafkaVersionControlService implements TopicConfigService { private static final ILog log = LogFactory.getLog(TopicConfigServiceImpl.class); private static final String GET_TOPIC_CONFIG = "getTopicConfig"; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java new file mode 100644 index 00000000..5efc4438 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java @@ -0,0 +1,74 @@ +package com.xiaojukeji.know.streaming.km.core.service.version; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchQuery; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import org.springframework.util.CollectionUtils; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author wyb + * @date 2022/11/9 + */ +public abstract class BaseConnectorMetricService extends BaseConnectorVersionControlService{ + private static final ILog LOGGER = LogFactory.getLog(BaseMetricService.class); + + private List metricNames = new ArrayList<>(); + private List metricFields = new ArrayList<>(); + + @PostConstruct + public void init(){ + initMetricFieldAndNameList(); + initRegisterVCHandler(); + } + + protected void initMetricFieldAndNameList(){ + metricNames = listVersionControlItems().stream().map(v -> v.getName()).collect(Collectors.toList()); + metricFields = listMetricPOFields(); + } + + protected abstract List listMetricPOFields(); + + protected abstract void initRegisterVCHandler(); + + /** + * 检查 str 是不是一个 metricName + * @param str + */ + protected boolean isMetricName(String str){ + return metricNames.contains(str); + } + + /** + * 检查 str 是不是一个 fieldName + * @param str + */ + protected boolean isMetricField(String str){ + return metricFields.contains(str); + } + + protected void setQueryMetricFlag(SearchQuery query){ + if(null == query){return;} + + String fieldName = query.getQueryName(); + + query.setMetric(isMetricName(fieldName)); + query.setField(isMetricField(fieldName)); + } + + protected void setQueryMetricFlag(List matches){ + if(CollectionUtils.isEmpty(matches)){return;} + + for (SearchQuery match : matches){ + setQueryMetricFlag(match); + } + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorVersionControlService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorVersionControlService.java new file mode 100644 index 00000000..ced858ff --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorVersionControlService.java @@ -0,0 +1,55 @@ +package com.xiaojukeji.know.streaming.km.core.service.version; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionConnectJmxInfo; +import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nullable; + +/** + * @author wyb + * @date 2022/11/8 + */ +public abstract class BaseConnectorVersionControlService extends BaseVersionControlService { + + @Autowired + ConnectClusterService connectClusterService; + + @Nullable + protected Object doVCHandler(Long connectClusterId, String action, VersionItemParam param) throws VCHandlerNotExistException { + String versionStr = connectClusterService.getClusterVersion(connectClusterId); + + LOGGER.debug( + "method=doVCHandler||connectClusterId={}||action={}||type={}||param={}", + connectClusterId, action, getVersionItemType().getMessage(), ConvertUtil.obj2Json(param) + ); + + Tuple ret = doVCHandler(versionStr, action, param); + + LOGGER.debug( + "method=doVCHandler||clusterId={}||action={}||methodName={}||type={}||param={}||ret={}!", + connectClusterId, action, ret != null ?ret.getV2(): "", getVersionItemType().getMessage(), ConvertUtil.obj2Json(param), ConvertUtil.obj2Json(ret) + ); + + return ret == null? null: ret.getV1(); + } + + @Nullable + protected String getMethodName(Long connectClusterId, String action) { + String versionStr = connectClusterService.getClusterVersion(connectClusterId); + + return getMethodName(versionStr, action); + } + + @Nullable + protected VersionConnectJmxInfo getJMXInfo(Long connectClusterId, String action) { + String versionStr = connectClusterService.getClusterVersion(connectClusterId); + + return (VersionConnectJmxInfo) getJMXInfo(versionStr, action); + } + +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseKafkaVersionControlService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseKafkaVersionControlService.java new file mode 100644 index 00000000..45db0c18 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseKafkaVersionControlService.java @@ -0,0 +1,52 @@ +package com.xiaojukeji.know.streaming.km.core.service.version; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionJmxInfo; +import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nullable; + +/** + * @author didi + */ +public abstract class BaseKafkaVersionControlService extends BaseVersionControlService{ + @Autowired + private ClusterPhyService clusterPhyService; + + @Nullable + protected Object doVCHandler(Long clusterPhyId, String action, VersionItemParam param) throws VCHandlerNotExistException { + String versionStr = clusterPhyService.getVersionFromCacheFirst(clusterPhyId); + + LOGGER.info( + "method=doVCHandler||clusterId={}||action={}||type={}||param={}", + clusterPhyId, action, getVersionItemType().getMessage(), ConvertUtil.obj2Json(param) + ); + + Tuple ret = doVCHandler(versionStr, action, param); + + LOGGER.debug( + "method=doVCHandler||clusterId={}||action={}||methodName={}||type={}||param={}||ret={}!", + clusterPhyId, action, ret != null ?ret.getV2(): "", getVersionItemType().getMessage(), ConvertUtil.obj2Json(param), ConvertUtil.obj2Json(ret) + ); + + return ret == null? null: ret.getV1(); + } + + @Nullable + protected String getMethodName(Long clusterPhyId, String action) { + String versionStr = clusterPhyService.getVersionFromCacheFirst(clusterPhyId); + + return getMethodName(versionStr, action); + } + + @Nullable + protected VersionJmxInfo getJMXInfo(Long clusterPhyId, String action){ + String versionStr = clusterPhyService.getVersionFromCacheFirst(clusterPhyId); + + return getJMXInfo(versionStr, action); + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java index d97a5243..82f841e1 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java @@ -17,7 +17,7 @@ import java.util.stream.Collectors; /** * @author didi */ -public abstract class BaseMetricService extends BaseVersionControlService { +public abstract class BaseMetricService extends BaseKafkaVersionControlService { private static final ILog LOGGER = LogFactory.getLog(BaseMetricService.class); private List metricNames = new ArrayList<>(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java index cb35befd..06c06286 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseVersionControlService.java @@ -1,6 +1,5 @@ package com.xiaojukeji.know.streaming.km.core.service.version; -import com.alibaba.fastjson.JSON; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; @@ -10,6 +9,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMethod import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.util.CollectionUtils; @@ -56,20 +56,14 @@ public abstract class BaseVersionControlService { } @Nullable - protected Object doVCHandler(Long clusterPhyId, String action, VersionItemParam param) throws VCHandlerNotExistException { - String methodName = getMethodName(clusterPhyId, action); - Object ret = versionControlService.doHandler(getVersionItemType(), methodName, param); + protected Tuple doVCHandler(String version, String action, VersionItemParam param) throws VCHandlerNotExistException { + String methodName = getMethodName(version, action); - LOGGER.debug( - "method=doVCHandler||clusterId={}||action={}||methodName={}||type={}param={}||ret={}!", - clusterPhyId, action, methodName, getVersionItemType().getMessage(), JSON.toJSONString(param), JSON.toJSONString(ret) - ); - - return ret; + return new Tuple<>(versionControlService.doHandler(getVersionItemType(), methodName, param), methodName); } - protected String getMethodName(Long clusterId, String action) { - VersionControlItem item = versionControlService.getVersionControlItem(clusterId, getVersionItemType().getCode(), action); + protected String getMethodName(String version, String action) { + VersionControlItem item = versionControlService.getVersionControlItem(version, getVersionItemType().getCode(), action); if (null == item) { return ""; } @@ -81,8 +75,8 @@ public abstract class BaseVersionControlService { return ""; } - protected VersionJmxInfo getJMXInfo(Long clusterId, String action){ - VersionControlItem item = versionControlService.getVersionControlItem(clusterId, getVersionItemType().getCode(), action); + protected VersionJmxInfo getJMXInfo(String version, String action){ + VersionControlItem item = versionControlService.getVersionControlItem(version, getVersionItemType().getCode(), action); if (null == item) { return null; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/VersionControlService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/VersionControlService.java index 093722a2..f46399f4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/VersionControlService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/VersionControlService.java @@ -6,7 +6,6 @@ import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import java.util.List; -import java.util.Map; import java.util.function.Function; /** @@ -45,11 +44,11 @@ public interface VersionControlService { /** * 获取对应集群的版本兼容项 - * @param clusterId + * @param version * @param type * @return */ - List listVersionControlItem(Long clusterId, Integer type); + List listVersionControlItem(String version, Integer type); /** * 获取对应type所有的的版本兼容项 @@ -68,27 +67,18 @@ public interface VersionControlService { /** * 查询对应指标的版本兼容项 - * @param clusterId + * @param version * @param type * @param itemName * @return */ - VersionControlItem getVersionControlItem(Long clusterId, Integer type, String itemName); + VersionControlItem getVersionControlItem(String version, Integer type, String itemName); /** * 判断 item 是否被 clusterId 对应的版本支持 - * @param clusterId + * @param version * @param item * @return */ - boolean isClusterSupport(Long clusterId, VersionControlItem item); - - /** - * 查询对应指标的版本兼容项 - * @param clusterId - * @param type - * @param itemNames - * @return - */ - Map getVersionControlItems(Long clusterId, Integer type, List itemNames); + boolean isClusterSupport(String version, VersionControlItem item); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/impl/VersionControlServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/impl/VersionControlServiceImpl.java index de563b03..b70c5ee0 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/impl/VersionControlServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/impl/VersionControlServiceImpl.java @@ -7,11 +7,8 @@ import com.xiaojukeji.know.streaming.km.common.component.SpringTool; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.common.utils.VersionUtil; -import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlMetricService; import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -26,18 +23,24 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; -@Slf4j @DependsOn("springTool") @Service("versionControlService") public class VersionControlServiceImpl implements VersionControlService { + /** + * key:versionItemType + */ + private final Map> versionItemMap = new ConcurrentHashMap<>(); - @Autowired - private ClusterPhyService clusterPhyService; + /** + * key:versionItemType + * key1:metricName + */ + private final Map>> versionItemMetricNameMap = new ConcurrentHashMap<>(); - private final Map> versionItemMap = new ConcurrentHashMap<>(); - private final Map>> versionItemMetricNameMap = new ConcurrentHashMap<>(); - - private final Map> functionMap = new ConcurrentHashMap<>(); + /** + * key : VersionItemTypeEnum.code@methodName + */ + private final Map> functionMap = new ConcurrentHashMap<>(); @PostConstruct public void init(){ @@ -51,7 +54,7 @@ public class VersionControlServiceImpl implements VersionControlService { @Override public void registerHandler(VersionItemTypeEnum typeEnum, String methodName, Function func){ - functionMap.put(typeEnum.getCode() + "@" + methodName , func); + functionMap.put(versionFunctionKey(typeEnum.getCode(), methodName), func); } @Override @@ -76,24 +79,23 @@ public class VersionControlServiceImpl implements VersionControlService { itemMap.put(action, controlItems); versionItemMetricNameMap.put(typeCode, itemMap); - functionMap.put(typeCode + "@" + methodName , func); + functionMap.put(versionFunctionKey(typeCode, methodName), func); } @Nullable @Override public Object doHandler(VersionItemTypeEnum typeEnum, String methodName, VersionItemParam param) throws VCHandlerNotExistException { - Function func = functionMap.get(typeEnum.getCode() + "@" + methodName); + Function func = functionMap.get(versionFunctionKey(typeEnum.getCode(), methodName)); if(null == func) { - throw new VCHandlerNotExistException(typeEnum.getCode() + "@" + methodName); + throw new VCHandlerNotExistException(versionFunctionKey(typeEnum.getCode(), methodName)); } return func.apply(param); } @Override - public List listVersionControlItem(Long clusterId, Integer type) { - String versionStr = clusterPhyService.getVersionFromCacheFirst(clusterId); - long versionLong = VersionUtil.normailze(versionStr); + public List listVersionControlItem(String version, Integer type) { + long versionLong = VersionUtil.normailze(version); List items = versionItemMap.get(type); if(CollectionUtils.isEmpty(items)) { @@ -122,8 +124,8 @@ public class VersionControlServiceImpl implements VersionControlService { } @Override - public VersionControlItem getVersionControlItem(Long clusterId, Integer type, String itemName) { - List items = listVersionControlItem(clusterId, type); + public VersionControlItem getVersionControlItem(String version, Integer type, String itemName) { + List items = listVersionControlItem(version, type); for(VersionControlItem item : items){ if(itemName.equals(item.getName())){ @@ -135,24 +137,13 @@ public class VersionControlServiceImpl implements VersionControlService { } @Override - public boolean isClusterSupport(Long clusterId, VersionControlItem item){ - String versionStr = clusterPhyService.getVersionFromCacheFirst(clusterId); - long versionLong = VersionUtil.normailze(versionStr); - + public boolean isClusterSupport(String version, VersionControlItem item) { + long versionLong = VersionUtil.normailze(version); return item.getMinVersion() <= versionLong && versionLong < item.getMaxVersion(); } - @Override - public Map getVersionControlItems(Long clusterId, Integer type, List itemNames){ - Map versionControlItemMap = new HashMap<>(); - - for(String itemName : itemNames){ - VersionControlItem item = getVersionControlItem(clusterId, type, itemName); - if(null != item){ - versionControlItemMap.put(itemName, item); - } - } - - return versionControlItemMap; + /**************************************************** private method ****************************************************/ + private String versionFunctionKey(int typeCode, String methodName){ + return typeCode + "@" + methodName; } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BaseMetricVersionMetric.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BaseMetricVersionMetric.java index 41e702c7..7484fa2d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BaseMetricVersionMetric.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/BaseMetricVersionMetric.java @@ -1,8 +1,10 @@ package com.xiaojukeji.know.streaming.km.core.service.version.metrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionConnectJmxInfo; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMethodInfo; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionJmxInfo; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlMetricService; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum.V_0_10_0_0; @@ -58,4 +60,17 @@ public abstract class BaseMetricVersionMetric implements VersionControlMetricSer jmxExtendInfo.setMethodName(methodName); return jmxExtendInfo; } + + protected VersionConnectJmxInfo buildConnectJMXMethodExtend(String methodName) { + VersionConnectJmxInfo connectorJmxInfo = new VersionConnectJmxInfo(); + connectorJmxInfo.setMethodName(methodName); + return connectorJmxInfo; + } + + protected VersionConnectJmxInfo buildConnectJMXMethodExtend(String methodName, ConnectorTypeEnum type) { + VersionConnectJmxInfo connectorJmxInfo = new VersionConnectJmxInfo(); + connectorJmxInfo.setMethodName(methodName); + connectorJmxInfo.setType(type); + return connectorJmxInfo; + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectClusterMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectClusterMetricVersionItems.java new file mode 100644 index 00000000..09b7483d --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectClusterMetricVersionItems.java @@ -0,0 +1,110 @@ +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem.CATEGORY_CLUSTER; +import static com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem.CATEGORY_PERFORMANCE; +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_CLUSTER; +import static com.xiaojukeji.know.streaming.km.common.jmx.JmxAttribute.*; +import static com.xiaojukeji.know.streaming.km.common.jmx.JmxName.JMX_CONNECT_WORKER_METRIC; +import static com.xiaojukeji.know.streaming.km.core.service.connect.cluster.impl.ConnectClusterMetricServiceImpl.*; + + +@Component +public class ConnectClusterMetricVersionItems extends BaseMetricVersionMetric { + public static final String CONNECT_CLUSTER_METRIC_CONNECTOR_COUNT = "ConnectorCount"; + public static final String CONNECT_CLUSTER_METRIC_TASK_COUNT = "TaskCount"; + + public static final String CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_ATTEMPTS_TOTAL = "ConnectorStartupAttemptsTotal"; + + public static final String CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_FAILURE_PERCENTAGE = "ConnectorStartupFailurePercentage"; + + public static final String CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_FAILURE_TOTAL = "ConnectorStartupFailureTotal"; + + public static final String CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_SUCCESS_PERCENTAGE = "ConnectorStartupSuccessPercentage"; + + public static final String CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_SUCCESS_TOTAL = "ConnectorStartupSuccessTotal"; + + public static final String CONNECT_CLUSTER_METRIC_TASK_STARTUP_ATTEMPTS_TOTAL = "TaskStartupAttemptsTotal"; + + public static final String CONNECT_CLUSTER_METRIC_TASK_STARTUP_FAILURE_PERCENTAGE = "TaskStartupFailurePercentage"; + + public static final String CONNECT_CLUSTER_METRIC_TASK_STARTUP_FAILURE_TOTAL = "TaskStartupFailureTotal"; + + public static final String CONNECT_CLUSTER_METRIC_TASK_STARTUP_SUCCESS_PERCENTAGE = "TaskStartupSuccessPercentage"; + + public static final String CONNECT_CLUSTER_METRIC_TASK_STARTUP_SUCCESS_TOTAL = "TaskStartupSuccessTotal"; + + public static final String CONNECT_CLUSTER_METRIC_COLLECT_COST_TIME = Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME; + + + @Override + public int versionItemType() { + return METRIC_CONNECT_CLUSTER.getCode(); + } + + @Override + public List init() { + List items = new ArrayList<>(); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_CONNECTOR_COUNT).unit("个").desc("连接器数量").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_TASK_COUNT).unit("个").desc("任务数量").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_ATTEMPTS_TOTAL).unit("次").desc("连接器启动次数").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(CONNECTOR_STARTUP_ATTEMPTS_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_FAILURE_PERCENTAGE).unit("%").desc("连接器启动失败概率").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_AVG) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(CONNECTOR_STARTUP_FAILURE_PERCENTAGE))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_FAILURE_TOTAL).unit("次").desc("连接器启动失败次数").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(CONNECTOR_STARTUP_FAILURE_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_SUCCESS_PERCENTAGE).unit("%").desc("连接器启动成功概率").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_AVG) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(CONNECTOR_STARTUP_SUCCESS_PERCENTAGE))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_CONNECTOR_STARTUP_SUCCESS_TOTAL).unit("次").desc("连接器启动成功次数").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(CONNECTOR_STARTUP_SUCCESS_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_TASK_STARTUP_ATTEMPTS_TOTAL).unit("次").desc("任务启动次数").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_STARTUP_ATTEMPTS_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_TASK_STARTUP_FAILURE_PERCENTAGE).unit("%").desc("任务启动失败概率").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_AVG) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_STARTUP_FAILURE_PERCENTAGE))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_TASK_STARTUP_FAILURE_TOTAL).unit("次").desc("任务启动失败次数").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_STARTUP_FAILURE_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_TASK_STARTUP_SUCCESS_PERCENTAGE).unit("%").desc("任务启动成功概率").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_AVG) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_STARTUP_SUCCESS_PERCENTAGE))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_TASK_STARTUP_SUCCESS_TOTAL).unit("次").desc("任务启动成功次数").category(CATEGORY_CLUSTER) + .extend(buildConnectJMXMethodExtend(CONNECT_CLUSTER_METHOD_GET_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_METRIC).jmxAttribute(TASK_STARTUP_SUCCESS_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECT_CLUSTER_METRIC_COLLECT_COST_TIME).unit("秒").desc("采集connect集群指标耗时").category(CATEGORY_PERFORMANCE) + .extendMethod(CONNECT_CLUSTER_METHOD_DO_NOTHING)); + return items; + } + +} + diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java new file mode 100644 index 00000000..cb9ddd07 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java @@ -0,0 +1,310 @@ +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem.*; +import static com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum.SINK; +import static com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum.SOURCE; +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_CONNECTOR; +import static com.xiaojukeji.know.streaming.km.common.jmx.JmxAttribute.*; +import static com.xiaojukeji.know.streaming.km.common.jmx.JmxName.*; +import static com.xiaojukeji.know.streaming.km.core.service.broker.impl.BrokerMetricServiceImpl.BROKER_METHOD_DO_NOTHING; +import static com.xiaojukeji.know.streaming.km.core.service.connect.connector.impl.ConnectorMetricServiceImpl.*; + + +@Component +public class ConnectorMetricVersionItems extends BaseMetricVersionMetric { + + public static final String CONNECTOR_METRIC_COLLECT_COST_TIME = Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME; + + public static final String CONNECTOR_METRIC_HEALTH_STATE = "HealthState"; + + public static final String CONNECTOR_METRIC_CONNECTOR_TOTAL_TASK_COUNT = "ConnectorTotalTaskCount"; + + public static final String CONNECTOR_METRIC_HEALTH_CHECK_PASSED = "HealthCheckPassed"; + + public static final String CONNECTOR_METRIC_HEALTH_CHECK_TOTAL = "HealthCheckTotal"; + + public static final String CONNECTOR_METRIC_CONNECTOR_RUNNING_TASK_COUNT = "ConnectorRunningTaskCount"; + + public static final String CONNECTOR_METRIC_CONNECTOR_PAUSED_TASK_COUNT = "ConnectorPausedTaskCount"; + + public static final String CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT = "ConnectorFailedTaskCount"; + + public static final String CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT = "ConnectorUnassignedTaskCount"; + + public static final String CONNECTOR_METRIC_BATCH_SIZE_AVG = "BatchSizeAvg"; + + public static final String CONNECTOR_METRIC_BATCH_SIZE_MAX = "BatchSizeMax"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_AVG_TIME_MS = "OffsetCommitAvgTimeMs"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_MAX_TIME_MS = "OffsetCommitMaxTimeMs"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_FAILURE_PERCENTAGE = "OffsetCommitFailurePercentage"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_SUCCESS_PERCENTAGE = "OffsetCommitSuccessPercentage"; + + public static final String CONNECTOR_METRIC_POLL_BATCH_AVG_TIME_MS = "PollBatchAvgTimeMs"; + + public static final String CONNECTOR_METRIC_POLL_BATCH_MAX_TIME_MS = "PollBatchMaxTimeMs"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_ACTIVE_COUNT = "SourceRecordActiveCount"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_ACTIVE_COUNT_AVG = "SourceRecordActiveCountAvg"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_ACTIVE_COUNT_MAX = "SourceRecordActiveCountMax"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_POLL_RATE = "SourceRecordPollRate"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_POLL_TOTAL = "SourceRecordPollTotal"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_WRITE_RATE = "SourceRecordWriteRate"; + + public static final String CONNECTOR_METRIC_SOURCE_RECORD_WRITE_TOTAL = "SourceRecordWriteTotal"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_COMPLETION_RATE = "OffsetCommitCompletionRate"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_COMPLETION_TOTAL = "OffsetCommitCompletionTotal"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_SKIP_RATE = "OffsetCommitSkipRate"; + + public static final String CONNECTOR_METRIC_OFFSET_COMMIT_SKIP_TOTAL = "OffsetCommitSkipTotal"; + + public static final String CONNECTOR_METRIC_PARTITION_COUNT = "PartitionCount"; + + public static final String CONNECTOR_METRIC_PUT_BATCH_AVG_TIME_MS = "PutBatchAvgTimeMs"; + + public static final String CONNECTOR_METRIC_PUT_BATCH_MAX_TIME_MS = "PutBatchMaxTimeMs"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_ACTIVE_COUNT = "SinkRecordActiveCount"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_ACTIVE_COUNT_AVG = "SinkRecordActiveCountAvg"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_ACTIVE_COUNT_MAX = "SinkRecordActiveCountMax"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_LAG_MAX = "SinkRecordLagMax"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_READ_RATE = "SinkRecordReadRate"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_READ_TOTAL = "SinkRecordReadTotal"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_SEND_RATE = "SinkRecordSendRate"; + + public static final String CONNECTOR_METRIC_SINK_RECORD_SEND_TOTAL = "SinkRecordSendTotal"; + + public static final String CONNECTOR_METRIC_DEADLETTERQUEUE_PRODUCE_FAILURES = "DeadletterqueueProduceFailures"; + + public static final String CONNECTOR_METRIC_DEADLETTERQUEUE_PRODUCE_REQUESTS = "DeadletterqueueProduceRequests"; + + public static final String CONNECTOR_METRIC_LAST_ERROR_TIMESTAMP = "LastErrorTimestamp"; + + public static final String CONNECTOR_METRIC_TOTAL_ERRORS_LOGGED = "TotalErrorsLogged"; + + public static final String CONNECTOR_METRIC_TOTAL_RECORD_ERRORS = "TotalRecordErrors"; + + public static final String CONNECTOR_METRIC_TOTAL_RECORD_FAILURES = "TotalRecordFailures"; + + public static final String CONNECTOR_METRIC_TOTAL_RECORDS_SKIPPED = "TotalRecordsSkipped"; + + public static final String CONNECTOR_METRIC_TOTAL_RETRIES = "TotalRetries"; + + @Override + public int versionItemType() { + return METRIC_CONNECT_CONNECTOR.getCode(); + } + + @Override + public List init() { + List items = new ArrayList<>(); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_COLLECT_COST_TIME).unit("秒").desc("采集connector指标的耗时").category(CATEGORY_PERFORMANCE) + .extendMethod(CONNECTOR_METHOD_DO_NOTHING)); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_HEALTH_STATE).unit("0:好 1:中 2:差 3:宕机").desc("健康状态(0:好 1:中 2:差 3:宕机)").category(CATEGORY_HEALTH) + .extendMethod(CONNECTOR_METHOD_GET_METRIC_HEALTH_SCORE)); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_CONNECTOR_TOTAL_TASK_COUNT).unit("个").desc("所有任务数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_CONNECTOR_METRIC).jmxAttribute(CONNECTOR_TOTAL_TASK_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_CONNECTOR_RUNNING_TASK_COUNT).unit("个").desc("运行状态的任务数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_CONNECTOR_METRIC).jmxAttribute(CONNECTOR_RUNNING_TASK_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_CONNECTOR_PAUSED_TASK_COUNT).unit("个").desc("暂停状态的任务数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_CONNECTOR_METRIC).jmxAttribute(CONNECTOR_PAUSED_TASK_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT).unit("个").desc("失败状态的任务数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_CONNECTOR_METRIC).jmxAttribute(CONNECTOR_FAILED_TASK_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT).unit("个").desc("未被分配的任务数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM) + .jmxObjectName(JMX_CONNECT_WORKER_CONNECTOR_METRIC).jmxAttribute(CONNECTOR_UNASSIGNED_TASK_COUNT))); + + + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_BATCH_SIZE_AVG).unit("条").desc("批次数量平均值").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG) + .jmxObjectName(JMX_CONNECTOR_TASK_CONNECTOR_METRIC).jmxAttribute(BATCH_SIZE_AVG))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_BATCH_SIZE_MAX).unit("条").desc("批次数量最大值").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX) + .jmxObjectName(JMX_CONNECTOR_TASK_CONNECTOR_METRIC).jmxAttribute(BATCH_SIZE_MAX))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_AVG_TIME_MS).unit("ms").desc("位点提交平均耗时").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG) + .jmxObjectName(JMX_CONNECTOR_TASK_CONNECTOR_METRIC).jmxAttribute(OFFSET_COMMIT_AVG_TIME_MS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_MAX_TIME_MS).unit("ms").desc("位点提交最大耗时").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX) + .jmxObjectName(JMX_CONNECTOR_TASK_CONNECTOR_METRIC).jmxAttribute(OFFSET_COMMIT_MAX_TIME_MS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_FAILURE_PERCENTAGE).unit("%").desc("位点提交失败概率").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG) + .jmxObjectName(JMX_CONNECTOR_TASK_CONNECTOR_METRIC).jmxAttribute(OFFSET_COMMIT_FAILURE_PERCENTAGE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_SUCCESS_PERCENTAGE).unit("%").desc("位点提交成功概率").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG) + .jmxObjectName(JMX_CONNECTOR_TASK_CONNECTOR_METRIC).jmxAttribute(OFFSET_COMMIT_SUCCESS_PERCENTAGE))); + + + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_POLL_BATCH_AVG_TIME_MS).unit("ms").desc("POLL平均耗时").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(POLL_BATCH_AVG_TIME_MS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_POLL_BATCH_MAX_TIME_MS).unit("ms").desc("POLL最大耗时").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(POLL_BATCH_MAX_TIME_MS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_ACTIVE_COUNT).unit("条").desc("pending状态消息数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_ACTIVE_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_ACTIVE_COUNT_AVG).unit("条").desc("pending状态平均消息数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_ACTIVE_COUNT_AVG))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_ACTIVE_COUNT_MAX).unit("条").desc("pending状态最大消息数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_ACTIVE_COUNT_MAX))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_POLL_RATE).unit(BYTE_PER_SEC).desc("消息读取速率").category(CATEGORY_FLOW) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_POLL_RATE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_POLL_TOTAL).unit("条").desc("消息读取总数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_POLL_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_WRITE_RATE).unit(BYTE_PER_SEC).desc("消息写入速率").category(CATEGORY_FLOW) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_WRITE_RATE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SOURCE_RECORD_WRITE_TOTAL).unit("条").desc("消息写入总数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SOURCE) + .jmxObjectName(JMX_CONNECTOR_SOURCE_TASK_METRICS).jmxAttribute(SOURCE_RECORD_WRITE_TOTAL))); + + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_COMPLETION_RATE).unit(BYTE_PER_SEC).desc("成功的位点提交速率").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(OFFSET_COMMIT_COMPLETION_RATE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_COMPLETION_TOTAL).unit("个").desc("成功的位点提交总数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(OFFSET_COMMIT_COMPLETION_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_SKIP_RATE).unit("").desc("被跳过的位点提交速率").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(OFFSET_COMMIT_SKIP_RATE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_OFFSET_COMMIT_SKIP_TOTAL).unit("").desc("被跳过的位点提交总数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(OFFSET_COMMIT_SKIP_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_PARTITION_COUNT).unit("个").desc("被分配到的分区数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(PARTITION_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_PUT_BATCH_AVG_TIME_MS).unit("ms").desc("PUT平均耗时").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_AVG, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(PUT_BATCH_AVG_TIME_MS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_PUT_BATCH_MAX_TIME_MS).unit("ms").desc("PUT最大耗时").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(PUT_BATCH_MAX_TIME_MS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_ACTIVE_COUNT).unit("条").desc("pending状态消息数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_ACTIVE_COUNT))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_ACTIVE_COUNT_AVG).unit("条").desc("pending状态平均消息数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_ACTIVE_COUNT_AVG))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_ACTIVE_COUNT_MAX).unit("条").desc("pending状态最大消息数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_ACTIVE_COUNT_MAX))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_READ_RATE).unit(BYTE_PER_SEC).desc("消息读取速率").category(CATEGORY_FLOW) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_READ_RATE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_READ_TOTAL).unit("条").desc("消息读取总数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_READ_TOTAL))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_SEND_RATE).unit(BYTE_PER_SEC).desc("消息写入速率").category(CATEGORY_FLOW) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_SEND_RATE))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_SINK_RECORD_SEND_TOTAL).unit("条").desc("消息写入总数量").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM, SINK) + .jmxObjectName(JMX_CONNECTOR_SINK_TASK_METRICS).jmxAttribute(SINK_RECORD_SEND_TOTAL))); + + + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_DEADLETTERQUEUE_PRODUCE_FAILURES).unit("次").desc("死信队列写入失败数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(DEADLETTERQUEUE_PRODUCE_FAILURES))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_DEADLETTERQUEUE_PRODUCE_REQUESTS).unit("次").desc("死信队列写入数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(DEADLETTERQUEUE_PRODUCE_REQUESTS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_LAST_ERROR_TIMESTAMP).unit("").desc("最后一次错误时间").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_MAX) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(LAST_ERROR_TIMESTAMP))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_TOTAL_ERRORS_LOGGED).unit("条").desc("记录日志的错误消息数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(TOTAL_ERRORS_LOGGED))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_TOTAL_RECORD_ERRORS).unit("次").desc("消息处理错误的次数(异常消息数量)").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(TOTAL_RECORD_ERRORS))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_TOTAL_RECORD_FAILURES).unit("次").desc("消息处理失败的次数(每次retry处理失败都会+1)").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(TOTAL_RECORD_FAILURES))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_TOTAL_RECORDS_SKIPPED).unit("条").desc("因为失败导致跳过(未处理)的消息数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(TOTAL_RECORDS_SKIPPED))); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_TOTAL_RETRIES).unit("次").desc("失败重试的次数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECTOR_TASK_METRICS_SUM) + .jmxObjectName(JMX_CONNECTOR_TASK_ERROR_METRICS).jmxAttribute(TOTAL_RETRIES))); + return items; + } +} + diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java new file mode 100644 index 00000000..b5256e31 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java @@ -0,0 +1,27 @@ +package com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; +import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_MIRROR_MAKER; + +@Component +public class MirrorMakerMetricVersionItems extends BaseMetricVersionMetric { + + @Override + public int versionItemType() { + return METRIC_CONNECT_MIRROR_MAKER.getCode(); + } + + @Override + public List init(){ + List items = new ArrayList<>(); + + return items; + } +} + diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java index c19bebc6..5a72b38c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java @@ -55,6 +55,13 @@ public class ClusterMetricVersionItems extends BaseMetricVersionMetric { public static final String CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER = "HealthCheckPassed_Cluster"; public static final String CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER = "HealthCheckTotal_Cluster"; + /** + * connector健康指标 + */ + public static final String CLUSTER_METRIC_HEALTH_STATE_CONNECTOR = "HealthState_Connector"; + public static final String CLUSTER_METRIC_HEALTH_CHECK_PASSED_CONNECTOR = "HealthCheckPassed_Connector"; + public static final String CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CONNECTOR = "HealthCheckTotal_Connector"; + public static final String CLUSTER_METRIC_TOTAL_REQ_QUEUE_SIZE = "TotalRequestQueueSize"; public static final String CLUSTER_METRIC_TOTAL_RES_QUEUE_SIZE = "TotalResponseQueueSize"; public static final String CLUSTER_METRIC_EVENT_QUEUE_SIZE = "EventQueueSize"; diff --git a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/common/AbstractMonitorSinkTag.java b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/common/AbstractMonitorSinkTag.java deleted file mode 100644 index dba9e12c..00000000 --- a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/common/AbstractMonitorSinkTag.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.xiaojukeji.know.streaming.km.monitor.common; - -import java.util.Map; - -/** - * @author zengqiao - * @date 20/5/24 - */ -public abstract class AbstractMonitorSinkTag { - - public abstract String convert2Tags(); - - public abstract Map tagsMap(); -} \ No newline at end of file diff --git a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java index 47288792..1fccaf90 100644 --- a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java +++ b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java @@ -4,7 +4,7 @@ import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.*; import com.xiaojukeji.know.streaming.km.common.bean.event.metric.*; -import com.xiaojukeji.know.streaming.km.common.utils.NamedThreadFactory; +import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil; import com.xiaojukeji.know.streaming.km.monitor.common.MetricSinkPoint; import org.springframework.context.ApplicationListener; @@ -12,9 +12,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import static com.xiaojukeji.know.streaming.km.monitor.common.MonitorSinkTagEnum.*; @@ -23,10 +20,12 @@ public abstract class AbstractMonitorSinkService implements ApplicationListener< private static final int STEP = 60; - private ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 6000, TimeUnit.MILLISECONDS, - new LinkedBlockingDeque<>(1000), - new NamedThreadFactory("KM-Monitor-Sink-" + monitorName()), - (r, e) -> LOGGER.warn("class=AbstractMonitorSinkService||msg=Deque is blocked, taskCount:{}" + e.getTaskCount())); + private FutureUtil sinkTP = FutureUtil.init( + "SinkMetricsTP", + 5, + 5, + 10000 + ); /** * monitor 服务的名称 @@ -36,7 +35,7 @@ public abstract class AbstractMonitorSinkService implements ApplicationListener< @Override public void onApplicationEvent(BaseMetricEvent event) { - executor.execute( () -> { + sinkTP.submitTask(() -> { if (event instanceof BrokerMetricEvent) { BrokerMetricEvent brokerMetricEvent = (BrokerMetricEvent)event; sinkMetrics(brokerMetric2SinkPoint(brokerMetricEvent.getBrokerMetrics())); @@ -194,10 +193,10 @@ public abstract class AbstractMonitorSinkService implements ApplicationListener< Map tagsMap) { List pointList = new ArrayList<>(); - for(String metricName : metrics.keySet()){ + for(Map.Entry entry: metrics.entrySet()){ MetricSinkPoint metricSinkPoint = new MetricSinkPoint(); - metricSinkPoint.setName(metricPre + "_" + metricName); - metricSinkPoint.setValue(metrics.get(metricName)); + metricSinkPoint.setName(metricPre + "_" + entry.getKey()); + metricSinkPoint.setValue(entry.getValue()); metricSinkPoint.setTimestamp(timeStamp); metricSinkPoint.setStep(STEP); metricSinkPoint.setTagsMap(tagsMap); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/AbstractConnectClusterChangeHandler.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/AbstractConnectClusterChangeHandler.java new file mode 100644 index 00000000..056ac89c --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/AbstractConnectClusterChangeHandler.java @@ -0,0 +1,44 @@ +package com.xiaojukeji.know.streaming.km.persistence.connect; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.event.cluster.connect.ConnectClusterLoadChangedEvent; +import org.springframework.context.ApplicationListener; + +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author wyb + * @date 2022/11/7 + */ +public abstract class AbstractConnectClusterChangeHandler implements ApplicationListener { + + private static final ILog log = LogFactory.getLog(AbstractConnectClusterChangeHandler.class); + + protected final ReentrantLock modifyClientMapLock = new ReentrantLock(); + + protected abstract void add(ConnectCluster connectCluster); + + protected abstract void modify(ConnectCluster newConnectCluster, ConnectCluster oldConnectCluster); + + protected abstract void remove(ConnectCluster connectCluster); + + + @Override + public void onApplicationEvent(ConnectClusterLoadChangedEvent event) { + switch (event.getOperationEnum()) { + case ADD: + this.add(event.getInDBConnectCluster()); + break; + case EDIT: + this.modify(event.getInDBConnectCluster(), event.getInCacheConnectCluster()); + break; + case DELETE: + this.remove(event.getInCacheConnectCluster()); + break; + default: + log.error("method=onApplicationEvent||event={}||msg=illegal event", event); + } + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/ConnectJMXClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/ConnectJMXClient.java new file mode 100644 index 00000000..727ad7f6 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/ConnectJMXClient.java @@ -0,0 +1,146 @@ +package com.xiaojukeji.know.streaming.km.persistence.connect; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectWorkerPO; +import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectWorkerDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author wyb + * @date 2022/10/31 + */ +@Component +public class ConnectJMXClient extends AbstractConnectClusterChangeHandler { + private static final ILog log = LogFactory.getLog(ConnectJMXClient.class); + + private static final Map> JMX_MAP = new ConcurrentHashMap<>(); + + @Autowired + private ConnectWorkerDAO connectWorkerDAO; + + + public JmxConnectorWrap getClientWithCheck(Long connectClusterId, String workerId) { + JmxConnectorWrap jmxConnectorWrap = this.getClient(connectClusterId, workerId); + + if (ValidateUtils.isNull(jmxConnectorWrap) || !jmxConnectorWrap.checkJmxConnectionAndInitIfNeed()) { + log.error("method=getClientWithCheck||connectClusterId={}||workerId={}||msg=get jmx connector failed!", connectClusterId, workerId); + return null; + } + + return jmxConnectorWrap; + } + + public JmxConnectorWrap getClient(Long connectorClusterId, String workerId) { + Map jmxMap = JMX_MAP.getOrDefault(connectorClusterId, new ConcurrentHashMap<>()); + + JmxConnectorWrap jmxConnectorWrap = jmxMap.get(workerId); + if (jmxConnectorWrap != null) { + // 已新建成功,则直接返回 + return jmxConnectorWrap; + } + + // 未创建,则进行创建 + return this.createJmxConnectorWrap(connectorClusterId, workerId); + } + + private JmxConnectorWrap createJmxConnectorWrap(Long connectorClusterId, String workerId) { + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectorClusterId); + if (connectCluster == null) { + return null; + } + return this.createJmxConnectorWrap(connectCluster, workerId); + } + + private JmxConnectorWrap createJmxConnectorWrap(ConnectCluster connectCluster, String workerId) { + ConnectWorker connectWorker = this.getConnectWorkerFromDB(connectCluster.getId(), workerId); + if (connectWorker == null) { + return null; + } + + try { + modifyClientMapLock.lock(); + + JmxConnectorWrap jmxConnectorWrap = JMX_MAP.getOrDefault(connectCluster.getId(), new ConcurrentHashMap<>()).get(workerId); + if (jmxConnectorWrap != null) { + return jmxConnectorWrap; + } + + log.debug("method=createJmxConnectorWrap||connectClusterId={}||workerId={}||msg=create JmxConnectorWrap starting", connectCluster.getId(), workerId); + + JmxConfig jmxConfig = ConvertUtil.str2ObjByJson(connectCluster.getJmxProperties(), JmxConfig.class); + if (jmxConfig == null) { + jmxConfig = new JmxConfig(); + } + + + jmxConnectorWrap = new JmxConnectorWrap( + "connectClusterId: " + connectCluster.getId() + " workerId: " + workerId, + null, + connectWorker.getHost(), + connectWorker.getJmxPort() != null ? connectWorker.getJmxPort() : jmxConfig.getJmxPort(), + jmxConfig + ); + + Map workerMap = JMX_MAP.getOrDefault(connectCluster.getId(), new ConcurrentHashMap<>()); + workerMap.put(workerId, jmxConnectorWrap); + JMX_MAP.put(connectCluster.getId(), workerMap); + return jmxConnectorWrap; + } catch (Exception e) { + log.debug("method=createJmxConnectorWrap||connectClusterId={}||workerId={}||msg=create JmxConnectorWrap failed||errMsg=exception||", connectCluster.getId(), workerId, e); + } finally { + modifyClientMapLock.unlock(); + } + return null; + } + + + private ConnectWorker getConnectWorkerFromDB(Long connectorClusterId, String workerId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectWorkerPO::getConnectClusterId, connectorClusterId); + lambdaQueryWrapper.eq(ConnectWorkerPO::getWorkerId, workerId); + ConnectWorkerPO connectWorkerPO = connectWorkerDAO.selectOne(lambdaQueryWrapper); + if (connectWorkerPO == null) { + return null; + } + return ConvertUtil.obj2Obj(connectWorkerPO, ConnectWorker.class); + } + + + @Override + protected void add(ConnectCluster connectCluster) { + JMX_MAP.putIfAbsent(connectCluster.getId(), new ConcurrentHashMap<>()); + } + + @Override + protected void modify(ConnectCluster newConnectCluster, ConnectCluster oldConnectCluster) { + if (newConnectCluster.getJmxProperties().equals(oldConnectCluster.getJmxProperties())) { + return; + } + this.remove(newConnectCluster); + this.add(newConnectCluster); + } + + @Override + protected void remove(ConnectCluster connectCluster) { + Map jmxMap = JMX_MAP.remove(connectCluster.getId()); + if (jmxMap == null) { + return; + } + for (JmxConnectorWrap jmxConnectorWrap : jmxMap.values()) { + jmxConnectorWrap.close(); + } + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/cache/LoadedConnectClusterCache.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/cache/LoadedConnectClusterCache.java new file mode 100644 index 00000000..bdc5bf1d --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/cache/LoadedConnectClusterCache.java @@ -0,0 +1,37 @@ +package com.xiaojukeji.know.streaming.km.persistence.connect.cache; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author wyb + * @date 2022/11/7 + */ +public class LoadedConnectClusterCache { + private static final Map CONNECT_CLUSTER_MAP = new ConcurrentHashMap<>(); + + public static boolean containsByPhyId(Long connectClusterId) { + return CONNECT_CLUSTER_MAP.containsKey(connectClusterId); + } + + public static ConnectCluster getByPhyId(Long connectClusterId) { + return CONNECT_CLUSTER_MAP.get(connectClusterId); + } + + public static ConnectCluster remove(Long connectClusterId) { + return CONNECT_CLUSTER_MAP.remove(connectClusterId); + } + + public static void replace(ConnectCluster connectCluster) { + CONNECT_CLUSTER_MAP.put(connectCluster.getId(), connectCluster); + } + + public static Map listAll() { + return CONNECT_CLUSTER_MAP; + } + + +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/schedule/ScheduleFlushConnectClusterTask.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/schedule/ScheduleFlushConnectClusterTask.java new file mode 100644 index 00000000..b19ebf78 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/connect/schedule/ScheduleFlushConnectClusterTask.java @@ -0,0 +1,104 @@ +package com.xiaojukeji.know.streaming.km.persistence.connect.schedule; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.event.cluster.connect.ConnectClusterLoadChangedEvent; +import com.xiaojukeji.know.streaming.km.common.component.SpringTool; +import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectClusterDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Component +public class ScheduleFlushConnectClusterTask { + private static final ILog log = LogFactory.getLog(ScheduleFlushConnectClusterTask.class); + + @Autowired + private ConnectClusterDAO connectClusterDAO; + + private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(2000); + + private final Thread handleEventThread = new Thread(() -> handleEvent(), "ScheduleFlushConnectClusterTask"); + + @PostConstruct + public void init() { + // 启动线程 + handleEventThread.start(); + + // 立即加载集群 + flush(); + } + + @Scheduled(cron="0/10 * * * * ?") + public void flush() { + List inDBConnectClusterList = ConvertUtil.list2List(connectClusterDAO.selectList(null), ConnectCluster.class); + Map inDBConnectClusterMap = inDBConnectClusterList.stream().collect(Collectors.toMap(ConnectCluster::getId, Function.identity())); + + //排查新增 + for (ConnectCluster inDBConnectCluster : inDBConnectClusterList) { + ConnectCluster inCacheConnectCluster = LoadedConnectClusterCache.getByPhyId(inDBConnectCluster.getId()); + //存在,查看是否需要替换 + if (inCacheConnectCluster != null) { + if (inCacheConnectCluster.equals(inDBConnectCluster)) { + continue; + } + LoadedConnectClusterCache.replace(inCacheConnectCluster); + this.put2Queue(new ConnectClusterLoadChangedEvent(this, inDBConnectCluster, inCacheConnectCluster, OperationEnum.EDIT)); + + } else { + LoadedConnectClusterCache.replace(inDBConnectCluster); + this.put2Queue(new ConnectClusterLoadChangedEvent(this, inDBConnectCluster, null, OperationEnum.ADD)); + } + + } + + //排查删除 + for (ConnectCluster inCacheConnectCluster : LoadedConnectClusterCache.listAll().values()) { + if (inDBConnectClusterMap.containsKey(inCacheConnectCluster.getId())) { + continue; + } + LoadedConnectClusterCache.remove(inCacheConnectCluster.getId()); + this.put2Queue(new ConnectClusterLoadChangedEvent(this, null, inCacheConnectCluster, OperationEnum.DELETE)); + } + + } + + + private void put2Queue(ConnectClusterLoadChangedEvent event) { + try { + eventQueue.put(event); + } catch (Exception e) { + log.error("method=put2Queue||event={}||errMsg=exception", event, e); + } + } + + + + + private void handleEvent() { + while (true) { + try { + ConnectClusterLoadChangedEvent event = eventQueue.take(); + SpringTool.publish(event); + } catch (Exception e) { + log.error("method=handleEvent||errMsg=exception", e); + } + } + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java index e95d3c43..ec261b18 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/jmx/impl/JmxDAOImpl.java @@ -26,7 +26,7 @@ public class JmxDAOImpl implements JmxDAO { public Object getJmxValue(Long clusterPhyId, String jmxHost, Integer jmxPort, JmxConfig jmxConfig, ObjectName objectName, String attribute) { JmxConnectorWrap jmxConnectorWrap = null; try { - jmxConnectorWrap = new JmxConnectorWrap(clusterPhyId, null, null, jmxHost, jmxPort, jmxConfig); + jmxConnectorWrap = new JmxConnectorWrap("clusterPhyId: " + clusterPhyId, null, jmxHost, jmxPort, jmxConfig); if (!jmxConnectorWrap.checkJmxConnectionAndInitIfNeed()) { log.error( "method=getJmxValue||clusterPhyId={}||jmxHost={}||jmxPort={}||jmxConfig={}||errMgs=create jmx client failed", diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java index 11e5ae36..1ace6742 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaJMXClient.java @@ -159,8 +159,7 @@ public class KafkaJMXClient extends AbstractClusterLoadedChangedHandler { } JmxConnectorWrap jmxConnectorWrap = new JmxConnectorWrap( - clusterPhy.getId(), - brokerId, + "clusterPhyId: " + clusterPhy.getId() + " brokerId: " + brokerId, broker.getStartTimestamp(), jmxConfig != null ? broker.getJmxHost(jmxConfig.getUseWhichEndpoint()) : broker.getHost(), broker.getJmxPort() != null ? broker.getJmxPort() : jmxConfig.getJmxPort(), diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectClusterDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectClusterDAO.java new file mode 100644 index 00000000..fae91cbe --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectClusterDAO.java @@ -0,0 +1,9 @@ +package com.xiaojukeji.know.streaming.km.persistence.mysql.connect; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectClusterPO; +import org.springframework.stereotype.Repository; + +@Repository +public interface ConnectClusterDAO extends BaseMapper { +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectWorkerDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectWorkerDAO.java new file mode 100644 index 00000000..edb50869 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectWorkerDAO.java @@ -0,0 +1,9 @@ +package com.xiaojukeji.know.streaming.km.persistence.mysql.connect; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectWorkerPO; +import org.springframework.stereotype.Repository; + +@Repository +public interface ConnectWorkerDAO extends BaseMapper { +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectorDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectorDAO.java new file mode 100644 index 00000000..69d228ba --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/ConnectorDAO.java @@ -0,0 +1,9 @@ +package com.xiaojukeji.know.streaming.km.persistence.mysql.connect; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import org.springframework.stereotype.Repository; + +@Repository +public interface ConnectorDAO extends BaseMapper { +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/WorkerConnectorDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/WorkerConnectorDAO.java new file mode 100644 index 00000000..fee9f872 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/connect/WorkerConnectorDAO.java @@ -0,0 +1,9 @@ +package com.xiaojukeji.know.streaming.km.persistence.mysql.connect; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.WorkerConnectorPO; +import org.springframework.stereotype.Repository; + +@Repository +public interface WorkerConnectorDAO extends BaseMapper { +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java index d4fc8094..1890a1db 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java @@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -30,6 +31,14 @@ public class KafkaHealthController { @Autowired private HealthStateService healthStateService; + private List allDimensionCodeList = new ArrayList() { + { + for (HealthCheckDimensionEnum dimensionEnum : HealthCheckDimensionEnum.values()) { + add(dimensionEnum.getDimension()); + } + } + }; + @Autowired private HealthCheckResultService healthCheckResultService; @@ -40,11 +49,21 @@ public class KafkaHealthController { @RequestParam(required = false) Integer dimensionCode) { HealthCheckDimensionEnum dimensionEnum = HealthCheckDimensionEnum.getByCode(dimensionCode); if (!dimensionEnum.equals(HealthCheckDimensionEnum.UNKNOWN)) { - return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList(healthStateService.getDimensionHealthResult(clusterPhyId, dimensionEnum))); + return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList(healthStateService.getDimensionHealthResult(clusterPhyId, Arrays.asList(dimensionCode)))); } return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( - healthStateService.getClusterHealthResult(clusterPhyId) + healthStateService.getDimensionHealthResult(clusterPhyId, allDimensionCodeList) + )); + } + + @ApiOperation(value = "集群-健康检查详情") + @PostMapping(value = "clusters/{clusterPhyId}/health-detail") + @ResponseBody + public Result> getClusterHealthCheckResultDetail(@PathVariable Long clusterPhyId, + @RequestBody List dimensionCodeList) { + return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( + healthStateService.getDimensionHealthResult(clusterPhyId, dimensionCodeList) )); } @@ -55,7 +74,7 @@ public class KafkaHealthController { @PathVariable Integer dimensionCode, @PathVariable String resName) { return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreBaseResultVOList( - healthStateService.getResHealthResult(clusterPhyId, dimensionCode, resName) + healthStateService.getResHealthResult(clusterPhyId, clusterPhyId, dimensionCode, resName) )); } diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/version/VersionController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/version/VersionController.java index f8e00430..986fd825 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/version/VersionController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/version/VersionController.java @@ -35,7 +35,19 @@ public class VersionController { @GetMapping(value = "support-kafka-versions") @ResponseBody public Result> listAllVersions() { - Result> rm = versionControlManager.listAllVersions(); + Result> rm = versionControlManager.listAllKafkaVersions(); + if (rm.failed()) { + return Result.buildFromIgnoreData(rm); + } + + return Result.buildSuc(new TreeMap<>(rm.getData())); + } + + @ApiOperation(value = "支持的kafka-Connect版本列表", notes = "") + @GetMapping(value = "support-kafka-connect-versions") + @ResponseBody + public Result> listAllConnectVersions() { + Result> rm = versionControlManager.listAllKafkaVersions(); if (rm.failed()) { return Result.buildFromIgnoreData(rm); } @@ -54,7 +66,7 @@ public class VersionController { @GetMapping(value = "clusters/{clusterId}/types/{type}/support-kafka-versions") @ResponseBody public Result> listClusterVersionControlItem(@PathVariable Long clusterId, @PathVariable Integer type) { - return versionControlManager.listClusterVersionControlItem(clusterId, type); + return versionControlManager.listKafkaClusterVersionControlItem(clusterId, type); } @ApiOperation(value = "用户设置的指标显示项", notes = "") diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/zk/ZookeeperMetricsController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/zk/ZookeeperMetricsController.java index bb2ea098..d481e1de 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/zk/ZookeeperMetricsController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/zk/ZookeeperMetricsController.java @@ -10,8 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ZookeeperMetr import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperMetricService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -26,8 +24,6 @@ import java.util.List; @RestController @RequestMapping(ApiPrefix.API_V3_PREFIX) public class ZookeeperMetricsController { - private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperMetricsController.class); - @Autowired private ZookeeperMetricService zookeeperMetricService; diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java index b75ab3d9..521e1f84 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncKafkaGroupTask.java @@ -34,7 +34,7 @@ public class SyncKafkaGroupTask extends AbstractAsyncMetadataDispatchTask { @Override public TaskResult processClusterTask(ClusterPhy clusterPhy, long triggerTimeUnitMs) throws Exception { // 获取集群的Group列表 - List groupNameList = groupService.listGroupsFromKafka(clusterPhy.getId()); + List groupNameList = groupService.listGroupsFromKafka(clusterPhy); TaskResult allSuccess = TaskResult.SUCCESS; @@ -42,7 +42,7 @@ public class SyncKafkaGroupTask extends AbstractAsyncMetadataDispatchTask { List groupList = new ArrayList<>(); for (String groupName : groupNameList) { try { - Group group = groupService.getGroupFromKafka(clusterPhy.getId(), groupName); + Group group = groupService.getGroupFromKafka(clusterPhy, groupName); if (group == null) { continue; } From 186dcd07e09396c215ddf984563183099995cefb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 19:24:56 +0800 Subject: [PATCH 059/150] =?UTF-8?q?=E5=A2=9E=E5=8A=A03.2=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install_guide/版本升级手册.md | 79 +++++++++++++++++++++++++++++- km-dist/init/sql/ddl-ks-km.sql | 76 +++++++++++++++++++++++++++- km-dist/init/sql/dml-ks-km.sql | 4 ++ 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/docs/install_guide/版本升级手册.md b/docs/install_guide/版本升级手册.md index 3cd580b8..943e50d7 100644 --- a/docs/install_guide/版本升级手册.md +++ b/docs/install_guide/版本升级手册.md @@ -5,8 +5,85 @@ - 如果中间某个版本没有升级信息,则表示该版本直接替换安装包即可从前一个版本升级至当前版本。 ### 6.2.0、升级至 `master` 版本 +**SQL 变更** +```sql +DROP TABLE IF EXISTS `ks_kc_connect_cluster`; +CREATE TABLE `ks_kc_connect_cluster` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Connect集群ID', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '集群名称', + `group_name` varchar(128) NOT NULL DEFAULT '' COMMENT '集群Group名称', + `cluster_url` varchar(1024) NOT NULL DEFAULT '' COMMENT '集群地址', + `member_leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL地址', + `version` varchar(64) NOT NULL DEFAULT '' COMMENT 'connect版本', + `jmx_properties` text COMMENT 'JMX配置', + `state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '集群使用的消费组状态,也表示集群状态:-1 Unknown,0 ReBalance,1 Active,2 Dead,3 Empty', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '接入时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_id_group_name` (`id`,`group_name`), + UNIQUE KEY `uniq_name_kafka_cluster` (`name`,`kafka_cluster_phy_id`), + KEY `idx_kafka_cluster_phy_id` (`kafka_cluster_phy_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Connect集群信息表'; -暂无 + +DROP TABLE IF EXISTS `ks_kc_connector`; +CREATE TABLE `ks_kc_connector` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `connector_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector名称', + `connector_class_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector类', + `connector_type` varchar(32) NOT NULL DEFAULT '' COMMENT 'Connector类型', + `state` varchar(45) NOT NULL DEFAULT '' COMMENT '状态', + `topics` text COMMENT '访问过的Topics', + `task_count` int(11) NOT NULL DEFAULT '0' COMMENT '任务数', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_connect_cluster_id_connector_name` (`connect_cluster_id`,`connector_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Connector信息表'; + + +DROP TABLE IF EXISTS `ks_kc_worker`; +CREATE TABLE `ks_kc_worker` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `member_id` varchar(512) NOT NULL DEFAULT '' COMMENT '成员ID', + `host` varchar(128) NOT NULL DEFAULT '' COMMENT '主机名', + `jmx_port` int(16) NOT NULL DEFAULT '-1' COMMENT 'Jmx端口', + `url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL信息', + `leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'leaderURL信息', + `leader` int(16) NOT NULL DEFAULT '0' COMMENT '状态: 1是leader,0不是leader', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_id_member_id` (`connect_cluster_id`,`member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='worker信息表'; + + +DROP TABLE IF EXISTS `ks_kc_worker_connector`; +CREATE TABLE `ks_kc_worker_connector` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `connector_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector名称', + `worker_member_id` varchar(256) NOT NULL DEFAULT '', + `task_id` int(16) NOT NULL DEFAULT '-1' COMMENT 'Task的ID', + `state` varchar(128) DEFAULT NULL COMMENT '任务状态', + `worker_id` varchar(128) DEFAULT NULL COMMENT 'worker信息', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_relation` (`connect_cluster_id`,`connector_name`,`task_id`,`worker_member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Worker和Connector关系表'; + +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_FAILED_TASK_COUNT', '{\"value\" : 1}', 'connector失败状态的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_UNASSIGNED_TASK_COUNT', '{\"value\" : 1}', 'connector未被分配的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE', '{\"value\" : 0.05}', 'Connect集群任务启动失败概率', 'admin'); +``` ### 6.2.1、升级至 `v3.1.0` 版本 diff --git a/km-dist/init/sql/ddl-ks-km.sql b/km-dist/init/sql/ddl-ks-km.sql index 907ff355..8da89aab 100644 --- a/km-dist/init/sql/ddl-ks-km.sql +++ b/km-dist/init/sql/ddl-ks-km.sql @@ -388,4 +388,78 @@ CREATE TABLE `ks_km_group` ( `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uniq_cluster_phy_id_name` (`cluster_phy_id`,`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Group信息表'; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Group信息表'; + + +DROP TABLE IF EXISTS `ks_kc_connect_cluster`; +CREATE TABLE `ks_kc_connect_cluster` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Connect集群ID', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '集群名称', + `group_name` varchar(128) NOT NULL DEFAULT '' COMMENT '集群Group名称', + `cluster_url` varchar(1024) NOT NULL DEFAULT '' COMMENT '集群地址', + `member_leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL地址', + `version` varchar(64) NOT NULL DEFAULT '' COMMENT 'connect版本', + `jmx_properties` text COMMENT 'JMX配置', + `state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '集群使用的消费组状态,也表示集群状态:-1 Unknown,0 ReBalance,1 Active,2 Dead,3 Empty', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '接入时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_id_group_name` (`id`,`group_name`), + UNIQUE KEY `uniq_name_kafka_cluster` (`name`,`kafka_cluster_phy_id`), + KEY `idx_kafka_cluster_phy_id` (`kafka_cluster_phy_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Connect集群信息表'; + + +DROP TABLE IF EXISTS `ks_kc_connector`; +CREATE TABLE `ks_kc_connector` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `connector_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector名称', + `connector_class_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector类', + `connector_type` varchar(32) NOT NULL DEFAULT '' COMMENT 'Connector类型', + `state` varchar(45) NOT NULL DEFAULT '' COMMENT '状态', + `topics` text COMMENT '访问过的Topics', + `task_count` int(11) NOT NULL DEFAULT '0' COMMENT '任务数', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_connect_cluster_id_connector_name` (`connect_cluster_id`,`connector_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Connector信息表'; + + +DROP TABLE IF EXISTS `ks_kc_worker`; +CREATE TABLE `ks_kc_worker` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `member_id` varchar(512) NOT NULL DEFAULT '' COMMENT '成员ID', + `host` varchar(128) NOT NULL DEFAULT '' COMMENT '主机名', + `jmx_port` int(16) NOT NULL DEFAULT '-1' COMMENT 'Jmx端口', + `url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL信息', + `leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'leaderURL信息', + `leader` int(16) NOT NULL DEFAULT '0' COMMENT '状态: 1是leader,0不是leader', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_id_member_id` (`connect_cluster_id`,`member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='worker信息表'; + + +DROP TABLE IF EXISTS `ks_kc_worker_connector`; +CREATE TABLE `ks_kc_worker_connector` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `connector_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector名称', + `worker_member_id` varchar(256) NOT NULL DEFAULT '', + `task_id` int(16) NOT NULL DEFAULT '-1' COMMENT 'Task的ID', + `state` varchar(128) DEFAULT NULL COMMENT '任务状态', + `worker_id` varchar(128) DEFAULT NULL COMMENT 'worker信息', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_relation` (`connect_cluster_id`,`connector_name`,`task_id`,`worker_member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Worker和Connector关系表'; \ No newline at end of file diff --git a/km-dist/init/sql/dml-ks-km.sql b/km-dist/init/sql/dml-ks-km.sql index 6a198a3f..17600fa1 100644 --- a/km-dist/init/sql/dml-ks-km.sql +++ b/km-dist/init/sql/dml-ks-km.sql @@ -12,3 +12,7 @@ INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value 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'); + +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_FAILED_TASK_COUNT', '{\"value\" : 1}', 'connector失败状态的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_UNASSIGNED_TASK_COUNT', '{\"value\" : 1}', 'connector未被分配的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE', '{\"value\" : 0.05}', 'connecct集群任务启动失败概率', 'admin'); \ No newline at end of file From 6aefc16fa01dbd7a761e920db5ae89c343544836 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 19:42:04 +0800 Subject: [PATCH 060/150] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Connect=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AbstractConnectClusterDispatchTask.java | 51 ++++++ .../health/AbstractHealthCheckTask.java | 124 +++++++++++++ .../health/ConnectClusterHealthCheckTask.java | 32 ++++ .../health/ConnectorHealthCheckTask.java | 32 ++++ .../AbstractAsyncMetadataDispatchTask.java | 47 +++++ .../connect/metadata/SyncConnectorTask.java | 60 +++++++ .../metadata/SyncWorkerConnectorTask.java | 83 +++++++++ .../AbstractAsyncMetricsDispatchTask.java | 47 +++++ .../ConnectClusterMetricCollectorTask.java | 31 ++++ .../metrics/ConnectorMetricCollectorTask.java | 31 ++++ .../SyncConnectClusterAndWorkerTask.java | 166 ++++++++++++++++++ 11 files changed, 704 insertions(+) create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/AbstractConnectClusterDispatchTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/AbstractHealthCheckTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectClusterHealthCheckTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectorHealthCheckTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/AbstractAsyncMetadataDispatchTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncWorkerConnectorTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/AbstractAsyncMetricsDispatchTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectClusterMetricCollectorTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectorMetricCollectorTask.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncConnectClusterAndWorkerTask.java diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/AbstractConnectClusterDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/AbstractConnectClusterDispatchTask.java new file mode 100644 index 00000000..e3c1e611 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/AbstractConnectClusterDispatchTask.java @@ -0,0 +1,51 @@ +package com.xiaojukeji.know.streaming.km.task.connect; + +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.task.AbstractDispatchTask; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + * @author wyb + * @date 2022/11/7 + */ +public abstract class AbstractConnectClusterDispatchTask extends AbstractDispatchTask { + private static final ILog log = LogFactory.getLog(AbstractConnectClusterDispatchTask.class); + + @Autowired + private ConnectClusterService connectClusterService; + + protected abstract TaskResult processSubTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception; + @Override + protected List listAllTasks() { + return connectClusterService.listAllClusters(); + } + + @Override + protected TaskResult processTask(List subTaskList, long triggerTimeUnitMs) { + boolean allSuccess = true; + for (ConnectCluster elem : subTaskList) { + try { + log.debug("method=processTask||taskName={}||connectClusterId={}||msg=start", this.taskName, elem.getId()); + + TaskResult tr = this.processSubTask(elem, triggerTimeUnitMs); + if (TaskResult.SUCCESS.getCode() != tr.getCode()) { + log.warn("method=processTask||taskName={}||connectClusterId={}||msg=process failed", this.taskName, elem.getId()); + allSuccess = false; + continue; + } + + log.debug("method=processTask||taskName={}||connectClusterId={}||msg=finished", this.taskName, elem.getId()); + } catch (Exception e) { + log.error("method=processTask||taskName={}||connectClusterId={}||errMsg=throw exception", this.taskName, elem.getId(), e); + } + } + + return allSuccess ? TaskResult.SUCCESS : TaskResult.FAIL; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/AbstractHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/AbstractHealthCheckTask.java new file mode 100644 index 00000000..2f726f80 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/AbstractHealthCheckTask.java @@ -0,0 +1,124 @@ +package com.xiaojukeji.know.streaming.km.task.connect.health; + +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.service.CollectThreadPoolService; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; +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.utils.FutureWaitUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; +import com.xiaojukeji.know.streaming.km.task.connect.metrics.AbstractAsyncMetricsDispatchTask; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.*; + +/** + * @author wyb + * @date 2022/11/8 + */ +public abstract class AbstractHealthCheckTask extends AbstractAsyncMetricsDispatchTask { + private static final ILog log = LogFactory.getLog(AbstractHealthCheckTask.class); + + @Autowired + private HealthCheckResultService healthCheckResultService; + + @Autowired + private CollectThreadPoolService collectThreadPoolService; + + public abstract AbstractHealthCheckService getCheckService(); + + @Override + public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + return this.calAndUpdateHealthCheckResult(connectCluster, triggerTimeUnitMs); + } + + private TaskResult calAndUpdateHealthCheckResult(ConnectCluster connectCluster, long triggerTimeUnitMs){ + // 获取配置,<配置名,配置信息> + Map healthConfigMap = healthCheckResultService.getClusterHealthConfig(connectCluster.getId()); + + // 获取资源列表 + List paramList = this.getCheckService().getResList(connectCluster.getId()); + + // 检查结果 + List checkResultList = Collections.synchronizedList(new ArrayList<>()); + if (ValidateUtils.isEmptyList(paramList)) { + // 当前无该维度的资源,则直接设置为 + checkResultList.addAll(this.getNoResResult(connectCluster.getId(), this.getCheckService(), healthConfigMap)); + } + + // 获取合适的线程池 + FutureWaitUtil futureWaitUtil = collectThreadPoolService.selectSuitableFutureUtil(connectCluster.getId()); + + // 遍历资源 + for (ClusterParam clusterParam: paramList) { + futureWaitUtil.runnableTask( + String.format("class=%s||method=calAndUpdateHealthCheckResult||clusterId=%d||resData=%s", this.getCheckService().getClass().getSimpleName(), connectCluster.getId(), clusterParam), + 30000, + () -> checkResultList.addAll(this.checkAndGetResult(clusterParam, healthConfigMap)) + ); + } + + futureWaitUtil.waitExecute(30000); + + try { + healthCheckResultService.batchReplace(connectCluster.getId(), this.getCheckService().getHealthCheckDimensionEnum().getDimension(), checkResultList); + } catch (Exception e) { + log.error( + "class=%s||method=calAndUpdateHealthCheckResult||clusterId={}||errMsg=exception!", + this.getCheckService().getClass().getSimpleName(), connectCluster.getId(), e + ); + } + + return TaskResult.SUCCESS; + } + + private List getNoResResult(Long connectClusterId, AbstractHealthCheckService healthCheckService, Map healthConfigMap) { + List resultList = new ArrayList<>(); + + // 进行检查 + for (BaseClusterHealthConfig clusterHealthConfig: healthConfigMap.values()) { + HealthCheckDimensionEnum dimensionEnum = healthCheckService.getHealthCheckDimensionEnum(); + if (!clusterHealthConfig.getCheckNameEnum().getDimensionEnum().equals(dimensionEnum)) { + // 类型不匹配 + continue; + } + + // 记录 + HealthCheckResult checkResult = new HealthCheckResult( + dimensionEnum.getDimension(), + clusterHealthConfig.getCheckNameEnum().getConfigName(), + connectClusterId, + "-1" + ); + checkResult.setPassed(Constant.YES); + resultList.add(checkResult); + } + + return resultList; + } + + private List checkAndGetResult(ClusterParam clusterParam, + Map healthConfigMap) { + List resultList = new ArrayList<>(); + + // 进行检查 + for (BaseClusterHealthConfig clusterHealthConfig: healthConfigMap.values()) { + HealthCheckResult healthCheckResult = this.getCheckService().checkAndGetResult(clusterParam, clusterHealthConfig); + if (healthCheckResult == null) { + continue; + } + + // 记录 + resultList.add(healthCheckResult); + } + + return resultList; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectClusterHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectClusterHealthCheckTask.java new file mode 100644 index 00000000..d0dc6b11 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectClusterHealthCheckTask.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.task.connect.health; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.connect.HealthCheckConnectClusterService; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author wyc + * @date 2022/11/9 + */ +@NoArgsConstructor +@AllArgsConstructor +@Task(name = "ConnectClusterHealthCheckTask", + description = "ConnectCluster健康检查", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class ConnectClusterHealthCheckTask extends AbstractHealthCheckTask { + + @Autowired + private HealthCheckConnectClusterService healthCheckConnectClusterService; + + @Override + public AbstractHealthCheckService getCheckService() { + return healthCheckConnectClusterService; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectorHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectorHealthCheckTask.java new file mode 100644 index 00000000..eebd201b --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/health/ConnectorHealthCheckTask.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.task.connect.health; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.connect.HealthCheckConnectorService; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author wyb + * @date 2022/11/8 + */ +@NoArgsConstructor +@AllArgsConstructor +@Task(name = "ConnectorHealthCheckTask", + description = "Connector健康检查", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class ConnectorHealthCheckTask extends AbstractHealthCheckTask { + + @Autowired + HealthCheckConnectorService healthCheckConnectorService; + + @Override + public AbstractHealthCheckService getCheckService() { + return healthCheckConnectorService; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/AbstractAsyncMetadataDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/AbstractAsyncMetadataDispatchTask.java new file mode 100644 index 00000000..fa46be8e --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/AbstractAsyncMetadataDispatchTask.java @@ -0,0 +1,47 @@ +package com.xiaojukeji.know.streaming.km.task.connect.metadata; + +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.task.connect.AbstractConnectClusterDispatchTask; +import com.xiaojukeji.know.streaming.km.task.service.TaskThreadPoolService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 元数据同步相关任务 + */ +public abstract class AbstractAsyncMetadataDispatchTask extends AbstractConnectClusterDispatchTask { + private static final ILog log = LogFactory.getLog(AbstractAsyncMetadataDispatchTask.class); + + public abstract TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception; + + @Autowired + private TaskThreadPoolService taskThreadPoolService; + + @Override + protected TaskResult processSubTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + return this.asyncProcessSubTask(connectCluster, triggerTimeUnitMs); + } + + public TaskResult asyncProcessSubTask(ConnectCluster connectCluster, long triggerTimeUnitMs) { + taskThreadPoolService.submitMetadataTask( + String.format("taskName=%s||clusterPhyId=%d", this.taskName, connectCluster.getId()), + this.timeoutUnitSec.intValue() * 1000, + () -> { + try { + TaskResult tr = this.processClusterTask(connectCluster, triggerTimeUnitMs); + if (TaskResult.SUCCESS_CODE != tr.getCode()) { + log.error("class=AbstractAsyncMetadataDispatchTask||taskName={}||connectClusterId={}||taskResult={}||msg=failed", this.taskName, connectCluster.getId(), tr); + } else { + log.debug("class=AbstractAsyncMetadataDispatchTask||taskName={}||connectClusterId={}||msg=success", this.taskName, connectCluster.getId()); + } + } catch (Exception e) { + log.error("class=AbstractAsyncMetadataDispatchTask||taskName={}||connectClusterId={}||errMsg=exception", this.taskName, connectCluster.getId(), e); + } + } + ); + + return TaskResult.SUCCESS; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java new file mode 100644 index 00000000..4d45e7c2 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java @@ -0,0 +1,60 @@ +package com.xiaojukeji.know.streaming.km.task.connect.metadata; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + + +@Task(name = "SyncConnectorTask", + description = "Connector信息同步到DB", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class SyncConnectorTask extends AbstractAsyncMetadataDispatchTask { + private static final ILog LOGGER = LogFactory.getLog(SyncConnectorTask.class); + + @Autowired + private ConnectorService connectorService; + + @Override + public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) { + Result> nameListResult = connectorService.listConnectorsFromCluster(connectCluster.getId()); + if (nameListResult.failed()) { + return TaskResult.FAIL; + } + + boolean allSuccess = true; + + List connectorList = new ArrayList<>(); + for (String connectorName: nameListResult.getData()) { + Result ksConnectorResult = connectorService.getAllConnectorInfoFromCluster(connectCluster.getId(), connectorName); + if (ksConnectorResult.failed()) { + LOGGER.error( + "class=SyncConnectorTask||method=processClusterTask||connectClusterId={}||connectorName={}||result={}", + connectCluster.getId(), connectorName, ksConnectorResult + ); + + allSuccess = false; + continue; + } + + connectorList.add(ksConnectorResult.getData()); + } + + connectorService.batchReplace(connectCluster.getKafkaClusterPhyId(), connectCluster.getId(), connectorList, new HashSet<>(nameListResult.getData())); + + return allSuccess? TaskResult.SUCCESS: TaskResult.FAIL; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncWorkerConnectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncWorkerConnectorTask.java new file mode 100644 index 00000000..2d576a06 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncWorkerConnectorTask.java @@ -0,0 +1,83 @@ +package com.xiaojukeji.know.streaming.km.task.connect.metadata; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +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.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSGroupDescription; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.group.GroupService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; +/** + * @author wyb + * @date 2022/11/14 + */ +@Task(name = "SyncWorkerConnectorTask", + description = "WorkerConnector信息同步到DB", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class SyncWorkerConnectorTask extends AbstractAsyncMetadataDispatchTask{ + + private static final ILog LOGGER = LogFactory.getLog(SyncWorkerConnectorTask.class); + + @Autowired + ConnectorService connectorService; + + @Autowired + private GroupService groupService; + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Override + public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + List connectorPOList = connectorService.listByConnectClusterIdFromDB(connectCluster.getId()); + if (connectorPOList.isEmpty()) { + return TaskResult.SUCCESS; + } + + //获取集群信息 + ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(connectCluster.getKafkaClusterPhyId()); + if (clusterPhy == null) { + LOGGER.error( + "class=SyncWorkerConnectorTask||method=processClusterTask||connectClusterId={}||clusterPhyId={}||errMsg=clusterPhy not exist!.", + connectCluster.getId(), connectCluster.getKafkaClusterPhyId() + ); + return TaskResult.FAIL; + + } + KSGroupDescription ksGroupDescription = groupService.getGroupDescriptionFromKafka(clusterPhy, connectCluster.getGroupName()); + + //获取workerConnector列表 + try { + List workerConnectorList = new ArrayList<>(); + for (ConnectorPO connectorPO : connectorPOList) { + workerConnectorList.addAll(workerConnectorService.getWorkerConnectorListFromCluster(connectCluster, connectorPO.getConnectorName())); + } + workerConnectorService.batchReplaceInDB(connectCluster.getId(), workerConnectorList); + } catch (Exception e) { + LOGGER.error( + "class=SyncWorkerConnectorTask||method=processClusterTask||connectClusterId={}||ksGroupDescription={}||errMsg=exception.", + connectCluster.getId(), ksGroupDescription, e + ); + + return TaskResult.FAIL; + } + + return TaskResult.SUCCESS; + } + + +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/AbstractAsyncMetricsDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/AbstractAsyncMetricsDispatchTask.java new file mode 100644 index 00000000..72fa1430 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/AbstractAsyncMetricsDispatchTask.java @@ -0,0 +1,47 @@ +package com.xiaojukeji.know.streaming.km.task.connect.metrics; + +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.task.connect.AbstractConnectClusterDispatchTask; +import com.xiaojukeji.know.streaming.km.task.service.TaskThreadPoolService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Metrics相关任务 + */ +public abstract class AbstractAsyncMetricsDispatchTask extends AbstractConnectClusterDispatchTask { + private static final ILog log = LogFactory.getLog(AbstractAsyncMetricsDispatchTask.class); + + public abstract TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception; + + @Autowired + private TaskThreadPoolService taskThreadPoolService; + + @Override + protected TaskResult processSubTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + return this.asyncProcessSubTask(connectCluster, triggerTimeUnitMs); + } + + public TaskResult asyncProcessSubTask(ConnectCluster connectCluster, long triggerTimeUnitMs) { + taskThreadPoolService.submitMetricsTask( + String.format("taskName=%s||clusterPhyId=%d", this.taskName, connectCluster.getId()), + this.timeoutUnitSec.intValue() * 1000, + () -> { + try { + TaskResult tr = this.processClusterTask(connectCluster, triggerTimeUnitMs); + if (TaskResult.SUCCESS_CODE != tr.getCode()) { + log.error("class=AbstractAsyncMetricsDispatchTask||taskName={}||connectClusterId={}||taskResult={}||msg=failed", this.taskName, connectCluster.getId(), tr); + } else { + log.debug("class=AbstractAsyncMetricsDispatchTask||taskName={}||connectClusterId={}||msg=success", this.taskName, connectCluster.getId()); + } + } catch (Exception e) { + log.error("class=AbstractAsyncMetricsDispatchTask||taskName={}||connectClusterId={}||errMsg=exception", this.taskName, connectCluster.getId(), e); + } + } + ); + + return TaskResult.SUCCESS; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectClusterMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectClusterMetricCollectorTask.java new file mode 100644 index 00000000..1fa1672c --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectClusterMetricCollectorTask.java @@ -0,0 +1,31 @@ +package com.xiaojukeji.know.streaming.km.task.connect.metrics; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.xiaojukeji.know.streaming.km.collector.metric.connect.ConnectClusterMetricCollector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Task(name = "ConnectClusterMetricCollectorTask", + description = "ConnectCluster指标采集任务", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class ConnectClusterMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { + + @Autowired + private ConnectClusterMetricCollector connectClusterMetricCollector; + + @Override + public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + connectClusterMetricCollector.collectMetrics(connectCluster); + + return TaskResult.SUCCESS; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectorMetricCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectorMetricCollectorTask.java new file mode 100644 index 00000000..d3422f6f --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metrics/ConnectorMetricCollectorTask.java @@ -0,0 +1,31 @@ +package com.xiaojukeji.know.streaming.km.task.connect.metrics; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.xiaojukeji.know.streaming.km.collector.metric.connect.ConnectConnectorMetricCollector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author wyb + * @date 2022/11/7 + */ +@Task(name = "ConnectorMetricCollectorTask", + description = "Connector指标采集任务", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class ConnectorMetricCollectorTask extends AbstractAsyncMetricsDispatchTask { + + @Autowired + private ConnectConnectorMetricCollector connectConnectorMetricCollector; + + @Override + public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + connectConnectorMetricCollector.collectMetrics(connectCluster); + + return TaskResult.SUCCESS; + } +} diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncConnectClusterAndWorkerTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncConnectClusterAndWorkerTask.java new file mode 100644 index 00000000..76fb2b99 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncConnectClusterAndWorkerTask.java @@ -0,0 +1,166 @@ +package com.xiaojukeji.know.streaming.km.task.kafka.metadata; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +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.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectClusterMetadata; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.group.Group; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSGroupDescription; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSMemberConnectAssignment; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafka.KSMemberDescription; +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.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.group.GroupStateEnum; +import com.xiaojukeji.know.streaming.km.common.enums.group.GroupTypeEnum; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.core.service.group.GroupService; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.enums.group.GroupTypeEnum.CONNECT_CLUSTER_PROTOCOL_TYPE; + +@Task(name = "SyncClusterAndWorkerTask", + description = "Connect-Cluster&Worker信息同步到DB", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class SyncConnectClusterAndWorkerTask extends AbstractAsyncMetadataDispatchTask { + private static final ILog LOGGER = LogFactory.getLog(SyncConnectClusterAndWorkerTask.class); + + @Autowired + private GroupService groupService; + + @Autowired + private WorkerService workerService; + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Autowired + private ConnectClusterService connectClusterService; + + @Override + public TaskResult processClusterTask(ClusterPhy clusterPhy, long triggerTimeUnitMs) { + boolean allSuccess = true; + + //获取connect集群 + List groupList = groupService.listClusterGroups(clusterPhy.getId()).stream().filter(elem->elem.getType()==GroupTypeEnum.CONNECT_CLUSTER).collect(Collectors.toList()); + for (Group group: groupList) { + + try { + KSGroupDescription ksGroupDescription = groupService.getGroupDescriptionFromKafka(clusterPhy, group.getName()); + if (!ksGroupDescription.protocolType().equals(CONNECT_CLUSTER_PROTOCOL_TYPE)) { + continue; + } + + Result rl = this.handleConnectClusterMetadata(clusterPhy.getId(), group.getName(), ksGroupDescription); + if (rl.failed()) { + allSuccess = false; + continue; + } + + Result rv = this.handleWorkerMetadata(rl.getData(), ksGroupDescription); + if (rv.failed()) { + allSuccess = false; + } + + } catch (Exception e) { + LOGGER.error( + "class=SyncClusterAndWorkerTask||method=processClusterTask||clusterPhyId={}||groupName={}||errMsg=exception.", + clusterPhy.getId(), group.getName(), e + ); + + allSuccess = false; + } + } + + return allSuccess? TaskResult.SUCCESS: TaskResult.FAIL; + } + + + private Result handleWorkerMetadata(Long connectClusterId, KSGroupDescription ksGroupDescription) { + try { + List workerList = new ArrayList<>(); + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectClusterId); + + for (KSMemberDescription memberDescription: ksGroupDescription.members()) { + KSMemberConnectAssignment assignment = (KSMemberConnectAssignment) memberDescription.assignment(); + if (assignment != null) { + workerList.add(new ConnectWorker( + connectCluster.getKafkaClusterPhyId(), + connectClusterId, + memberDescription.consumerId(), + memberDescription.host().substring(1), + Constant.INVALID_CODE, + assignment.getWorkerState().url(), + assignment.getAssignment().leaderUrl(), + memberDescription.consumerId().equals(assignment.getAssignment().leader()) ? Constant.YES : Constant.NO + )); + } else { + workerList.add(new ConnectWorker( + connectCluster.getKafkaClusterPhyId(), + connectClusterId, + memberDescription.consumerId(), + memberDescription.host().substring(1), + Constant.INVALID_CODE, + "", + "", + Constant.NO + )); + } + } + + workerService.batchReplaceInDB(connectClusterId, workerList); + } catch (Exception e) { + LOGGER.error( + "class=SyncClusterAndWorkerTask||method=handleWorkerMetadata||connectClusterId={}||ksGroupDescription={}||errMsg=exception.", + connectClusterId, ksGroupDescription, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.MYSQL_OPERATE_FAILED, e.getMessage()); + } + + return Result.buildSuc(); + } + + private Result handleConnectClusterMetadata(Long clusterPhyId, String groupName, KSGroupDescription ksGroupDescription) { + try { + for (KSMemberDescription memberDescription: ksGroupDescription.members()) { + KSMemberConnectAssignment assignment = (KSMemberConnectAssignment) memberDescription.assignment(); + + ConnectClusterMetadata metadata = new ConnectClusterMetadata( + clusterPhyId, + groupName, + GroupStateEnum.getByRawState(ksGroupDescription.state()), + assignment == null? "": assignment.getAssignment().leaderUrl() + ); + + Long connectClusterId = connectClusterService.replaceAndReturnIdInDB(metadata); + + return Result.buildSuc(connectClusterId); + } + } catch (Exception e) { + LOGGER.error( + "class=SyncClusterAndWorkerTask||method=handleConnectClusterMetadata||clusterPhyId={}||groupName={}||ksGroupDescription={}||errMsg=exception.", + clusterPhyId, groupName, ksGroupDescription, e + ); + + return Result.buildFromRSAndMsg(ResultStatus.MYSQL_OPERATE_FAILED, e.getMessage()); + } + + return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, "消费组无成员"); + } +} From 7a0db7161ba05999811a94328ebb0051632cea70 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 19:43:23 +0800 Subject: [PATCH 061/150] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Connect=20=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E5=B1=82=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biz/cluster/ClusterConnectorsManager.java | 15 ++ .../impl/ClusterConnectorsManagerImpl.java | 152 ++++++++++++++++++ .../connect/connector/ConnectorManager.java | 15 ++ .../connector/WorkerConnectorManager.java | 16 ++ .../connector/impl/ConnectorManagerImpl.java | 93 +++++++++++ .../impl/WorkerConnectorManageImpl.java | 37 +++++ 6 files changed, 328 insertions(+) create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/ClusterConnectorsManager.java create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/WorkerConnectorManager.java create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/WorkerConnectorManageImpl.java diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/ClusterConnectorsManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/ClusterConnectorsManager.java new file mode 100644 index 00000000..c20c5c77 --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/ClusterConnectorsManager.java @@ -0,0 +1,15 @@ +package com.xiaojukeji.know.streaming.km.biz.cluster; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterConnectorsOverviewDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect.ConnectStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterConnectorOverviewVO; + +/** + * Kafka集群Connector概览 + */ +public interface ClusterConnectorsManager { + PaginationResult getClusterConnectorsOverview(Long clusterPhyId, ClusterConnectorsOverviewDTO dto); + + ConnectStateVO getClusterConnectorsState(Long clusterPhyId); +} diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java new file mode 100644 index 00000000..46d34378 --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java @@ -0,0 +1,152 @@ +package com.xiaojukeji.know.streaming.km.biz.cluster.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.biz.cluster.ClusterConnectorsManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterConnectorsOverviewDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectorsDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +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.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect.ConnectStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterConnectorOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.converter.ConnectConverter; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil; +import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import org.apache.kafka.connect.runtime.AbstractStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + + +@Service +public class ClusterConnectorsManagerImpl implements ClusterConnectorsManager { + private static final ILog LOGGER = LogFactory.getLog(ClusterConnectorsManagerImpl.class); + + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private ConnectorMetricService connectorMetricService; + + @Autowired + private WorkerService workerService; + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Override + public PaginationResult getClusterConnectorsOverview(Long clusterPhyId, ClusterConnectorsOverviewDTO dto) { + List clusterList = connectClusterService.listByKafkaCluster(clusterPhyId); + + List poList = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); + + // 查询实时指标 + Result> latestMetricsResult = connectorMetricService.getLatestMetricsFromES( + clusterPhyId, + poList.stream().map(elem -> new ClusterConnectorDTO(elem.getConnectClusterId(), elem.getConnectorName())).collect(Collectors.toList()), + dto.getLatestMetricNames() + ); + + if (latestMetricsResult.failed()) { + LOGGER.error("method=getClusterConnectorsOverview||clusterPhyId={}||result={}||errMsg=get latest metric failed", clusterPhyId, latestMetricsResult); + return PaginationResult.buildFailure(latestMetricsResult, dto); + } + + // 转换成vo + List voList = ConnectConverter.convert2ClusterConnectorOverviewVOList(clusterList, poList,latestMetricsResult.getData()); + + // 请求分页信息 + PaginationResult voPaginationResult = this.pagingConnectorInLocal(voList, dto); + if (voPaginationResult.failed()) { + LOGGER.error("method=getClusterConnectorsOverview||clusterPhyId={}||result={}||errMsg=pagination in local failed", clusterPhyId, voPaginationResult); + + return PaginationResult.buildFailure(voPaginationResult, dto); + } + + // 查询历史指标 + Result> lineMetricsResult = connectorMetricService.listConnectClusterMetricsFromES( + clusterPhyId, + this.buildMetricsConnectorsDTO( + voPaginationResult.getData().getBizData().stream().map(elem -> new ClusterConnectorDTO(elem.getConnectClusterId(), elem.getConnectorName())).collect(Collectors.toList()), + dto.getMetricLines() + ) + ); + + + return PaginationResult.buildSuc( + ConnectConverter.supplyData2ClusterConnectorOverviewVOList( + voPaginationResult.getData().getBizData(), + lineMetricsResult.getData() + ), + voPaginationResult + ); + } + + @Override + public ConnectStateVO getClusterConnectorsState(Long clusterPhyId) { + //获取Connect集群Id列表 + List connectClusterList = connectClusterService.listByKafkaCluster(clusterPhyId); + List connectorPOList = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); + List workerConnectorList = workerConnectorService.listByKafkaClusterIdFromDB(clusterPhyId); + List connectWorkerList = workerService.listByKafkaClusterIdFromDB(clusterPhyId); + + return convert2ConnectStateVO(connectClusterList, connectorPOList, workerConnectorList, connectWorkerList); + } + + /**************************************************** private method ****************************************************/ + + private MetricsConnectorsDTO buildMetricsConnectorsDTO(List connectorDTOList, MetricDTO metricDTO) { + MetricsConnectorsDTO dto = ConvertUtil.obj2Obj(metricDTO, MetricsConnectorsDTO.class); + dto.setConnectorNameList(connectorDTOList == null? new ArrayList<>(): connectorDTOList); + + return dto; + } + + private ConnectStateVO convert2ConnectStateVO(List connectClusterList, List connectorPOList, List workerConnectorList, List connectWorkerList) { + ConnectStateVO connectStateVO = new ConnectStateVO(); + connectStateVO.setConnectClusterCount(connectClusterList.size()); + connectStateVO.setTotalConnectorCount(connectorPOList.size()); + connectStateVO.setAliveConnectorCount(connectorPOList.stream().filter(elem -> elem.getState().equals(AbstractStatus.State.RUNNING.name())).collect(Collectors.toList()).size()); + connectStateVO.setWorkerCount(connectWorkerList.size()); + connectStateVO.setTotalTaskCount(workerConnectorList.size()); + connectStateVO.setAliveTaskCount(workerConnectorList.stream().filter(elem -> elem.getState().equals(AbstractStatus.State.RUNNING.name())).collect(Collectors.toList()).size()); + return connectStateVO; + } + + private PaginationResult pagingConnectorInLocal(List connectorVOList, ClusterConnectorsOverviewDTO dto) { + //模糊匹配 + connectorVOList = PaginationUtil.pageByFuzzyFilter(connectorVOList, dto.getSearchKeywords(), Arrays.asList("connectClusterName")); + + //排序 + if (!dto.getLatestMetricNames().isEmpty()) { + PaginationMetricsUtil.sortMetrics(connectorVOList, "latestMetrics", dto.getSortMetricNameList(), "connectClusterName", dto.getSortType()); + } else { + PaginationUtil.pageBySort(connectorVOList, dto.getSortField(), dto.getSortType(), "connectClusterName", dto.getSortType()); + } + + //分页 + return PaginationUtil.pageBySubData(connectorVOList, dto); + } + +} diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java new file mode 100644 index 00000000..0247a7d3 --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java @@ -0,0 +1,15 @@ +package com.xiaojukeji.know.streaming.km.biz.connect.connector; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.connector.ConnectorStateVO; + +import java.util.Properties; + +public interface ConnectorManager { + Result updateConnectorConfig(Long connectClusterId, String connectorName, Properties configs, String operator); + + Result createConnector(ConnectorCreateDTO dto, String operator); + + Result getConnectorStateVO(Long connectClusterId, String connectorName); +} diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/WorkerConnectorManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/WorkerConnectorManager.java new file mode 100644 index 00000000..eaf82423 --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/WorkerConnectorManager.java @@ -0,0 +1,16 @@ +package com.xiaojukeji.know.streaming.km.biz.connect.connector; + + +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task.KCTaskOverviewVO; + +import java.util.List; + +/** + * @author wyb + * @date 2022/11/14 + */ +public interface WorkerConnectorManager { + Result> getTaskOverview(Long connectClusterId, String connectorName); + +} diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java new file mode 100644 index 00000000..c28f310f --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java @@ -0,0 +1,93 @@ +package com.xiaojukeji.know.streaming.km.biz.connect.connector.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.biz.connect.connector.ConnectorManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; +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.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.connector.ConnectorStateVO; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.plugin.PluginService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import org.apache.kafka.connect.runtime.AbstractStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +@Service +public class ConnectorManagerImpl implements ConnectorManager { + private static final ILog LOGGER = LogFactory.getLog(ConnectorManagerImpl.class); + + @Autowired + private PluginService pluginService; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Override + public Result updateConnectorConfig(Long connectClusterId, String connectorName, Properties configs, String operator) { + Result infosResult = pluginService.validateConfig(connectClusterId, configs); + if (infosResult.failed()) { + return Result.buildFromIgnoreData(infosResult); + } + + if (infosResult.getData().getErrorCount() > 0) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "Connector参数错误"); + } + + return connectorService.updateConnectorConfig(connectClusterId, connectorName, configs, operator); + } + + @Override + public Result createConnector(ConnectorCreateDTO dto, String operator) { + Result createResult = connectorService.createConnector(dto.getConnectClusterId(), dto.getConnectorName(), dto.getConfigs(), operator); + if (createResult.failed()) { + return Result.buildFromIgnoreData(createResult); + } + + Result ksConnectorResult = connectorService.getAllConnectorInfoFromCluster(dto.getConnectClusterId(), dto.getConnectorName()); + if (ksConnectorResult.failed()) { + return Result.buildFromRSAndMsg(ResultStatus.SUCCESS, "创建成功,但是获取元信息失败,页面元信息会存在1分钟延迟"); + } + + connectorService.addNewToDB(ksConnectorResult.getData()); + return Result.buildSuc(); + } + + @Override + public Result getConnectorStateVO(Long connectClusterId, String connectorName) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, connectorName); + + if (connectorPO == null) { + return Result.buildFailure(ResultStatus.NOT_EXIST); + } + + List workerConnectorList = workerConnectorService.listFromDB(connectClusterId).stream().filter(elem -> elem.getConnectorName().equals(connectorName)).collect(Collectors.toList()); + + return Result.buildSuc(convert2ConnectorOverviewVO(connectorPO, workerConnectorList)); + } + + private ConnectorStateVO convert2ConnectorOverviewVO(ConnectorPO connectorPO, List workerConnectorList) { + ConnectorStateVO connectorStateVO = new ConnectorStateVO(); + connectorStateVO.setConnectClusterId(connectorPO.getConnectClusterId()); + connectorStateVO.setName(connectorPO.getConnectorName()); + connectorStateVO.setType(connectorPO.getConnectorType()); + connectorStateVO.setState(connectorPO.getState()); + connectorStateVO.setTotalTaskCount(workerConnectorList.size()); + connectorStateVO.setAliveTaskCount(workerConnectorList.stream().filter(elem -> elem.getState().equals(AbstractStatus.State.RUNNING.name())).collect(Collectors.toList()).size()); + connectorStateVO.setTotalWorkerCount(workerConnectorList.stream().map(elem -> elem.getWorkerId()).collect(Collectors.toSet()).size()); + return connectorStateVO; + } +} diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/WorkerConnectorManageImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/WorkerConnectorManageImpl.java new file mode 100644 index 00000000..4d0cd317 --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/WorkerConnectorManageImpl.java @@ -0,0 +1,37 @@ +package com.xiaojukeji.know.streaming.km.biz.connect.connector.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.biz.connect.connector.WorkerConnectorManager; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task.KCTaskOverviewVO; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author wyb + * @date 2022/11/14 + */ +@Service +public class WorkerConnectorManageImpl implements WorkerConnectorManager { + + private static final ILog LOGGER = LogFactory.getLog(WorkerConnectorManageImpl.class); + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Override + public Result> getTaskOverview(Long connectClusterId, String connectorName) { + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectClusterId); + List workerConnectorList = workerConnectorService.getWorkerConnectorListFromCluster(connectCluster, connectorName); + + return Result.buildSuc(ConvertUtil.list2List(workerConnectorList, KCTaskOverviewVO.class)); + } +} From d9c59cb3d302274fb5d09fbd5f47092d51038459 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 6 Dec 2022 19:44:18 +0800 Subject: [PATCH 062/150] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Connect=20Rest?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v3/cluster/ClusterConnectsController.java | 133 ++++++++++++++++++ .../KafkaConnectClusterController.java | 42 ++++++ .../connect/KafkaConnectPluginController.java | 56 ++++++++ .../v3/connect/KafkaConnectorController.java | 91 ++++++++++++ .../KafkaConnectorStateController.java | 98 +++++++++++++ .../api/v3/connect/KafkaTaskController.java | 33 +++++ 6 files changed, 453 insertions(+) create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectClusterController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectPluginController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorStateController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaTaskController.java diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java new file mode 100644 index 00000000..e7d93af8 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java @@ -0,0 +1,133 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.cluster; + +import com.xiaojukeji.know.streaming.km.biz.cluster.ClusterConnectorsManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterConnectorsOverviewDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectClustersDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.connect.MetricsConnectorsDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; +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.vo.cluster.connect.ConnectClusterBasicCombineExistVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect.ConnectClusterBasicVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterWorkerOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterConnectorOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect.ConnectStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.converter.ConnectConverter; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author zengqiao + * @date 22/10/27 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "集群Connects-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_PREFIX) // 这里使用 API_V3_PREFIX 没有使用 API_V3_CONNECT_PREFIX 的原因是这个接口在Kafka集群页面下 +public class ClusterConnectsController { + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectorMetricService connectorMetricService; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private ConnectClusterMetricService connectClusterMetricService; + + @Autowired + private WorkerService workerService; + + @Autowired + private ClusterConnectorsManager clusterConnectorsManager; + + + /**************************************************** connect method ****************************************************/ + + @ApiOperation(value = "Connect集群基本信息", notes = "") + @GetMapping(value = "kafka-clusters/{clusterPhyId}/connect-clusters/{connectClusterName}/basic-combine-exist") + @ResponseBody + public Result getBasicCombineExist(@PathVariable Long clusterPhyId, + @PathVariable String connectClusterName) { + return Result.buildSuc(ConnectConverter.convert2ConnectClusterBasicCombineExistVO( + connectClusterService.getByName(clusterPhyId, connectClusterName)) + ); + } + + @ApiOperation(value = "Connect集群基本信息列表", notes = "") + @GetMapping(value = "kafka-clusters/{clusterPhyId}/connect-clusters-basic") + @ResponseBody + public Result> getClusterConnectClustersBasic(@PathVariable Long clusterPhyId) { + return Result.buildSuc(ConvertUtil.list2List(connectClusterService.listByKafkaCluster(clusterPhyId), ConnectClusterBasicVO.class)); + } + + @ApiOperation(value = "Connect集群指标信息") + @PostMapping(value = "kafka-clusters/{clusterPhyId}/connect-cluster-metrics") + @ResponseBody + public Result> getConnectClusterMetrics(@PathVariable Long clusterPhyId, + @Validated @RequestBody MetricsConnectClustersDTO dto) { + return connectClusterMetricService.listConnectClusterMetricsFromES(clusterPhyId, dto); + } + + @ApiOperation(value = "集群Connectors状态", notes = "") + @GetMapping(value = "kafka-clusters/{clusterPhyId}/connect-state") + @ResponseBody + public Result getClusterConnectorsState(@PathVariable Long clusterPhyId) { + return Result.buildSuc(clusterConnectorsManager.getClusterConnectorsState(clusterPhyId)); + } + + + /**************************************************** connector method ****************************************************/ + + @ApiOperation(value = "Connectors基本信息", notes = "") + @GetMapping(value = "clusters/{clusterPhyId}/connectors-basic") + @ResponseBody + public Result> getClusterConnectorsBasic(@PathVariable Long clusterPhyId) { + return Result.buildSuc( + ConnectConverter.convert2BasicVOList( + connectClusterService.listByKafkaCluster(clusterPhyId), + connectorService.listByKafkaClusterIdFromDB(clusterPhyId) + ) + ); + } + + @ApiOperation(value = "Connectors概览列表", notes = "") + @PostMapping(value = "clusters/{clusterPhyId}/connectors-overview") + @ResponseBody + public PaginationResult getClusterConnectorsOverview(@PathVariable Long clusterPhyId, + @Validated @RequestBody ClusterConnectorsOverviewDTO dto) { + return clusterConnectorsManager.getClusterConnectorsOverview(clusterPhyId, dto); + } + + @ApiOperation(value = "集群Connectors指标信息") + @PostMapping(value = "clusters/{clusterPhyId}/connectors-metrics") + @ResponseBody + public Result> getClusterPhyMetrics(@PathVariable Long clusterPhyId, + @Validated @RequestBody MetricsConnectorsDTO dto) { + return connectorMetricService.listConnectClusterMetricsFromES(clusterPhyId, dto); + } + + /**************************************************** connector method ****************************************************/ + @ApiOperation(value = "worker概览列表", notes = "") + @GetMapping(value = "clusters/{clusterPhyId}/workers-overview") + @ResponseBody + public PaginationResult getClusterWorkersOverview(@PathVariable Long clusterPhyId, PaginationBaseDTO dto) { + return workerService.pageWorkByKafkaClusterPhy(clusterPhyId, dto); + } +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectClusterController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectClusterController.java new file mode 100644 index 00000000..33d5466e --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectClusterController.java @@ -0,0 +1,42 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect; + + +import com.didiglobal.logi.security.util.HttpRequestUtil; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.cluster.ConnectClusterDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Connect-Cluster-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_CONNECT_PREFIX) +public class KafkaConnectClusterController { + @Autowired + private ConnectClusterService connectClusterService; + + @ApiOperation(value = "删除Connect集群") + @DeleteMapping(value = "connect-clusters") + @ResponseBody + public Result deleteConnectCluster(@RequestParam("connectClusterId") Long connectClusterId) { + return connectClusterService.deleteInDB(connectClusterId, HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "修改Connect集群", notes = "") + @PutMapping(value = "batch-connect-clusters") + @ResponseBody + public Result batchModifyConnectCluster(@Validated @RequestBody List dtoList) { + return connectClusterService.batchModifyInDB(dtoList, HttpRequestUtil.getOperator()); + } +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectPluginController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectPluginController.java new file mode 100644 index 00000000..d8e19db8 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectPluginController.java @@ -0,0 +1,56 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect; + + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.plugin.ConnectPluginBasic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin.ConnectConfigInfosVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin.ConnectPluginBasicVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.plugin.PluginService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Connect-Plugin-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_CONNECT_PREFIX) +public class KafkaConnectPluginController { + + @Autowired + private PluginService pluginService; + + @ApiOperation(value = "Connect集群插件", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connector-plugins") + @ResponseBody + public Result> getConnectorPlugins(@PathVariable Long connectClusterId) { + Result> listResult = pluginService.listPluginsFromCluster(connectClusterId); + if (listResult.failed()) { + return Result.buildFromIgnoreData(listResult); + } + + listResult.getData().forEach(elem -> elem.setHelpDocLink("https://www.confluent.io/hub/")); + return Result.buildSuc(ConvertUtil.list2List(listResult.getData(), ConnectPluginBasicVO.class)); + } + + @ApiOperation(value = "Connect插件配置", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connector-plugins/{pluginName}/config") + @ResponseBody + public Result getPluginConfig(@PathVariable Long connectClusterId, @PathVariable String pluginName) { + Result infosResult = pluginService.getConfig(connectClusterId, pluginName); + if (infosResult.failed()) { + return Result.buildFromIgnoreData(infosResult); + } + + return Result.buildSuc(ConvertUtil.obj2Obj(infosResult.getData(), ConnectConfigInfosVO.class)); + } +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java new file mode 100644 index 00000000..8e5e7237 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java @@ -0,0 +1,91 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect; + + +import com.didiglobal.logi.security.util.HttpRequestUtil; +import com.xiaojukeji.know.streaming.km.biz.connect.connector.ConnectorManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorActionDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorDeleteDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorConfigModifyDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; +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.vo.connect.plugin.ConnectConfigInfosVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectActionEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.plugin.PluginService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Connect-Connector自身-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_CONNECT_PREFIX) +public class KafkaConnectorController { + + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectorManager connectorManager; + + @Autowired + private PluginService pluginService; + + @ApiOperation(value = "创建Connector", notes = "") + @PostMapping(value = "connectors") + @ResponseBody + public Result createConnector(@Validated @RequestBody ConnectorCreateDTO dto) { + return connectorManager.createConnector(dto, HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "删除Connector", notes = "") + @DeleteMapping(value ="connectors") + @ResponseBody + public Result deleteConnectors(@Validated @RequestBody ConnectorDeleteDTO dto) { + return connectorService.deleteConnector(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "操作Connector", notes = "") + @PutMapping(value ="connectors") + @ResponseBody + public Result operateConnectors(@Validated @RequestBody ConnectorActionDTO dto) { + if (ConnectActionEnum.RESTART.getValue().equals(dto.getAction())) { + return connectorService.restartConnector(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } else if (ConnectActionEnum.STOP.getValue().equals(dto.getAction())) { + return connectorService.stopConnector(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } else if (ConnectActionEnum.RESUME.getValue().equals(dto.getAction())) { + return connectorService.resumeConnector(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } + + return Result.buildFailure(ResultStatus.PARAM_ILLEGAL); + } + + @ApiOperation(value = "修改Connector配置", notes = "") + @PutMapping(value ="connectors-config") + @ResponseBody + public Result modifyConnectors(@Validated @RequestBody ConnectorConfigModifyDTO dto) { + return connectorManager.updateConnectorConfig(dto.getConnectClusterId(), dto.getConnectorName(), dto.getConfigs(), HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "校验Connector配置", notes = "") + @PutMapping(value ="connectors-config/validate") + @ResponseBody + public Result validateConnectors(@Validated @RequestBody ConnectorConfigModifyDTO dto) { + Result infoResult = pluginService.validateConfig(dto.getConnectClusterId(), dto.getConfigs()); + if (infoResult.failed()) { + return Result.buildFromIgnoreData(infoResult); + } + + return Result.buildSuc(ConvertUtil.obj2Obj(infoResult.getData(), ConnectConfigInfosVO.class)); + } +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorStateController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorStateController.java new file mode 100644 index 00000000..bd1513e3 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorStateController.java @@ -0,0 +1,98 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect; + +import com.xiaojukeji.know.streaming.km.biz.connect.connector.ConnectorManager; +import com.xiaojukeji.know.streaming.km.biz.connect.connector.WorkerConnectorManager; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicCombineExistVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.connector.ConnectorStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task.KCTaskOverviewVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.converter.ConnectConverter; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Properties; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Connect-Connector状态-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_CONNECT_PREFIX) +public class KafkaConnectorStateController { + + @Autowired + private ConnectorService connectorService; + + @Autowired + private ConnectorMetricService connectorMetricService; + + @Autowired + private WorkerConnectorManager workerConnectorManager; + + @Autowired + private ConnectorManager connectorManager; + + @Autowired + private ConnectClusterService connectClusterService; + + @ApiOperation(value = "Connectors基本信息", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/basic-combine-exist") + @ResponseBody + public Result getConnectorBasicCombineExist(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + return Result.buildSuc( + ConnectConverter.convert2BasicVO( + connectClusterService.getById(connectClusterId), + connectorService.getConnectorFromDB(connectClusterId, connectorName) + ) + ); + } + + @ApiOperation(value = "Connector配置", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/config") + @ResponseBody + public Result getConnectorConfig(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + Result connectorResult = connectorService.getConnectorInfoFromCluster(connectClusterId, connectorName); + if (connectorResult.failed()) { + return Result.buildFromIgnoreData(connectorResult); + } + + Properties props = new Properties(); + props.putAll(connectorResult.getData().getConfig()); + return Result.buildSuc(props); + } + + @ApiOperation(value = "获取Connector的Task列表", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/tasks") + @ResponseBody + public Result> getConnectorTasks(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + return workerConnectorManager.getTaskOverview(connectClusterId, connectorName); + } + + @ApiOperation(value = "Connector近期指标") + @PostMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/latest-metrics") + @ResponseBody + public Result getConnectorLatestMetrics(@PathVariable Long connectClusterId, + @PathVariable String connectorName, + @RequestBody List metricsNames) { + return connectorMetricService.getLatestMetricsFromES(connectClusterId, connectorName, metricsNames); + } + + @ApiOperation(value = "获取Connector的状态", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/state") + @ResponseBody + public Result getConnectorStateVO(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + return connectorManager.getConnectorStateVO(connectClusterId, connectorName); + } + +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaTaskController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaTaskController.java new file mode 100644 index 00000000..3b01c5c6 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaTaskController.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect; + + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.task.TaskActionDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @author zengqiao + * @date 22/10/17 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Connect-Task-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_CONNECT_PREFIX) +public class KafkaTaskController { + + @Autowired + private WorkerConnectorService workerConnectorService; + + @ApiOperation(value = "操作Task", notes = "") + @PutMapping(value ="tasks") + @ResponseBody + public Result actionTask(@Validated @RequestBody TaskActionDTO dto) { + return workerConnectorService.actionTask(dto); + } +} From f3c4133cd258c28a1470e87f99ea5c00181ffd07 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 7 Dec 2022 16:09:54 +0800 Subject: [PATCH 063/150] =?UTF-8?q?[Bugfix]=E5=88=86=E6=89=B9=E4=BB=8EES?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2Topic=E6=9C=80=E8=BF=91=E4=B8=80=E6=9D=A1?= =?UTF-8?q?=E6=8C=87=E6=A0=87(#817)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/common/constant/ESConstant.java | 5 ++++ .../km/core/flusher/DatabaseDataFlusher.java | 29 +++++++++++++------ .../service/topic/TopicMetricService.java | 5 +--- .../topic/impl/TopicMetricServiceImpl.java | 13 +++++++-- .../km/persistence/es/ESOpClient.java | 12 +++++--- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESConstant.java index 1b8a7740..85bf2084 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ESConstant.java @@ -36,6 +36,11 @@ public class ESConstant { public static final Integer DEFAULT_RETRY_TIME = 3; + /** + * 获取Topic-Latest指标时,单次允许的Topic数 + */ + public static final int SEARCH_LATEST_TOPIC_METRIC_CNT_PER_REQUEST = 500; + private ESConstant() { } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java index 70d138be..8f71a5ea 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java @@ -130,20 +130,31 @@ public class DatabaseDataFlusher { private void flushTopicLatestMetricsCache() { for (ClusterPhy clusterPhy: LoadedClusterPhyCache.listAll().values()) { FutureUtil.quickStartupFutureUtil.submitTask(() -> { - try { + List topicNameList = topicService.listTopicsFromCacheFirst(clusterPhy.getId()).stream().map(Topic::getTopicName).collect(Collectors.toList()); - List topicNameList = topicService.listTopicsFromCacheFirst(clusterPhy.getId()).stream().map(Topic::getTopicName).collect(Collectors.toList()); + for (int i = 0; i < 3; ++i) { + try { + List metricsList = topicMetricService.listTopicLatestMetricsFromES( + clusterPhy.getId(), + topicNameList, + Collections.emptyList() + ); - List metricsList = topicMetricService.listTopicLatestMetricsFromES(clusterPhy.getId(), topicNameList, Collections.emptyList()); + if (!topicNameList.isEmpty() && metricsList.isEmpty()) { + // 没有指标时,重试 + continue; + } - Map metricsMap = metricsList - .stream() - .collect(Collectors.toMap(TopicMetrics::getTopic, Function.identity())); + Map metricsMap = metricsList + .stream() + .collect(Collectors.toMap(TopicMetrics::getTopic, Function.identity())); - DataBaseDataLocalCache.putTopicMetrics(clusterPhy.getId(), metricsMap); + DataBaseDataLocalCache.putTopicMetrics(clusterPhy.getId(), metricsMap); - } catch (Exception e) { - LOGGER.error("method=flushTopicLatestMetricsCache||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); + break; + } catch (Exception e) { + LOGGER.error("method=flushTopicLatestMetricsCache||clusterPhyId={}||errMsg=exception!", clusterPhy.getId(), e); + } } }); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java index 014b460a..f9318bcd 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/TopicMetricService.java @@ -37,12 +37,9 @@ public interface TopicMetricService { /** * 获取Topic维度最新的一条指标 - * @param clusterPhyId - * @param topicNames - * @param metricNameList - * @return */ List listTopicLatestMetricsFromES(Long clusterPhyId, List topicNames, List metricNameList); + /** * 获取Topic维度最新的一条指标 * @param clusterPhyId diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java index fe680901..731bc548 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java @@ -18,6 +18,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionJmxInf import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.TopicMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.constant.ESConstant; import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; import com.xiaojukeji.know.streaming.km.common.enums.AggTypeEnum; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; @@ -152,9 +153,17 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe @Override public List listTopicLatestMetricsFromES(Long clusterPhyId, List topicNames, List metricNames) { - List topicMetricPOs = topicMetricESDAO.listTopicLatestMetric(clusterPhyId, topicNames, metricNames); + List poList = new ArrayList<>(); - return ConvertUtil.list2List(topicMetricPOs, TopicMetrics.class); + for (int i = 0; i < topicNames.size(); i += ESConstant.SEARCH_LATEST_TOPIC_METRIC_CNT_PER_REQUEST) { + poList.addAll(topicMetricESDAO.listTopicLatestMetric( + clusterPhyId, + topicNames.subList(i, Math.min(i + ESConstant.SEARCH_LATEST_TOPIC_METRIC_CNT_PER_REQUEST, topicNames.size())), + Collections.emptyList()) + ); + } + + return ConvertUtil.list2List(poList, TopicMetrics.class); } @Override diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java index 822c44e1..5764dfd6 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESOpClient.java @@ -150,8 +150,7 @@ public class ESOpClient { } public List performRequest(String indexName, String queryDsl, Class clzz) { - ESQueryResponse esQueryResponse = doQuery( - new ESQueryRequest().indices(indexName).source(queryDsl).clazz(clzz)); + ESQueryResponse esQueryResponse = this.doQuery(new ESQueryRequest().indices(indexName).source(queryDsl).clazz(clzz)); if (esQueryResponse == null) { return new ArrayList<>(); } @@ -447,8 +446,13 @@ public class ESOpClient { return response; } catch (Exception e) { - LOGGER.error( "method=doQuery||indexName={}||queryDsl={}||errMsg=query error. ", - request.indices(), bytesReferenceConvertDsl(request.source()), e); + LOGGER.error( + "method=doQuery||indexName={}||queryDsl={}||errMsg=query error. ", + request.indices(), + bytesReferenceConvertDsl(request.source()), + e + ); + return null; } } From 5cad7b41062e2812d577c9bd0506c56b6009410d Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 7 Dec 2022 16:19:44 +0800 Subject: [PATCH 064/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E9=9B=86?= =?UTF-8?q?=E7=BE=A4Topic=E5=88=97=E8=A1=A8=E9=A1=B5=E9=9D=A2=E7=99=BD?= =?UTF-8?q?=E5=B1=8F=E9=97=AE=E9=A2=98(#819)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 集群Topic列表健康状态对应关系存在问题,导致当健康状态指标存在时,会出现白屏。 --- .../layout-clusters-fe/src/pages/TopicList/config.tsx | 8 ++++++++ .../layout-clusters-fe/src/pages/TopicList/index.tsx | 9 ++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/config.tsx index c424d44c..200e20aa 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/config.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/config.tsx @@ -23,3 +23,11 @@ export const getChartConfig = (title: string) => { }, }; }; + +export const HealthStateMap: any = { + '-1': 'Unknown', + 0: '好', + 1: '中', + 2: '差', + 3: 'Down', +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx index 780161e3..b2be16bd 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx @@ -18,7 +18,7 @@ import ReplicaMove from '@src/components/TopicJob/ReplicaMove'; import { formatAssignSize } from '../Jobs/config'; import { DownOutlined } from '@ant-design/icons'; import { tableHeaderPrefix } from '@src/constants/common'; -import {sliderValueMap} from "@src/pages/MutliClusterPage/config"; +import { HealthStateMap } from './config'; const { Option } = Select; @@ -92,8 +92,7 @@ const AutoPage = (props: any) => { const orgVal = record?.latestMetrics?.metrics?.[metricName]; if (orgVal !== undefined) { if (metricName === 'HealthState') { - const val = sliderValueMap[(orgVal) as keyof typeof sliderValueMap]; - return val.name; + return HealthStateMap[orgVal] || '-'; } else if (metricName === 'LogSize') { return Number(Utils.formatAssignSize(orgVal, 'MB')).toLocaleString(); } else { @@ -165,8 +164,8 @@ const AutoPage = (props: any) => { sorter: true, // 设计图上量出来的是144,但做的时候发现写144 header部分的sort箭头不出来,所以临时调大些 width: 170, - render: (value: any, record: any) =>{ - return calcCurValue(record, "HealthState") + render: (value: any, record: any) => { + return calcCurValue(record, 'HealthState'); }, }, // { From c4fb18a73c4a969c60e8ec130a3ae21e4831ee83 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Thu, 8 Dec 2022 17:02:37 +0800 Subject: [PATCH 065/150] =?UTF-8?q?=20[Bugfix]=E4=BF=AE=E5=A4=8D=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E4=BB=BB=E5=8A=A1=E7=8A=B6=E6=80=81=E4=B8=8D=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E9=97=AE=E9=A2=98(#815)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/service/reassign/impl/ReassignJobServiceImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java index 9bcd1d29..30c2f6e1 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java @@ -508,8 +508,9 @@ public class ReassignJobServiceImpl implements ReassignJobService { } }); - if (!topicPartitions.isEmpty()){ - return opPartitionService.preferredReplicaElection(jobPO.getClusterPhyId(), topicPartitions); + //无论优先副本选举是否成功,都返回成功,以保证job的数据更新 + if (!topicPartitions.isEmpty()) { + opPartitionService.preferredReplicaElection(jobPO.getClusterPhyId(), topicPartitions); } return Result.buildSuc(); From b2f0f69365d898a12eb9f485c674acb07651efd7 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 9 Dec 2022 14:34:58 +0800 Subject: [PATCH 066/150] =?UTF-8?q?[Optimize]Overview=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E7=9A=84TopN=E6=9F=A5=E8=AF=A2ES=E6=B5=81=E7=A8=8B=E4=BC=98?= =?UTF-8?q?=E5=8C=96(#823)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、复用线程池,同时支持线程池的线程数可配置; 2、优化查询TopN指标时,可能会出现重复查询的问题; 3、处理代码扫描(SonarLint)反馈的问题; --- .../bean/vo/metrics/point/MetricPointVO.java | 9 +- .../service/version/BaseMetricService.java | 45 ++--- .../km/persistence/es/ESTPService.java | 41 +++++ .../persistence/es/dao/BaseMetricESDAO.java | 24 +-- .../persistence/es/dao/BrokerMetricESDAO.java | 120 +++++------- .../es/dao/ClusterMetricESDAO.java | 60 +++--- .../persistence/es/dao/GroupMetricESDAO.java | 62 ++++--- .../persistence/es/dao/TopicMetricESDAO.java | 157 ++++++++-------- .../connect/ConnectClusterMetricESDAO.java | 122 +++++-------- .../es/dao/connect/ConnectorMetricESDAO.java | 172 +++++++----------- km-rest/src/main/resources/application.yml | 6 +- 11 files changed, 398 insertions(+), 420 deletions(-) create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESTPService.java diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/metrics/point/MetricPointVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/metrics/point/MetricPointVO.java index c647b222..72b5b31e 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/metrics/point/MetricPointVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/metrics/point/MetricPointVO.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -11,7 +10,6 @@ import lombok.NoArgsConstructor; */ @Data @NoArgsConstructor -@AllArgsConstructor @ApiModel(description = "指标点") public class MetricPointVO implements Comparable { @ApiModelProperty(value = "指标名", example = "HealthScore") @@ -26,6 +24,13 @@ public class MetricPointVO implements Comparable { @ApiModelProperty(value = "指标值聚合方式:avg、max、min、sum") private String aggType; + public MetricPointVO(String name, Long timeStamp, String value, String aggType) { + this.name = name; + this.timeStamp = timeStamp; + this.value = value; + this.aggType = aggType; + } + @Override public int compareTo(MetricPointVO o) { if(null == o){return 0;} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java index 82f841e1..c6cc8275 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseMetricService.java @@ -38,40 +38,41 @@ public abstract class BaseMetricService extends BaseKafkaVersionControlService { protected abstract void initRegisterVCHandler(); - protected List metricMap2VO(Long clusterId, - Map>> map){ - List multiLinesVOS = new ArrayList<>(); - if (map == null || map.isEmpty()) { + protected List metricMap2VO(Long clusterId, Map>> metricsMap ){ + List lineVOList = new ArrayList<>(); + if (metricsMap == null || metricsMap.isEmpty()) { // 如果为空,则直接返回 - return multiLinesVOS; + return lineVOList; } - for(String metric : map.keySet()){ + for(Map.Entry>> entry : metricsMap.entrySet()){ try { MetricMultiLinesVO multiLinesVO = new MetricMultiLinesVO(); - multiLinesVO.setMetricName(metric); + multiLinesVO.setMetricName(entry.getKey()); - List metricLines = new ArrayList<>(); - - Map> metricPointMap = map.get(metric); - if(null == metricPointMap || metricPointMap.isEmpty()){continue;} - for(Map.Entry> entry : metricPointMap.entrySet()){ - MetricLineVO metricLineVO = new MetricLineVO(); - metricLineVO.setName(entry.getKey().toString()); - metricLineVO.setMetricName(metric); - metricLineVO.setMetricPoints(entry.getValue()); - - metricLines.add(metricLineVO); + if(null == entry.getValue() || entry.getValue().isEmpty()){ + continue; } + List metricLines = new ArrayList<>(); + entry.getValue().entrySet().forEach(resNameAndMetricsEntry -> { + MetricLineVO metricLineVO = new MetricLineVO(); + metricLineVO.setName(resNameAndMetricsEntry.getKey().toString()); + metricLineVO.setMetricName(entry.getKey()); + metricLineVO.setMetricPoints(resNameAndMetricsEntry.getValue()); + + metricLines.add(metricLineVO); + }); + multiLinesVO.setMetricLines(metricLines); - multiLinesVOS.add(multiLinesVO); - }catch (Exception e){ - LOGGER.error("method=metricMap2VO||cluster={}||msg=exception!", clusterId, e); + + lineVOList.add(multiLinesVO); + } catch (Exception e){ + LOGGER.error("method=metricMap2VO||clusterId={}||msg=exception!", clusterId, e); } } - return multiLinesVOS; + return lineVOList; } /** diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESTPService.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESTPService.java new file mode 100644 index 00000000..194f2458 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/ESTPService.java @@ -0,0 +1,41 @@ +package com.xiaojukeji.know.streaming.km.persistence.es; + +import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +/** + * 处理ES请求的线程池 + */ +@Service +@NoArgsConstructor +public class ESTPService { + @Value("${thread-pool.es.search.thread-num:10}") + private Integer esSearchThreadCnt; + + @Value("${thread-pool.es.search.queue-size:5000}") + private Integer esSearchThreadQueueSize; + + private FutureWaitUtil searchESTP; + + @PostConstruct + private void init() { + searchESTP = FutureWaitUtil.init( + "SearchESTP", + esSearchThreadCnt, + esSearchThreadCnt, + esSearchThreadQueueSize + ); + } + + public void submitSearchTask(String taskName, Integer timeoutUnisMs, Runnable runnable) { + searchESTP.runnableTask(taskName, timeoutUnisMs, runnable); + } + + public void waitExecute() { + searchESTP.waitExecute(); + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java index 609b63d0..48651dd7 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BaseMetricESDAO.java @@ -12,7 +12,9 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPoint import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; import com.xiaojukeji.know.streaming.km.common.utils.EnvUtil; import com.xiaojukeji.know.streaming.km.common.utils.IndexNameUtils; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.persistence.es.BaseESDAO; +import com.xiaojukeji.know.streaming.km.persistence.es.ESTPService; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateLoaderUtil; import lombok.NoArgsConstructor; @@ -48,6 +50,9 @@ public class BaseMetricESDAO extends BaseESDAO { @Autowired private TemplateLoaderUtil templateLoaderUtil; + @Autowired + protected ESTPService esTPService; + /** * es 地址 */ @@ -364,21 +369,16 @@ public class BaseMetricESDAO extends BaseESDAO { sb.append(str, 1, str.length() - 1); } - protected Map checkBucketsAndHitsOfResponseAggs(ESQueryResponse response){ - if(null == response || null == response.getAggs()){ + protected Map checkBucketsAndHitsOfResponseAggs(ESQueryResponse response) { + if(null == response + || null == response.getAggs() + || null == response.getAggs().getEsAggrMap() + || null == response.getAggs().getEsAggrMap().get(HIST) + || ValidateUtils.isEmptyList(response.getAggs().getEsAggrMap().get(HIST).getBucketList())) { return null; } - Map esAggrMap = response.getAggs().getEsAggrMap(); - if (null == esAggrMap || null == esAggrMap.get(HIST)) { - return null; - } - - if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ - return null; - } - - return esAggrMap; + return response.getAggs().getEsAggrMap(); } protected int handleESQueryResponseCount(ESQueryResponse response){ diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java index d3d52f9e..d6b47c73 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/BrokerMetricESDAO.java @@ -6,12 +6,10 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BrokerMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; import java.util.*; @@ -29,8 +27,6 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { register( this); } - protected FutureWaitUtil queryFuture = FutureWaitUtil.init("BrokerMetricESDAO", 4,8, 500); - /** * 获取集群 clusterId 中 brokerId 最新的统计指标 */ @@ -140,7 +136,7 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { aggDsl ); - queryFuture.runnableTask( + esTPService.submitSearchTask( String.format("class=BrokerMetricESDAO||method=listBrokerMetricsByBrokerIds||ClusterPhyId=%d", clusterPhyId), 5000, () -> { @@ -163,7 +159,7 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { } } - queryFuture.waitExecute(); + esTPService.waitExecute(); return table; } @@ -220,106 +216,86 @@ public class BrokerMetricESDAO extends BaseMetricESDAO { return metricMap; } - private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ + private Map> handleListESQueryResponse(ESQueryResponse response, List metricNameList, String aggType){ Map> metricMap = new HashMap<>(); - if(null == response || null == response.getAggs()){ + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); + if (esAggrMap == null) { return metricMap; } - Map esAggrMap = response.getAggs().getEsAggrMap(); - if (null == esAggrMap || null == esAggrMap.get(HIST)) { - return metricMap; - } - - if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ - return metricMap; - } - - for(String metric : metrics){ + for(String metricName : metricNameList){ List metricPoints = new ArrayList<>(); - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + esAggrMap.get(HIST).getBucketList().forEach(esBucket -> { try { - if (null != esBucket.getUnusedMap().get(KEY)) { - Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); - Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); - if(null == value){return;} - - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setTimeStamp(timestamp); - metricPoint.setValue(value.toString()); - metricPoint.setName(metric); - - metricPoints.add(metricPoint); - }else { - LOGGER.info(""); + if (null == esBucket.getUnusedMap().get(KEY)) { + return; } - }catch (Exception e){ - LOGGER.error("metric={}||errMsg=exception!", metric, e); + + Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(metricName).getUnusedMap().get(VALUE); + if(null == value) { + return; + } + + metricPoints.add(new MetricPointVO(metricName, timestamp, value.toString(), aggType)); + } catch (Exception e){ + LOGGER.error("method=handleListESQueryResponse||metricName={}||errMsg=exception!", metricName, e); } } ); - metricMap.put(metric, optimizeMetricPoints(metricPoints)); + metricMap.put(metricName, optimizeMetricPoints(metricPoints)); } return metricMap; } - private Map> handleTopBrokerESQueryResponse(ESQueryResponse response, List metrics, int topN){ + private Map> handleTopBrokerESQueryResponse(ESQueryResponse response, List metricNameList, int topN) { Map> ret = new HashMap<>(); - if(null == response || null == response.getAggs()){ + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); + if (esAggrMap == null) { return ret; } - Map esAggrMap = response.getAggs().getEsAggrMap(); - if (null == esAggrMap || null == esAggrMap.get(HIST)) { - return ret; - } - - if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ - return ret; - } - - Map>> metricBrokerValueMap = new HashMap<>(); + Map>> metricNameBrokerValueMap = new HashMap<>(); //1、先获取每个指标对应的所有brokerIds以及指标的值 - for(String metric : metrics) { - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + for(String metricName : metricNameList) { + esAggrMap.get(HIST).getBucketList().forEach(esBucket -> { try { - if (null != esBucket.getUnusedMap().get(KEY)) { - Long brokerId = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); - Object value = esBucket.getAggrMap().get(HIST).getBucketList().get(0).getAggrMap() - .get(metric).getUnusedMap().get(VALUE); - if(null == value){return;} - - List> brokerValue = (null == metricBrokerValueMap.get(metric)) ? - new ArrayList<>() : metricBrokerValueMap.get(metric); - - brokerValue.add(new Tuple<>(brokerId, Double.valueOf(value.toString()))); - metricBrokerValueMap.put(metric, brokerValue); + if (null == esBucket.getUnusedMap().get(KEY)) { + return; } - }catch (Exception e){ - LOGGER.error("metrice={}||errMsg=exception!", metric, e); + + Long brokerId = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(HIST).getBucketList().get(0).getAggrMap().get(metricName).getUnusedMap().get(VALUE); + if(null == value) { + return; + } + + metricNameBrokerValueMap.putIfAbsent(metricName, new ArrayList<>()); + metricNameBrokerValueMap.get(metricName).add(new Tuple<>(brokerId, Double.valueOf(value.toString()))); + } catch (Exception e) { + LOGGER.error("method=handleTopBrokerESQueryResponse||metric={}||errMsg=exception!", metricName, e); } - } ); + }); } //2、对每个指标的broker按照指标值排序,并截取前topN个brokerIds - for(String metric : metricBrokerValueMap.keySet()){ - List> brokerValue = metricBrokerValueMap.get(metric); + for(Map.Entry>> entry : metricNameBrokerValueMap.entrySet()){ + entry.getValue().sort((o1, o2) -> { + if(null == o1 || null == o2){ + return 0; + } - brokerValue.sort((o1, o2) -> { - if(null == o1 || null == o2){return 0;} return o2.getV2().compareTo(o1.getV2()); } ); - List> temp = (brokerValue.size() > topN) ? brokerValue.subList(0, topN) : brokerValue; - List brokerIds = temp.stream().map(t -> t.getV1()).collect( Collectors.toList()); - - ret.put(metric, brokerIds); + // 获取TopN的Broker + List brokerIdList = entry.getValue().subList(0, Math.min(topN, entry.getValue().size())).stream().map(elem -> elem.getV1()).collect(Collectors.toList()); + ret.put(entry.getKey(), brokerIdList); } return ret; diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java index 5d53aa16..ea6d3852 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ClusterMetricESDAO.java @@ -10,7 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchRange; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchSort; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ClusterMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; @@ -35,8 +34,6 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { register(this); } - protected FutureWaitUtil queryFuture = FutureWaitUtil.init("ClusterMetricESDAO", 4,8, 500); - /** * 获取集群 clusterId 最新的统计指标 */ @@ -127,30 +124,39 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { //4、构造dsl查询条件,开始查询 for(Long clusterPhyId : clusterPhyIds){ try { - queryFuture.runnableTask( + esTPService.submitSearchTask( String.format("class=ClusterMetricESDAO||method=listClusterMetricsByClusterIds||ClusterPhyId=%d", clusterPhyId), 5000, () -> { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslConstant.GET_CLUSTER_AGG_LIST_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); + DslConstant.GET_CLUSTER_AGG_LIST_METRICS, + clusterPhyId, + startTime, + endTime, + interval, + aggDsl + ); Map> metricMap = esOpClient.performRequestWithRouting( - String.valueOf(clusterPhyId), realIndex, dsl, - s -> handleListESQueryResponse(s, metrics, aggType), 3); + String.valueOf(clusterPhyId), + realIndex, + dsl, + s -> handleListESQueryResponse(s, metrics, aggType), + DEFAULT_RETRY_TIME + ); synchronized (table){ - for(String metric : metricMap.keySet()){ - table.put(metric, clusterPhyId, metricMap.get(metric)); + for(Map.Entry> entry : metricMap.entrySet()){ + table.put(entry.getKey(), clusterPhyId, entry.getValue()); } } }); }catch (Exception e){ - LOGGER.error("method=listClusterMetricsByClusterIds||clusterPhyId={}||errMsg=exception!", - clusterPhyId, e); + LOGGER.error("method=listClusterMetricsByClusterIds||clusterPhyId={}||errMsg=exception!", clusterPhyId, e); } } - queryFuture.waitExecute(); + esTPService.waitExecute(); return table; } @@ -182,35 +188,33 @@ public class ClusterMetricESDAO extends BaseMetricESDAO { return metricMap; } - private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ - Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); - if(null == esAggrMap){return new HashMap<>();} + private Map> handleListESQueryResponse(ESQueryResponse response, List metricNameList, String aggType){ + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap) { + return new HashMap<>(); + } Map> metricMap = new HashMap<>(); - for(String metric : metrics){ + for(String metricName : metricNameList) { List metricPoints = new ArrayList<>(); esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { try { if (null != esBucket.getUnusedMap().get(KEY)) { Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); - Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); - if(null == value){return;} + Object value = esBucket.getAggrMap().get(metricName).getUnusedMap().get(VALUE); + if(null == value) { + return; + } - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setTimeStamp(timestamp); - metricPoint.setValue(value.toString()); - metricPoint.setName(metric); - - metricPoints.add(metricPoint); + metricPoints.add(new MetricPointVO(metricName, timestamp, value.toString(), aggType)); } - }catch (Exception e){ - LOGGER.error("method=handleESQueryResponse||metric={}||errMsg=exception!", metric, e); + } catch (Exception e){ + LOGGER.error("method=handleListESQueryResponse||metricName={}||errMsg=exception!", metricName, e); } } ); - metricMap.put(metric, optimizeMetricPoints(metricPoints)); + metricMap.put(metricName, optimizeMetricPoints(metricPoints)); } return metricMap; diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java index 26ebdea1..0b3ab0e6 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/GroupMetricESDAO.java @@ -10,7 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.TopicPartitionK import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.GroupMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.enums.AggTypeEnum; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; @@ -35,8 +34,6 @@ public class GroupMetricESDAO extends BaseMetricESDAO { register(this); } - protected FutureWaitUtil queryFuture = FutureWaitUtil.init("GroupMetricESDAO", 4,8, 500); - public List listLatestMetricsAggByGroupTopic(Long clusterPhyId, List groupTopicList, List metrics, AggTypeEnum aggType){ Long latestTime = getLatestMetricTime(); Long startTime = latestTime - FIVE_MIN; @@ -49,7 +46,7 @@ public class GroupMetricESDAO extends BaseMetricESDAO { List groupMetricPOS = new CopyOnWriteArrayList<>(); for(GroupTopic groupTopic : groupTopicList){ - queryFuture.runnableTask( + esTPService.submitSearchTask( String.format("class=GroupMetricESDAO||method=listLatestMetricsAggByGroupTopic||ClusterPhyId=%d||groupName=%s||topicName=%s", clusterPhyId, groupTopic.getGroupName(), groupTopic.getTopicName()), 5000, @@ -73,7 +70,7 @@ public class GroupMetricESDAO extends BaseMetricESDAO { }); } - queryFuture.waitExecute(); + esTPService.waitExecute(); return groupMetricPOS; } @@ -126,13 +123,26 @@ public class GroupMetricESDAO extends BaseMetricESDAO { Integer partition = tp.getPartition(); String dsl = dslLoaderUtil.getFormatDslByFileName( - DslConstant.LIST_GROUP_METRICS, clusterId, groupName, topic, partition, startTime, endTime, interval, aggDsl); + DslConstant.LIST_GROUP_METRICS, + clusterId, + groupName, + topic, + partition, + startTime, + endTime, + interval, + aggDsl + ); - Map> metricMap = esOpClient.performRequest(realIndex, dsl, - s -> handleGroupMetrics(s, aggType, metrics), 3); + Map> metricMap = esOpClient.performRequest( + realIndex, + dsl, + s -> handleGroupMetrics(s, aggType, metrics), + DEFAULT_RETRY_TIME + ); - for(String metric : metricMap.keySet()){ - table.put(metric, topic + "&" + partition, metricMap.get(metric)); + for(Map.Entry> entry: metricMap.entrySet()){ + table.put(entry.getKey(), topic + "&" + partition, entry.getValue()); } } @@ -188,23 +198,27 @@ public class GroupMetricESDAO extends BaseMetricESDAO { for(String metric : metrics){ List metricPoints = new ArrayList<>(); - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + esAggrMap.get(HIST).getBucketList().forEach(esBucket -> { try { - if (null != esBucket.getUnusedMap().get(KEY)) { - Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); - Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); - if(value == null){return;} - - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setTimeStamp(timestamp); - metricPoint.setValue(value.toString()); - metricPoint.setName(metric); - - metricPoints.add(metricPoint); + if (null == esBucket.getUnusedMap().get(KEY)) { + return; } + + Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); + if(value == null) { + return; + } + + MetricPointVO metricPoint = new MetricPointVO(); + metricPoint.setAggType(aggType); + metricPoint.setTimeStamp(timestamp); + metricPoint.setValue(value.toString()); + metricPoint.setName(metric); + + metricPoints.add(metricPoint); }catch (Exception e){ - LOGGER.error("method=handleESQueryResponse||metric={}||errMsg=exception!", metric, e); + LOGGER.error("method=handleGroupMetrics||metric={}||errMsg=exception!", metric, e); } } ); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java index 49749160..88c0a82f 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java @@ -10,7 +10,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchTerm; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchSort; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.TopicMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; @@ -26,7 +25,6 @@ import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateC @Component public class TopicMetricESDAO extends BaseMetricESDAO { - @PostConstruct public void init() { super.indexName = TOPIC_INDEX; @@ -34,8 +32,6 @@ public class TopicMetricESDAO extends BaseMetricESDAO { register(this); } - protected FutureWaitUtil queryFuture = FutureWaitUtil.init("TopicMetricESDAO", 4,8, 500); - public List listTopicMaxMinMetrics(Long clusterPhyId, List topics, String metric, boolean max, Long startTime, Long endTime){ //1、获取需要查下的索引 String realIndex = realIndex(startTime, endTime); @@ -128,8 +124,13 @@ public class TopicMetricESDAO extends BaseMetricESDAO { ? dslLoaderUtil.getFormatDslByFileName( DslConstant.COUNT_TOPIC_METRIC_VALUE, clusterPhyId, topic, startTime, endTime, termDsl) : dslLoaderUtil.getFormatDslByFileName( DslConstant.COUNT_TOPIC_NOT_METRIC_VALUE, clusterPhyId, topic, startTime, endTime, termDsl); - return esOpClient.performRequestWithRouting(topic, realIndex, dsl, - s -> handleESQueryResponseCount(s), 3); + return esOpClient.performRequestWithRouting( + topic, + realIndex, + dsl, + s -> handleESQueryResponseCount(s), + DEFAULT_RETRY_TIME + ); } /** @@ -207,38 +208,52 @@ public class TopicMetricESDAO extends BaseMetricESDAO { * 获取每个 metric 的 topN 个 topic 的指标,如果获取不到 topN 的topics, 则默认返回 defaultTopics 的指标 */ public Table> listTopicMetricsByTopN(Long clusterPhyId, - List defaultTopics, - List metrics, + List defaultTopicNameList, + List metricNameList, String aggType, int topN, Long startTime, Long endTime){ //1、获取topN要查询的topic,每一个指标的topN的topic可能不一样 - Map> metricTopics = this.getTopNTopics(clusterPhyId, metrics, aggType, topN, startTime, endTime); + Map> metricTopicsMap = this.getTopNTopics(clusterPhyId, metricNameList, aggType, topN, startTime, endTime); - Table> table = HashBasedTable.create(); + //2、获取topics列表 + Set topicNameSet = new HashSet<>(defaultTopicNameList); + metricTopicsMap.values().forEach(elem -> topicNameSet.addAll(elem)); - for(String metric : metrics) { - table.putAll(this.listTopicMetricsByTopics( - clusterPhyId, - Arrays.asList(metric), - aggType, - metricTopics.getOrDefault(metric, defaultTopics), - startTime, - endTime) - ); + //3、批量获取信息 + Table> allMetricsTable = this.listTopicMetricsByTopics( + clusterPhyId, + metricNameList, + aggType, + new ArrayList<>(topicNameSet), + startTime, + endTime + ); + + //4、获取Top-Metric + Table> metricsTable = HashBasedTable.create(); + for(String metricName: metricNameList) { + for (String topicName: metricTopicsMap.getOrDefault(metricName, defaultTopicNameList)) { + List voList = allMetricsTable.get(metricName, topicName); + if (voList == null) { + continue; + } + + metricsTable.put(metricName, topicName, voList); + } } - return table; + return metricsTable; } /** * 获取每个 metric 指定个 topic 的指标 */ public Table> listTopicMetricsByTopics(Long clusterPhyId, - List metrics, + List metricNameList, String aggType, - List topics, + List topicNameList, Long startTime, Long endTime){ //1、获取需要查下的索引 @@ -248,37 +263,47 @@ public class TopicMetricESDAO extends BaseMetricESDAO { String interval = MetricsUtils.getInterval(endTime - startTime); //3、构造agg查询条件 - String aggDsl = buildAggsDSL(metrics, aggType); + String aggDsl = buildAggsDSL(metricNameList, aggType); final Table> table = HashBasedTable.create(); //4、构造dsl查询条件 - for(String topic : topics){ + for(String topicName : topicNameList){ try { - queryFuture.runnableTask( - String.format("class=TopicMetricESDAO||method=listTopicMetricsByTopics||ClusterPhyId=%d||topicName=%s", - clusterPhyId, topic), + esTPService.submitSearchTask( + String.format("class=TopicMetricESDAO||method=listTopicMetricsByTopics||ClusterPhyId=%d||topicName=%s", clusterPhyId, topicName), 3000, () -> { String dsl = dslLoaderUtil.getFormatDslByFileName( - DslConstant.GET_TOPIC_AGG_LIST_METRICS, clusterPhyId, topic, startTime, endTime, interval, aggDsl); + DslConstant.GET_TOPIC_AGG_LIST_METRICS, + clusterPhyId, + topicName, + startTime, + endTime, + interval, + aggDsl + ); - Map> metricMap = esOpClient.performRequestWithRouting(topic, realIndex, dsl, - s -> handleListESQueryResponse(s, metrics, aggType), 3); + Map> metricMap = esOpClient.performRequestWithRouting( + topicName, + realIndex, + dsl, + s -> handleListESQueryResponse(s, metricNameList, aggType), + DEFAULT_RETRY_TIME + ); synchronized (table){ - for(String metric : metricMap.keySet()){ - table.put(metric, topic, metricMap.get(metric)); + for(Map.Entry> entry: metricMap.entrySet()){ + table.put(entry.getKey(), topicName, entry.getValue()); } } }); }catch (Exception e){ - LOGGER.error("method=listBrokerMetricsByBrokerIds||clusterPhyId={}||brokerId{}||errMsg=exception!", - clusterPhyId, topic, e); + LOGGER.error("method=listTopicMetricsByTopics||clusterPhyId={}||topicName={}||errMsg=exception!", clusterPhyId, topicName, e); } } - queryFuture.waitExecute(); + esTPService.waitExecute(); return table; } @@ -339,7 +364,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ Map> metricMap = new HashMap<>(); - Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); if(null == esAggrMap){return metricMap;} for(String metric : metrics){ @@ -352,15 +377,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); if(value == null){return;} - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setTimeStamp(timestamp); - metricPoint.setValue(value.toString()); - metricPoint.setName(metric); - - metricPoints.add(metricPoint); - }else { - LOGGER.info(""); + metricPoints.add(new MetricPointVO(metric, timestamp, value.toString(), aggType)); } }catch (Exception e){ LOGGER.error("method=handleListESQueryResponse||metric={}||errMsg=exception!", metric, e); @@ -373,7 +390,7 @@ public class TopicMetricESDAO extends BaseMetricESDAO { return metricMap; } - private Map> handleTopTopicESQueryResponse(ESQueryResponse response, List metrics, int topN){ + private Map> handleTopTopicESQueryResponse(ESQueryResponse response, List metricNameList, int topN){ Map> ret = new HashMap<>(); Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); @@ -382,57 +399,37 @@ public class TopicMetricESDAO extends BaseMetricESDAO { Map>> metricsTopicValueMap = new HashMap<>(); //1、先获取每个指标对应的所有 topic 以及指标的值 - for(String metric : metrics) { + for(String metricName: metricNameList) { esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { try { if (null != esBucket.getUnusedMap().get(KEY)) { String topic = esBucket.getUnusedMap().get(KEY).toString(); - Double value = Double.valueOf(esBucket.getAggrMap().get(HIST).getBucketList().get(0).getAggrMap() - .get(metric).getUnusedMap().get(VALUE).toString()); + Double value = Double.valueOf( + esBucket.getAggrMap().get(HIST).getBucketList().get(0).getAggrMap().get(metricName).getUnusedMap().get(VALUE).toString() + ); - List> brokerValue = (null == metricsTopicValueMap.get(metric)) ? - new ArrayList<>() : metricsTopicValueMap.get(metric); - - brokerValue.add(new Tuple<>(topic, value)); - metricsTopicValueMap.put(metric, brokerValue); + metricsTopicValueMap.putIfAbsent(metricName, new ArrayList<>()); + metricsTopicValueMap.get(metricName).add(new Tuple<>(topic, value)); } }catch (Exception e){ - LOGGER.error("method=handleTopBrokerESQueryResponse||metric={}||errMsg=exception!", metric, e); + LOGGER.error("method=handleTopTopicESQueryResponse||metricName={}||errMsg=exception!", metricName, e); } } ); } //2、对每个指标的broker按照指标值排序,并截取前topN个brokerIds - for(String metric : metricsTopicValueMap.keySet()){ - List> brokerValue = metricsTopicValueMap.get(metric); + for(Map.Entry>> entry: metricsTopicValueMap.entrySet()){ + entry.getValue().sort((o1, o2) -> { + if(null == o1 || null == o2) { + return 0; + } - brokerValue.sort((o1, o2) -> { - if(null == o1 || null == o2){return 0;} return o2.getV2().compareTo(o1.getV2()); } ); - List> temp = (brokerValue.size() > topN) ? brokerValue.subList(0, topN) : brokerValue; - List topics = temp.stream().map(t -> t.getV1()).collect(Collectors.toList()); + List topicNameList = entry.getValue().subList(0, Math.min(entry.getValue().size(), topN)).stream().map(t -> t.getV1()).collect(Collectors.toList()); - ret.put(metric, topics); - } - - return ret; - } - - private Map>> topicMetricMap2MetricTopicMap( - Map>> topicMetricMap){ - Map>> ret = new HashMap<>(); - - for(String topic : topicMetricMap.keySet()){ - Map> metricMap = topicMetricMap.get(topic); - - for(String metric : metricMap.keySet()){ - Map> brokerMap = (null == ret.get(metric)) ? new HashMap<>() : ret.get(metric); - - brokerMap.put(topic, metricMap.get(metric)); - ret.put(metric, brokerMap); - } + ret.put(entry.getKey(), topicNameList); } return ret; diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java index 07394f51..31256efe 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectClusterMetricESDAO.java @@ -5,13 +5,11 @@ import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.persistence.es.dao.BaseMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; import java.util.*; @@ -29,8 +27,6 @@ public class ConnectClusterMetricESDAO extends BaseMetricESDAO { register( this); } - protected FutureWaitUtil queryFuture = FutureWaitUtil.init("ConnectClusterMetricESDAO", 4,8, 500); - /** * 获取集群 clusterPhyId 中每个 metric 的 topN 的 connectCluster 在指定时间[startTime、endTime]区间内所有的指标 * topN 按照[startTime, endTime] 时间段内最后一个值来排序 @@ -97,7 +93,7 @@ public class ConnectClusterMetricESDAO extends BaseMetricESDAO { aggDsl ); - queryFuture.runnableTask( + esTPService.submitSearchTask( String.format("class=ConnectClusterMetricESDAO||method=listMetricsByConnectClusterIdList||ClusterPhyId=%d", clusterPhyId), 5000, () -> { @@ -106,24 +102,24 @@ public class ConnectClusterMetricESDAO extends BaseMetricESDAO { realIndex, dsl, s -> handleListESQueryResponse(s, metricNameList, aggType), - 3 + DEFAULT_RETRY_TIME ); synchronized (table) { - for(String metric : metricMap.keySet()){ - table.put(metric, connectClusterId, metricMap.get(metric)); + for(Map.Entry> entry : metricMap.entrySet()){ + table.put(entry.getKey(), connectClusterId, entry.getValue()); } } }); } catch (Exception e) { LOGGER.error( - "class=ConnectClusterMetricESDAO||method=listMetricsByConnectClusterIdList||clusterPhyId={}||connectClusterId{}||errMsg=exception!", + "method=listMetricsByConnectClusterIdList||clusterPhyId={}||connectClusterId={}||errMsg=exception!", clusterPhyId, connectClusterId, e ); } } - queryFuture.waitExecute(); + esTPService.waitExecute(); return table; } @@ -167,107 +163,89 @@ public class ConnectClusterMetricESDAO extends BaseMetricESDAO { /**************************************************** private method ****************************************************/ - private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ + private Map> handleListESQueryResponse(ESQueryResponse response, List metricNameList, String aggType){ Map> metricMap = new HashMap<>(); - if(null == response || null == response.getAggs()){ + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap) { return metricMap; } - Map esAggrMap = response.getAggs().getEsAggrMap(); - if (null == esAggrMap || null == esAggrMap.get(HIST)) { - return metricMap; - } - - if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ - return metricMap; - } - - for(String metric : metrics){ + for(String metricName : metricNameList){ List metricPoints = new ArrayList<>(); - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + esAggrMap.get(HIST).getBucketList().forEach(esBucket -> { try { if (null != esBucket.getUnusedMap().get(KEY)) { Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); - Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); - if(null == value){return;} + Object value = esBucket.getAggrMap().get(metricName).getUnusedMap().get(VALUE); + if(null == value) { + return; + } - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setTimeStamp(timestamp); - metricPoint.setValue(value.toString()); - metricPoint.setName(metric); - - metricPoints.add(metricPoint); - }else { - LOGGER.info(""); + metricPoints.add(new MetricPointVO(metricName, timestamp, value.toString(), aggType)); } }catch (Exception e){ - LOGGER.error("metric={}||errMsg=exception!", metric, e); + LOGGER.error("method=handleListESQueryResponse||metricName={}||errMsg=exception!", metricName, e); } } ); - metricMap.put(metric, optimizeMetricPoints(metricPoints)); + metricMap.put(metricName, optimizeMetricPoints(metricPoints)); } return metricMap; } - private Map> handleTopConnectClusterESQueryResponse(ESQueryResponse response, List metrics, int topN){ + private Map> handleTopConnectClusterESQueryResponse(ESQueryResponse response, List metricNameList, int topN){ Map> ret = new HashMap<>(); - if(null == response || null == response.getAggs()){ + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap) { return ret; } - Map esAggrMap = response.getAggs().getEsAggrMap(); - if (null == esAggrMap || null == esAggrMap.get(HIST)) { - return ret; - } - - if(CollectionUtils.isEmpty(esAggrMap.get(HIST).getBucketList())){ - return ret; - } - - Map>> metricBrokerValueMap = new HashMap<>(); + Map>> metricConnectClusterValueMap = new HashMap<>(); //1、先获取每个指标对应的所有brokerIds以及指标的值 - for(String metric : metrics) { - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + for(String metricName : metricNameList) { + esAggrMap.get(HIST).getBucketList().forEach(esBucket -> { try { - if (null != esBucket.getUnusedMap().get(KEY)) { - Long connectorClusterId = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); - Object value = esBucket.getAggrMap().get(HIST).getBucketList() - .get(0).getAggrMap().get(metric).getUnusedMap().get(VALUE); - - if(null == value){return;} - - List> connectorClusterValue = (null == metricBrokerValueMap.get(metric)) ? - new ArrayList<>() : metricBrokerValueMap.get(metric); - - connectorClusterValue.add(new Tuple<>(connectorClusterId, Double.valueOf(value.toString()))); - metricBrokerValueMap.put(metric, connectorClusterValue); + if (null == esBucket.getUnusedMap().get(KEY)) { + return; } + + Long connectorClusterId = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); + Object value = esBucket.getAggrMap().get(HIST).getBucketList().get(0).getAggrMap().get(metricName).getUnusedMap().get(VALUE); + if(null == value) { + return; + } + + metricConnectClusterValueMap.putIfAbsent(metricName, new ArrayList<>()); + metricConnectClusterValueMap.get(metricName).add(new Tuple<>(connectorClusterId, Double.valueOf(value.toString()))); }catch (Exception e){ - LOGGER.error("metric={}||errMsg=exception!", metric, e); + LOGGER.error("method=handleTopConnectClusterESQueryResponse||metricName={}||errMsg=exception!", metricName, e); } } ); } - //2、对每个指标的broker按照指标值排序,并截取前topN个brokerIds - for(String metric : metricBrokerValueMap.keySet()){ - List> connectorClusterValue = metricBrokerValueMap.get(metric); + //2、对每个指标的connect按照指标值排序,并截取前topN个connectIds + for(Map.Entry>> entry : metricConnectClusterValueMap.entrySet()){ + + entry.getValue().sort((o1, o2) -> { + if(null == o1 || null == o2) { + return 0; + } - connectorClusterValue.sort((o1, o2) -> { - if(null == o1 || null == o2){return 0;} return o2.getV2().compareTo(o1.getV2()); - } ); + }); - List> temp = (connectorClusterValue.size() > topN) ? connectorClusterValue.subList(0, topN) : connectorClusterValue; - List connectorClusterIds = temp.stream().map(t -> t.getV1()).collect(Collectors.toList()); + List connectorClusterIdList = entry.getValue() + .subList(0, Math.min(entry.getValue().size(), topN)) + .stream() + .map(t -> t.getV1()) + .collect(Collectors.toList()); - ret.put(metric, connectorClusterIds); + ret.put(entry.getKey(), connectorClusterIdList); } return ret; diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java index 060f57be..1c47e862 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java @@ -8,7 +8,6 @@ import com.google.common.collect.Table; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchTerm; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect.ConnectorMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; import com.xiaojukeji.know.streaming.km.common.utils.Triple; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; @@ -33,43 +32,55 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { register( this); } - protected FutureWaitUtil queryFuture = FutureWaitUtil.init("ConnectorMetricESDAO", 4,8, 500); - - /** - * 获取每个 metric 的 topN 个 connector 的指标,如果获取不到 topN 的topics, 则默认返回 defaultTopics 的指标 + * 获取每个 metric 的 topN 个 connector 的指标,如果获取不到 topN 的 connectors, 则默认返回 defaultTopics 的指标 */ public Table, List> listMetricsByTopN(Long clusterPhyId, - List> defaultConnectorList, - List metricNameList, - String aggType, - int topN, - Long startTime, - Long endTime){ + List> defaultConnectorList, + List metricNameList, + String aggType, + int topN, + Long startTime, + Long endTime) { //1、获取topN要查询的topic,每一个指标的topN的topic可能不一样 Map>> metricsMap = this.getTopNConnectors(clusterPhyId, metricNameList, aggType, topN, startTime, endTime); - Table, List> table = HashBasedTable.create(); + //2、获取connector列表 + Set> connectorSet = new HashSet<>(defaultConnectorList); + metricsMap.values().forEach(elem -> connectorSet.addAll(elem)); - for(String metricName : metricNameList){ - table.putAll(this.listMetricsByConnectors( - clusterPhyId, - Arrays.asList(metricName), - aggType, - metricsMap.getOrDefault(metricName, defaultConnectorList), - startTime, - endTime) - ); + //3、批量获取信息 + Table, List> allMetricsTable = this.listMetricsByConnectors( + clusterPhyId, + metricNameList, + aggType, + new ArrayList<>(connectorSet), + startTime, + endTime + ); + + //4、获取Top-Metric + Table, List> metricTable = HashBasedTable.create(); + for (String metricName: metricNameList) { + for (Tuple connector: metricsMap.getOrDefault(metricName, defaultConnectorList)) { + List voList = allMetricsTable.get(metricName, connector); + if (voList == null) { + continue; + } + + metricTable.put(metricName, connector, voList); + } } - return table; + // 返回结果 + return metricTable; } public List getConnectorLatestMetric(Long clusterPhyId, List> connectClusterIdAndConnectorNameList, List metricsNames){ List connectorMetricPOS = new CopyOnWriteArrayList<>(); for(Tuple connectClusterIdAndConnectorName : connectClusterIdAndConnectorNameList){ - queryFuture.runnableTask( + esTPService.submitSearchTask( "getConnectorLatestMetric", 30000, () -> { @@ -78,7 +89,7 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { }); } - queryFuture.waitExecute(); + esTPService.waitExecute(); return connectorMetricPOS; } @@ -112,11 +123,11 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { * 获取每个 metric 指定个 topic 的指标 */ public Table, List> listMetricsByConnectors(Long clusterPhyId, - List metrics, - String aggType, - List> connectorList, - Long startTime, - Long endTime) { + List metricNameList, + String aggType, + List> connectorList, + Long startTime, + Long endTime) { //1、获取需要查下的索引 String realIndex = realIndex(startTime, endTime); @@ -124,17 +135,15 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { String interval = MetricsUtils.getInterval(endTime - startTime); //3、构造agg查询条件 - String aggDsl = buildAggsDSL(metrics, aggType); + String aggDsl = buildAggsDSL(metricNameList, aggType); final Table, List> table = HashBasedTable.create(); //4、构造dsl查询条件 for(Tuple connector : connectorList) { try { - queryFuture.runnableTask( - String.format( - "method=listConnectorMetricsByConnectors||ClusterPhyId=%d||connectorName=%s", - clusterPhyId, connector.getV2() ), + esTPService.submitSearchTask( + String.format("class=ConnectorMetricESDAO||method=listMetricsByConnectors||ClusterPhyId=%d||connectorName=%s", clusterPhyId, connector.getV2()), 3000, () -> { String dsl = dslLoaderUtil.getFormatDslByFileName( @@ -152,25 +161,25 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { connector.getV1().toString(), realIndex, dsl, - s -> handleListESQueryResponse(s, metrics, aggType), + s -> handleListESQueryResponse(s, metricNameList, aggType), 3 ); - synchronized (table){ - for(String metric : metricMap.keySet()){ - table.put(metric, connector, metricMap.get(metric)); + synchronized (table) { + for(Map.Entry> entry: metricMap.entrySet()){ + table.put(entry.getKey(), connector, entry.getValue()); } } }); } catch (Exception e) { LOGGER.error( - "method=listConnectorMetricsByConnectors||clusterPhyId={}||connectorName{}||errMsg=exception!", - clusterPhyId, connector.getV2(), e + "method=listMetricsByConnectors||clusterPhyId={}||connectClusterId={}||connectorName{}||errMsg=exception!", + clusterPhyId, connector.getV1(), connector.getV2(), e ); } } - queryFuture.waitExecute(); + esTPService.waitExecute(); return table; } @@ -181,7 +190,7 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { String aggType, int topN, Long startTime, - Long endTime){ + Long endTime) { //1、获取需要查下的索引 String realIndex = realIndex(startTime, endTime); @@ -209,46 +218,19 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { ); } + /**************************************************** private method ****************************************************/ - private Table handleSingleESQueryResponse(ESQueryResponse response, List metrics, String aggType){ - Table table = HashBasedTable.create(); - - Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); - if(null == esAggrMap){return table;} - - for(String metric : metrics){ - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { - try { - if (null != esBucket.getUnusedMap().get(KEY)) { - String topic = esBucket.getUnusedMap().get(KEY).toString(); - String value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE).toString(); - - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setValue(value); - metricPoint.setName(metric); - - table.put(topic, metric, metricPoint); - }else { - LOGGER.debug("method=handleListESQueryResponse||metric={}||errMsg=get topic is null!", metric); - } - }catch (Exception e){ - LOGGER.error("method=handleListESQueryResponse||metric={}||errMsg=exception!", metric, e); - } - }); - } - - return table; - } private Map> handleListESQueryResponse(ESQueryResponse response, List metrics, String aggType){ Map> metricMap = new HashMap<>(); - Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); - if(null == esAggrMap){return metricMap;} + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); + if(null == esAggrMap) { + return metricMap; + } - for(String metric : metrics){ + for(String metric : metrics) { List metricPoints = new ArrayList<>(); esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { @@ -256,17 +238,11 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { if (null != esBucket.getUnusedMap().get(KEY)) { Long timestamp = Long.valueOf(esBucket.getUnusedMap().get(KEY).toString()); Object value = esBucket.getAggrMap().get(metric).getUnusedMap().get(VALUE); - if(value == null){return;} + if(value == null){ + return; + } - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setTimeStamp(timestamp); - metricPoint.setValue(value.toString()); - metricPoint.setName(metric); - - metricPoints.add(metricPoint); - }else { - LOGGER.info(""); + metricPoints.add(new MetricPointVO(metric, timestamp, value.toString(), aggType)); } }catch (Exception e){ LOGGER.error("method=handleListESQueryResponse||metric={}||errMsg=exception!", metric, e); @@ -279,10 +255,12 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { return metricMap; } - private Map>> handleTopConnectorESQueryResponse(ESQueryResponse response, List metricNameList, int topN){ + private Map>> handleTopConnectorESQueryResponse(ESQueryResponse response, + List metricNameList, + int topN) { Map>> ret = new HashMap<>(); - Map esAggrMap = checkBucketsAndHitsOfResponseAggs(response); + Map esAggrMap = this.checkBucketsAndHitsOfResponseAggs(response); if(null == esAggrMap) { return ret; } @@ -291,7 +269,7 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { // 1、先获取每个指标对应的所有 connector 以及指标的值 for(String metricName : metricNameList) { - esAggrMap.get(HIST).getBucketList().forEach( esBucket -> { + esAggrMap.get(HIST).getBucketList().forEach(esBucket -> { try { if (null != esBucket.getUnusedMap().get(KEY)) { String connectorNameAndClusterId = esBucket.getUnusedMap().get(KEY).toString(); @@ -338,24 +316,6 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { return ret; } - private Map>> topicMetricMap2MetricTopicMap( - Map>> topicMetricMap){ - Map>> ret = new HashMap<>(); - - for(String topic : topicMetricMap.keySet()){ - Map> metricMap = topicMetricMap.get(topic); - - for(String metric : metricMap.keySet()){ - Map> brokerMap = (null == ret.get(metric)) ? new HashMap<>() : ret.get(metric); - - brokerMap.put(topic, metricMap.get(metric)); - ret.put(metric, brokerMap); - } - } - - return ret; - } - private Tuple splitConnectorNameAndClusterId(String connectorNameAndClusterId){ String[] ss = connectorNameAndClusterId.split("#"); if(null == ss || ss.length != 2){return null;} diff --git a/km-rest/src/main/resources/application.yml b/km-rest/src/main/resources/application.yml index c9753b7e..291c6741 100644 --- a/km-rest/src/main/resources/application.yml +++ b/km-rest/src/main/resources/application.yml @@ -50,7 +50,6 @@ logging: thread-pool: scheduled: thread-num: 2 # @Scheduled任务的线程池大小,默认是一个 - collector: # 采集模块的配置 future-util: # 采集模块线程池配置 num: 3 # 线程池个数 @@ -58,7 +57,6 @@ thread-pool: queue-size: 10000 # 每个线程池队列大小 select-suitable-enable: true # 任务是否自动选择合适的线程池,非主要,可不修改 suitable-queue-size: 1000 # 线程池理想的队列大小,非主要,可不修改 - task: # 任务模块的配置 metrics: # metrics采集任务配置 thread-num: 18 # metrics采集任务线程池核心线程数 @@ -69,6 +67,10 @@ thread-pool: common: # 剩余其他任务配置 thread-num: 15 # 剩余其他任务线程池核心线程数 queue-size: 150 # 剩余其他任务线程池队列大小 + es: + search: # es查询线程池 + thread-num: 10 # 线程池大小 + queue-size: 5000 # 队列大小 # 客户端池大小相关配置 From 1c4fbef9f2dd7250be71108c44111218c907b4cb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 9 Dec 2022 15:44:09 +0800 Subject: [PATCH 067/150] =?UTF-8?q?[Feature]=E6=94=AF=E6=8C=81=E6=8B=86?= =?UTF-8?q?=E5=88=86API=E6=9C=8D=E5=8A=A1=E5=92=8CJob=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E9=83=A8=E7=BD=B2(#829)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、JMX检查功能是每一个KS都必须要有的,因此从Task模块移动到Core模块; 2、application.yml中补充Task模块任务的整体开关字段; --- .../core/flusher/JmxClientLegalFlusher.java | 44 ++++++++++++++ km-rest/src/main/resources/application.yml | 3 +- .../task/kafka/client/CheckJmxClientTask.java | 60 ------------------- 3 files changed, 46 insertions(+), 61 deletions(-) create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/JmxClientLegalFlusher.java delete mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/JmxClientLegalFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/JmxClientLegalFlusher.java new file mode 100644 index 00000000..40a55d47 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/JmxClientLegalFlusher.java @@ -0,0 +1,44 @@ +package com.xiaojukeji.know.streaming.km.core.flusher; + +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.utils.FutureUtil; +import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; +import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +/** + * JMX连接检查 + */ +@Service +public class JmxClientLegalFlusher { + private static final ILog LOGGER = LogFactory.getLog(JmxClientLegalFlusher.class); + + @Autowired + private BrokerService brokerService; + + @Autowired + private KafkaJMXClient kafkaJMXClient; + + @Scheduled(cron="0 0/1 * * * ?") + public void checkJmxClient() { + for (ClusterPhy clusterPhy: LoadedClusterPhyCache.listAll().values()) { + FutureUtil.quickStartupFutureUtil.submitTask( + () -> { + try { + kafkaJMXClient.checkAndRemoveIfIllegal( + clusterPhy.getId(), + brokerService.listAliveBrokersFromDB(clusterPhy.getId()) + ); + } catch (Exception e) { + LOGGER.error("method=checkJmxClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); + } + } + ); + } + } +} diff --git a/km-rest/src/main/resources/application.yml b/km-rest/src/main/resources/application.yml index 291c6741..efbafef5 100644 --- a/km-rest/src/main/resources/application.yml +++ b/km-rest/src/main/resources/application.yml @@ -31,8 +31,9 @@ spring: init-sql: true init-thread-num: 20 max-thread-num: 50 - log-expire: 3 # 日志保存天数,以天为单位 + log-expire: 3 # 日志保存天数,以天为单位 app-name: know-streaming + enable: true # true表示开启job任务, false表关闭。KS在部署上可以考虑部署两套服务,一套处理前端请求,一套执行job任务,此时可以通过该字段进行控制 claim-strategy: com.didiglobal.logi.job.core.consensual.RandomConsensual logi-security: # know-streaming 依赖的 logi-security 模块的数据库的配置,默认与 know-streaming 的数据库配置保持一致即可 jdbc-url: jdbc:mariadb://127.0.0.1:3306/know_streaming?useUnicode=true&characterEncoding=utf8&jdbcCompliantTruncation=true&allowMultiQueries=true&useSSL=false&alwaysAutoGeneratedKeys=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java deleted file mode 100644 index 2e67fcaa..00000000 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/client/CheckJmxClientTask.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.xiaojukeji.know.streaming.km.task.kafka.client; - -import com.didiglobal.logi.job.annotation.Task; -import com.didiglobal.logi.job.common.TaskResult; -import com.didiglobal.logi.job.core.consensual.ConsensualEnum; -import com.didiglobal.logi.job.core.job.Job; -import com.didiglobal.logi.job.core.job.JobContext; -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.core.service.broker.BrokerService; -import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; -import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; -import org.springframework.beans.factory.annotation.Autowired; - -@Task(name = "CheckJmxClientTask", - description = "检查Jmx客户端", - cron = "0 0/1 * * * ? *", - autoRegister = true, - timeout = 2 * 60, - consensual = ConsensualEnum.BROADCAST) -public class CheckJmxClientTask implements Job { - private static final ILog log = LogFactory.getLog(CheckJmxClientTask.class); - - @Autowired - private BrokerService brokerService; - - @Autowired - private KafkaJMXClient kafkaJMXClient; - - @Override - public TaskResult execute(JobContext jobContext) { - boolean status = true; - for (ClusterPhy clusterPhy: LoadedClusterPhyCache.listAll().values()) { - if (this.checkJmxClient(clusterPhy)) { - continue; - } - - status = false; - } - - return status? TaskResult.SUCCESS: TaskResult.FAIL; - } - - private boolean checkJmxClient(ClusterPhy clusterPhy) { - try { - kafkaJMXClient.checkAndRemoveIfIllegal( - clusterPhy.getId(), - brokerService.listAliveBrokersFromDB(clusterPhy.getId()) - ); - - return true; - } catch (Exception e) { - log.error("method=checkJmxClient||clusterPhyId={}||errMsg=exception", clusterPhy.getId(), e); - } - - return false; - } - -} From 4543a339b7f47b792fbb0e37a172fb980fce9cc4 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Fri, 9 Dec 2022 15:33:20 +0800 Subject: [PATCH 068/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8Djob=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=B8=AD=E7=9A=84=E6=95=B0=E7=BB=84=E8=B6=8A=E7=95=8C?= =?UTF-8?q?=E6=8A=A5=E9=94=99(#744)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/entity/reassign/ReassignResult.java | 7 --- .../km/common/utils/CommonUtils.java | 16 +++++++ .../reassign/impl/ReassignJobServiceImpl.java | 44 ++++++++++++++----- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/reassign/ReassignResult.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/reassign/ReassignResult.java index 92dbe77d..8a734b1e 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/reassign/ReassignResult.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/reassign/ReassignResult.java @@ -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); - } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java index f3d2b357..48601795 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/CommonUtils.java @@ -261,4 +261,20 @@ public class CommonUtils { return null; } } + + + /** + * 校验两个list的第一个元素是否相等,以","分隔元素。 + * @param str1 + * @param str2 + * @return + */ + public static boolean checkFirstElementIsEquals(String str1, String str2) { + if (ValidateUtils.anyBlank(str1, str2)) { + return false; + } + Integer targetLeader = CommonUtils.string2IntList(str1).get(0); + Integer originalLeader = CommonUtils.string2IntList(str2).get(0); + return originalLeader.equals(targetLeader); + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java index 30c2f6e1..5f9b737d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/reassign/impl/ReassignJobServiceImpl.java @@ -28,6 +28,7 @@ import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.common.utils.kafka.KafkaReassignUtil; 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.oprecord.OpLogWrapService; @@ -385,11 +386,13 @@ public class ReassignJobServiceImpl implements ReassignJobService { // 更新任务状态 rv = this.checkAndSetSuccessIfFinished(jobPO, rrr.getData()); - if (rv.successful()){ + + //如果任务还未完成,先返回,不必考虑优先副本的重新选举。 + if (!rv.successful()) { return Result.buildFromIgnoreData(rv); } - //已完成 + //任务已完成,检查是否需要重新选举,并进行选举。 rv = this.preferredReplicaElection(jobId); @@ -500,10 +503,8 @@ public class ReassignJobServiceImpl implements ReassignJobService { List subJobPOList = this.getSubJobsByJobId(jobId); List topicPartitions = new ArrayList<>(); subJobPOList.stream().forEach(reassignPO -> { - Integer targetLeader = CommonUtils.string2IntList(reassignPO.getReassignBrokerIds()).get(0); - Integer originalLeader = CommonUtils.string2IntList(reassignPO.getOriginalBrokerIds()).get(0); //替换过leader的添加到优先副本选举任务列表 - if (!originalLeader.equals(targetLeader)){ + if (!CommonUtils.checkFirstElementIsEquals(reassignPO.getReassignBrokerIds(), reassignPO.getOriginalBrokerIds())) { topicPartitions.add(new TopicPartition(reassignPO.getTopicName(), reassignPO.getPartitionId())); } }); @@ -534,8 +535,12 @@ public class ReassignJobServiceImpl implements ReassignJobService { if (dbSubPO == null) { // DB中不存在 reassignSubJobDAO.insert(elem); + return; } + //补全缺失信息 + this.completeInfo(elem,dbSubPO); + // 已存在则进行更新 elem.setId(dbSubPO.getId()); reassignSubJobDAO.updateById(elem); @@ -565,13 +570,10 @@ public class ReassignJobServiceImpl implements ReassignJobService { long now = System.currentTimeMillis(); boolean existNotFinished = false; - boolean unNeedPreferredReplicaElection = true; + boolean jobSucceed = false; List subJobPOList = this.getSubJobsByJobId(jobPO.getId()); for (ReassignSubJobPO subJobPO: subJobPOList) { - if (!reassignmentResult.checkPreferredReplicaElectionUnNeed(subJobPO.getReassignBrokerIds(),subJobPO.getOriginalBrokerIds())) { - unNeedPreferredReplicaElection = false; - } if (!reassignmentResult.checkPartitionFinished(subJobPO.getTopicName(), subJobPO.getPartitionId())) { existNotFinished = true; @@ -591,12 +593,13 @@ public class ReassignJobServiceImpl implements ReassignJobService { // 当前没有分区处于迁移中, 并且没有任务并不处于执行中 ReassignJobPO newJobPO = new ReassignJobPO(); newJobPO.setId(jobPO.getId()); + jobSucceed = true; newJobPO.setStatus(JobStatusEnum.SUCCESS.getStatus()); newJobPO.setFinishedTime(new Date(now)); reassignJobDAO.updateById(newJobPO); } - return Result.build(unNeedPreferredReplicaElection); + return Result.build(jobSucceed); } private Result> setJobInRunning(ReassignJobPO jobPO) { @@ -861,4 +864,25 @@ public class ReassignJobServiceImpl implements ReassignJobService { return returnRV; } + + private void completeInfo(ReassignSubJobPO newPO, ReassignSubJobPO dbPO) { + if (newPO.getJobId() == null) { + newPO.setJobId(dbPO.getJobId()); + } + if (newPO.getTopicName() == null) { + newPO.setTopicName(dbPO.getTopicName()); + } + if (newPO.getClusterPhyId() == null) { + newPO.setClusterPhyId(dbPO.getClusterPhyId()); + } + if (newPO.getPartitionId() == null) { + newPO.setPartitionId(dbPO.getPartitionId()); + } + if (newPO.getOriginalBrokerIds() == null || newPO.getOriginalBrokerIds().isEmpty()) { + newPO.setOriginalBrokerIds(dbPO.getOriginalBrokerIds()); + } + if (newPO.getReassignBrokerIds() == null || newPO.getReassignBrokerIds().isEmpty()) { + newPO.setReassignBrokerIds(dbPO.getReassignBrokerIds()); + } + } } From cd2c388e68fd3a27387f5eadf0054333f4cee5b5 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 14 Dec 2022 14:07:30 +0800 Subject: [PATCH 069/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96Sonar?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=89=AB=E6=8F=8F=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connect/connector/impl/ConnectorMetricServiceImpl.java | 6 +++--- .../km/core/service/topic/impl/TopicMetricServiceImpl.java | 2 +- .../km/rest/api/v3/cluster/ClusterConnectsController.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java index 10325e02..c40d1ee1 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java @@ -392,13 +392,13 @@ public class ConnectorMetricServiceImpl extends BaseConnectorMetricService imple } private List> listTopNConnectorList(Long clusterPhyId, Integer topN) { - List connectorPOS = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); + List poList = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); - if (CollectionUtils.isEmpty(connectorPOS)) { + if (CollectionUtils.isEmpty(poList)) { return new ArrayList<>(); } - return connectorPOS.subList(0, Math.min(topN, connectorPOS.size())) + return poList.subList(0, Math.min(topN, poList.size())) .stream() .map( c -> new Tuple<>(c.getId(), c.getConnectorName()) ) .collect(Collectors.toList()); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java index 731bc548..d4a12b41 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java @@ -185,7 +185,7 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe Table> retTable; if(CollectionUtils.isEmpty(topics)) { //如果 es 中获取不到topN的topic就使用从数据库中获取的topics - List defaultTopics = listTopNTopics(clusterId, topN); + List defaultTopics = this.listTopNTopics(clusterId, topN); retTable = topicMetricESDAO.listTopicMetricsByTopN(clusterId, defaultTopics, metrics, aggType, topN, startTime, endTime ); }else { retTable = topicMetricESDAO.listTopicMetricsByTopics(clusterId, metrics, aggType, topics, startTime, endTime); diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java index e7d93af8..9f679d31 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterConnectsController.java @@ -119,7 +119,7 @@ public class ClusterConnectsController { @PostMapping(value = "clusters/{clusterPhyId}/connectors-metrics") @ResponseBody public Result> getClusterPhyMetrics(@PathVariable Long clusterPhyId, - @Validated @RequestBody MetricsConnectorsDTO dto) { + @Validated @RequestBody MetricsConnectorsDTO dto) { return connectorMetricService.listConnectClusterMetricsFromES(clusterPhyId, dto); } From 896a94358749bf13868b4bd25bd74170557f20e4 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 14 Dec 2022 14:10:46 +0800 Subject: [PATCH 070/150] =?UTF-8?q?[Optimize]=E7=BC=A9=E7=9F=ADES=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E9=BB=98=E8=AE=A4=E4=BF=9D=E5=AD=98=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E4=B8=BA15=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- km-rest/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-rest/src/main/resources/application.yml b/km-rest/src/main/resources/application.yml index efbafef5..b8849dbf 100644 --- a/km-rest/src/main/resources/application.yml +++ b/km-rest/src/main/resources/application.yml @@ -93,7 +93,7 @@ es: io-thread-cnt: 2 max-retry-cnt: 5 index: - expire: 60 # 索引过期天数,60表示超过60天的索引会被KS过期删除 + expire: 15 # 索引过期天数,15表示超过15天的索引会被KS过期删除 # 普罗米修斯指标导出相关配置 management: From 7db757bc1254272c42dcab6756743d6e756f6037 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 14 Dec 2022 14:12:32 +0800 Subject: [PATCH 071/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96Connector?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=97=B6=E7=9A=84=E5=85=A5=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、增加config.action.reload的默认值; 2、增加errors.tolerance的默认值; --- .../entity/connect/config/ConnectConfigInfos.java | 15 ++++++++++++--- .../streaming/km/common/constant/Constant.java | 5 +++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java index bb8b773f..f58adbe0 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/config/ConnectConfigInfos.java @@ -7,8 +7,10 @@ import lombok.NoArgsConstructor; import org.apache.kafka.connect.runtime.rest.entities.ConfigInfo; import org.apache.kafka.connect.runtime.rest.entities.ConfigInfos; -import java.util.ArrayList; -import java.util.List; +import java.util.*; + +import static com.xiaojukeji.know.streaming.km.common.constant.Constant.CONNECTOR_CONFIG_ACTION_RELOAD_NAME; +import static com.xiaojukeji.know.streaming.km.common.constant.Constant.CONNECTOR_CONFIG_ERRORS_TOLERANCE_NAME; /** * @see ConfigInfos @@ -17,6 +19,13 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor public class ConnectConfigInfos { + + private static final Map> recommendValuesMap = new HashMap<>(); + + static { + recommendValuesMap.put(CONNECTOR_CONFIG_ACTION_RELOAD_NAME, Arrays.asList("none", "restart")); + recommendValuesMap.put(CONNECTOR_CONFIG_ERRORS_TOLERANCE_NAME, Arrays.asList("none", "all")); + } private String name; private int errorCount; @@ -48,7 +57,7 @@ public class ConnectConfigInfos { ConnectConfigValueInfo value = new ConnectConfigValueInfo(); value.setName(configInfo.configValue().name()); value.setValue(configInfo.configValue().value()); - value.setRecommendedValues(configInfo.configValue().recommendedValues()); + value.setRecommendedValues(recommendValuesMap.getOrDefault(configInfo.configValue().name(), configInfo.configValue().recommendedValues())); value.setErrors(configInfo.configValue().errors()); value.setVisible(configInfo.configValue().visible()); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java index df7ecce3..c8f4075b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java @@ -65,4 +65,9 @@ public class Constant { public static final Integer DEFAULT_RETRY_TIME = 3; public static final Integer ZK_ALIVE_BUT_4_LETTER_FORBIDDEN = 11; + + public static final String CONNECTOR_CONFIG_ACTION_RELOAD_NAME = "config.action.reload"; + + public static final String CONNECTOR_CONFIG_ERRORS_TOLERANCE_NAME = "errors.tolerance"; + } From 218459ad1b96e91c429b43759ee18c11f2fee13f Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 14 Dec 2022 14:14:20 +0800 Subject: [PATCH 072/150] =?UTF-8?q?=E5=A2=9E=E5=8A=A03.2.0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=98=E6=9B=B4=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Releases_Notes.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Releases_Notes.md b/Releases_Notes.md index ad89a3e9..656790f2 100644 --- a/Releases_Notes.md +++ b/Releases_Notes.md @@ -1,4 +1,62 @@ +## v3.2.0 + +**问题修复** +- 修复健康巡检结果更新至 DB 时,出现死锁问题; +- 修复 KafkaJMXClient 类中,logger错误的问题; +- 后端修复 Topic 过期策略在 0.10.1.0 版本能多选的问题,实际应该只能二选一; +- 修复接入集群时,不填写集群配置会报错的问题; +- 升级 spring-context 至 5.3.19 版本,修复安全漏洞; +- 修复 Broker & Topic 修改配置时,多版本兼容配置的版本信息错误的问题; +- 修复 Topic 列表的健康分为健康状态; +- 修复 Broker LogSize 指标存储名称错误导致查询不到的问题; +- 修复 Prometheus 中,缺少 Group 部分指标的问题; +- 修复因缺少健康状态指标导致集群数错误的问题; +- 修复后台任务记录操作日志时,因缺少操作用户信息导致出现异常的问题; +- 修复 Replica 指标查询时,DSL 错误的问题; +- 关闭 errorLogger,修复错误日志重复输出的问题; +- 修复系统管理更新用户信息失败的问题; +- 修复因原AR信息丢失,导致迁移任务一直处于执行中的错误; +- 修复集群 Topic 列表实时数据查询时,出现失败的问题; +- 修复集群 Topic 列表,页面白屏问题; +- 修复副本变更时,因AR数据异常,导致数组访问越界的问题; + + +**产品优化** +- 优化健康巡检为按照资源维度多线程并发处理; +- 统一日志输出格式,并优化部分输出的日志; +- 优化 ZK 四字命令结果解析过程中,容易引起误解的 WARN 日志; +- 优化 Zookeeper 详情中,目录结构的搜索文案; +- 优化线程池的名称,方便第三方系统进行相关问题的分析; +- 去除 ESClient 的并发访问控制,降低 ESClient 创建数及提升利用率; +- 优化 Topic Messages 抽屉文案; +- 优化 ZK 健康巡检失败时的错误日志信息; +- 提高 Offset 信息获取的超时时间,降低并发过高时出现请求超时的概率; +- 优化 Topic & Partition 元信息的更新策略,降低对 DB 连接的占用; +- 优化 Sonar 代码扫码问题; +- 优化分区 Offset 指标的采集; +- 优化前端图表相关组件逻辑; +- 优化产品主题色; +- Consumer 列表刷新按钮新增 hover 提示; +- 优化配置 Topic 的消息大小时的测试弹框体验; +- 优化 Overview 页面 TopN 查询的流程; + + +**功能新增** +- 新增页面无数据排查文档; +- 增加 ES 索引删除的功能; +- 支持拆分API服务和Job服务部署; + + +**Kafka Connect Beta版 (v3.2.0版本新增发布)** +- Connect 集群的纳管; +- Connector 的增删改查; +- Connect 集群 & Connector 的指标大盘; + + +--- + + ## v3.1.0 **Bug修复** From 44a2fe039864b0e94e19b6ded5f2f314bf002e88 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 14 Dec 2022 14:14:35 +0800 Subject: [PATCH 073/150] =?UTF-8?q?=E5=A2=9E=E5=8A=A03.2.0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8D=87=E7=BA=A7=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install_guide/版本升级手册.md | 50 +++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/install_guide/版本升级手册.md b/docs/install_guide/版本升级手册.md index 943e50d7..2a9951b1 100644 --- a/docs/install_guide/版本升级手册.md +++ b/docs/install_guide/版本升级手册.md @@ -4,7 +4,39 @@ - 如果想升级至具体版本,需要将你当前版本至你期望使用版本的变更统统执行一遍,然后才能正常使用。 - 如果中间某个版本没有升级信息,则表示该版本直接替换安装包即可从前一个版本升级至当前版本。 -### 6.2.0、升级至 `master` 版本 +### 升级至 `master` 版本 + +暂无 + +### 升级至 `3.2.0` 版本 + +**配置变更** + +```yaml +# 新增如下配置 + +spring: + logi-job: # know-streaming 依赖的 logi-job 模块的数据库的配置,默认与 know-streaming 的数据库配置保持一致即可 + enable: true # true表示开启job任务, false表关闭。KS在部署上可以考虑部署两套服务,一套处理前端请求,一套执行job任务,此时可以通过该字段进行控制 + +# 线程池大小相关配置 +thread-pool: + es: + search: # es查询线程池 + thread-num: 20 # 线程池大小 + queue-size: 10000 # 队列大小 + +# 客户端池大小相关配置 +client-pool: + kafka-admin: + client-cnt: 1 # 每个Kafka集群创建的KafkaAdminClient数 + +# ES客户端配置 +es: + index: + expire: 15 # 索引过期天数,15表示超过15天的索引会被KS过期删除 +``` + **SQL 变更** ```sql DROP TABLE IF EXISTS `ks_kc_connect_cluster`; @@ -56,9 +88,9 @@ CREATE TABLE `ks_kc_worker` ( `url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL信息', `leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'leaderURL信息', `leader` int(16) NOT NULL DEFAULT '0' COMMENT '状态: 1是leader,0不是leader', + `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', PRIMARY KEY (`id`), UNIQUE KEY `uniq_cluster_id_member_id` (`connect_cluster_id`,`member_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='worker信息表'; @@ -85,7 +117,9 @@ INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE', '{\"value\" : 0.05}', 'Connect集群任务启动失败概率', 'admin'); ``` -### 6.2.1、升级至 `v3.1.0` 版本 +--- + +### 升级至 `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'); @@ -97,7 +131,7 @@ INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value ``` -### 6.2.2、升级至 `v3.0.1` 版本 +### 升级至 `v3.0.1` 版本 **ES 索引模版** ```bash @@ -232,7 +266,7 @@ CREATE TABLE `ks_km_group` ( ``` -### 6.2.3、升级至 `v3.0.0` 版本 +### 升级至 `v3.0.0` 版本 **SQL 变更** @@ -244,7 +278,7 @@ ADD COLUMN `zk_properties` TEXT NULL COMMENT 'ZK配置' AFTER `jmx_properties`; --- -### 6.2.4、升级至 `v3.0.0-beta.2`版本 +### 升级至 `v3.0.0-beta.2`版本 **配置变更** @@ -315,7 +349,7 @@ ALTER TABLE `logi_security_oplog` --- -### 6.2.5、升级至 `v3.0.0-beta.1`版本 +### 升级至 `v3.0.0-beta.1`版本 **SQL 变更** @@ -334,7 +368,7 @@ ALTER COLUMN `operation_methods` set default ''; --- -### 6.2.6、`2.x`版本 升级至 `v3.0.0-beta.0`版本 +### `2.x`版本 升级至 `v3.0.0-beta.0`版本 **升级步骤:** From 5bceed71059290e5c39362ab2490e8f769c10fe5 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 15 Dec 2022 14:44:18 +0800 Subject: [PATCH 074/150] =?UTF-8?q?[Optimize]=E7=BC=A9=E5=B0=8FES=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E9=BB=98=E8=AE=A4shard=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- km-dist/init/sql/ddl-ks-km.sql | 2 +- km-dist/init/template/template.sh | 99 ++++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/km-dist/init/sql/ddl-ks-km.sql b/km-dist/init/sql/ddl-ks-km.sql index 8da89aab..30b51c20 100644 --- a/km-dist/init/sql/ddl-ks-km.sql +++ b/km-dist/init/sql/ddl-ks-km.sql @@ -440,9 +440,9 @@ CREATE TABLE `ks_kc_worker` ( `url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL信息', `leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'leaderURL信息', `leader` int(16) NOT NULL DEFAULT '0' COMMENT '状态: 1是leader,0不是leader', + `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', PRIMARY KEY (`id`), UNIQUE KEY `uniq_cluster_id_member_id` (`connect_cluster_id`,`member_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='worker信息表'; diff --git a/km-dist/init/template/template.sh b/km-dist/init/template/template.sh index 86fcfb66..baae166b 100644 --- a/km-dist/init/template/template.sh +++ b/km-dist/init/template/template.sh @@ -13,7 +13,7 @@ curl -s --connect-timeout 10 -o /dev/null -X POST -H 'cache-control: no-cache' - ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -115,7 +115,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -302,7 +302,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -377,7 +377,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -443,7 +443,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -509,7 +509,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -626,7 +626,92 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "AvgRequestLatency" : { + "type" : "double" + }, + "MinRequestLatency" : { + "type" : "double" + }, + "MaxRequestLatency" : { + "type" : "double" + }, + "OutstandingRequests" : { + "type" : "double" + }, + "NodeCount" : { + "type" : "double" + }, + "WatchCount" : { + "type" : "double" + }, + "NumAliveConnections" : { + "type" : "double" + }, + "PacketsReceived" : { + "type" : "double" + }, + "PacketsSent" : { + "type" : "double" + }, + "EphemeralsCount" : { + "type" : "double" + }, + "ApproximateDataSize" : { + "type" : "double" + }, + "OpenFileDescriptorCount" : { + "type" : "double" + }, + "MaxFileDescriptorCount" : { + "type" : "double" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "type" : "date" + } + } + }, + "aliases" : { } + }' + +curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${SERVER_ES_ADDRESS}/_template/ks_kafka_zookeeper_metric -d '{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_zookeeper_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" } }, "mappings" : { From 860d0b92e286894978423c73e4fb3eb753c303af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E8=B6=85?= Date: Thu, 15 Dec 2022 18:48:41 +0800 Subject: [PATCH 075/150] V3.2 --- .../config-manager-fe/config/theme.js | 4 +- .../src/pages/UserManage/RoleTabContent.tsx | 6 +- .../src/pages/UserManage/UserTabContent.tsx | 2 +- .../layout-clusters-fe/config/theme.js | 4 +- .../layout-clusters-fe/config/webpack.dev.js | 4 +- .../layout-clusters-fe/package-lock.json | 21119 +++++++++++++++- .../packages/layout-clusters-fe/package.json | 2 +- .../layout-clusters-fe/src/api/index.ts | 42 +- .../layout-clusters-fe/src/assets/no-data.png | Bin 0 -> 56362 bytes .../components/CardBar/BrokerHealthCheck.tsx | 2 +- .../src/components/CardBar/ConnectCard.tsx | 119 + .../components/CardBar/ConnectDetailCard.tsx | 122 + .../components/CardBar/TopicHealthCheck.tsx | 2 +- .../src/components/CardBar/index.less | 3 +- .../src/components/CardBar/index.tsx | 51 +- .../ChartOperateBar/MetricSelect.tsx | 156 +- .../components/ChartOperateBar/NodeScope.tsx | 203 - .../components/ChartOperateBar/NodeSelect.tsx | 115 + .../src/components/ChartOperateBar/index.tsx | 58 +- .../style/indicator-drawer.less | 142 +- .../components/DraggableCharts/ChartList.tsx | 141 + .../src/components/DraggableCharts/Detail.tsx | 20 +- .../src/components/DraggableCharts/index.tsx | 333 +- .../src/components/SwitchTab/index.tsx | 16 +- .../src/constants/chartConfig.ts | 2 + .../layout-clusters-fe/src/constants/menu.tsx | 36 +- .../layout-clusters-fe/src/constants/reg.ts | 6 + .../layout-clusters-fe/src/locales/zh.tsx | 5 + .../pages/BrokerControllerChangeLog/index.tsx | 2 +- .../src/pages/BrokerList/index.tsx | 2 +- .../src/pages/Connect/AddConnector.tsx | 1171 + .../src/pages/Connect/AddConnectorUseJSON.tsx | 262 + .../src/pages/Connect/Delete.tsx | 99 + .../src/pages/Connect/Detail.tsx | 111 + .../src/pages/Connect/HasConnector.tsx | 51 + .../src/pages/Connect/Workers.tsx | 120 + .../src/pages/Connect/config.tsx | 364 + .../src/pages/Connect/index.less | 193 + .../src/pages/Connect/index.tsx | 228 + .../pages/ConnectDashboard/MetricSelect.tsx | 298 + .../pages/ConnectDashboard/MetricsFilter.tsx | 235 + .../pages/ConnectDashboard/SelectContent.tsx | 233 + .../src/pages/ConnectDashboard/index.less | 13 + .../src/pages/ConnectDashboard/index.tsx | 259 + .../src/pages/ConsumerGroup/index.tsx | 2 +- .../src/pages/Consumers/index.tsx | 4 +- .../src/pages/Jobs/index.less | 28 +- .../src/pages/Jobs/index.tsx | 2 +- .../src/pages/LoadRebalance/index.tsx | 2 +- .../src/pages/Login/index.tsx | 2 +- .../pages/MutliClusterPage/AccessCluster.tsx | 836 +- .../src/pages/MutliClusterPage/index.less | 42 + .../src/pages/SecurityACLs/index.tsx | 2 +- .../SingleClusterDetail/DetailChart/index.tsx | 97 +- .../src/pages/SingleClusterDetail/config.tsx | 52 +- .../src/pages/TopicList/index.tsx | 8 +- .../src/pages/Zookeeper/index.tsx | 2 +- .../src/pages/pageRoutes.ts | 22 + .../src/style-addition.less | 12 +- 59 files changed, 25972 insertions(+), 1497 deletions(-) create mode 100644 km-console/packages/layout-clusters-fe/src/assets/no-data.png create mode 100644 km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectCard.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectDetailCard.tsx delete mode 100644 km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeScope.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeSelect.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/components/DraggableCharts/ChartList.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnector.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnectorUseJSON.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/Delete.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/Detail.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/Workers.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/index.less create mode 100644 km-console/packages/layout-clusters-fe/src/pages/Connect/index.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricSelect.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricsFilter.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/SelectContent.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.less create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx diff --git a/km-console/packages/config-manager-fe/config/theme.js b/km-console/packages/config-manager-fe/config/theme.js index cd58375e..3d57d2ab 100755 --- a/km-console/packages/config-manager-fe/config/theme.js +++ b/km-console/packages/config-manager-fe/config/theme.js @@ -1,7 +1,7 @@ const themeConfig = { - primaryColor: '#556ee6', + primaryColor: '#5664FF', theme: { - 'primary-color': '#556ee6', + 'primary-color': '#5664FF', 'border-radius-base': '2px', 'border-radius-sm': '2px', 'font-size-base': '12px', diff --git a/km-console/packages/config-manager-fe/src/pages/UserManage/RoleTabContent.tsx b/km-console/packages/config-manager-fe/src/pages/UserManage/RoleTabContent.tsx index f7db974b..eb0b82e9 100644 --- a/km-console/packages/config-manager-fe/src/pages/UserManage/RoleTabContent.tsx +++ b/km-console/packages/config-manager-fe/src/pages/UserManage/RoleTabContent.tsx @@ -423,7 +423,7 @@ export default (props: { curTabKey: string }): JSX.Element => { dataIndex: 'authedUserCnt', width: 100, render(cnt: Pick, record: RoleProps) { - return ( + return cnt ? ( { {cnt} + ) : ( + ); }, }, diff --git a/km-console/packages/config-manager-fe/src/pages/UserManage/UserTabContent.tsx b/km-console/packages/config-manager-fe/src/pages/UserManage/UserTabContent.tsx index 0ca3ca29..8a569b3b 100644 --- a/km-console/packages/config-manager-fe/src/pages/UserManage/UserTabContent.tsx +++ b/km-console/packages/config-manager-fe/src/pages/UserManage/UserTabContent.tsx @@ -53,7 +53,7 @@ const EditUserDrawer = forwardRef((props, ref) => { }) : request(api.editUser, { method: 'POST', - data: { ...formData }, + data: { ...formData, phone: Date.now() }, }); requestPromise.then( (res) => { diff --git a/km-console/packages/layout-clusters-fe/config/theme.js b/km-console/packages/layout-clusters-fe/config/theme.js index cd58375e..3d57d2ab 100755 --- a/km-console/packages/layout-clusters-fe/config/theme.js +++ b/km-console/packages/layout-clusters-fe/config/theme.js @@ -1,7 +1,7 @@ const themeConfig = { - primaryColor: '#556ee6', + primaryColor: '#5664FF', theme: { - 'primary-color': '#556ee6', + 'primary-color': '#5664FF', 'border-radius-base': '2px', 'border-radius-sm': '2px', 'font-size-base': '12px', diff --git a/km-console/packages/layout-clusters-fe/config/webpack.dev.js b/km-console/packages/layout-clusters-fe/config/webpack.dev.js index 6c7776ca..60f2bbf2 100644 --- a/km-console/packages/layout-clusters-fe/config/webpack.dev.js +++ b/km-console/packages/layout-clusters-fe/config/webpack.dev.js @@ -34,11 +34,11 @@ module.exports = { proxy: { '/ks-km/api/v3': { changeOrigin: true, - target: 'http://localhost:8080/', + target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/', }, '/logi-security/api/v1': { changeOrigin: true, - target: 'http://localhost:8080/', + target: 'https://api-kylin-xg02.intra.xiaojukeji.com/ks-km/', }, }, }, diff --git a/km-console/packages/layout-clusters-fe/package-lock.json b/km-console/packages/layout-clusters-fe/package-lock.json index 7a98639a..7e967f96 100644 --- a/km-console/packages/layout-clusters-fe/package-lock.json +++ b/km-console/packages/layout-clusters-fe/package-lock.json @@ -1,8 +1,20473 @@ { "name": "layout-clusters-fe", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "layout-clusters-fe", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@ant-design/compatible": "^1.0.8", + "@ant-design/icons": "^4.6.2", + "@knowdesign/icons": "^1.0.2", + "@types/react": "^17.0.39", + "@types/react-copy-to-clipboard": "^5.0.2", + "@types/react-dom": "^17.0.11", + "@types/react-highlight-words": "^0.16.0", + "@types/react-router": "5.1.18", + "@types/react-router-dom": "^5.3.3", + "@types/react-transition-group": "^4.2.2", + "@types/react-virtualized": "^9.21.13", + "axios": "^0.21.1", + "babel-preset-react-app": "^10.0.0", + "classnames": "^2.2.6", + "crypto-js": "^4.1.1", + "dotenv": "^16.0.1", + "html-webpack-plugin": "^4.0.0", + "knowdesign": "1.3.7", + "lodash": "^4.17.21", + "moment": "^2.24.0", + "react": "16.12.0", + "react-copy-to-clipboard": "^5.0.4", + "react-cron-antd": "^1.1.2", + "react-dom": "16.12.0", + "react-intl": "^3.2.1", + "react-joyride": "^2.5.3", + "single-spa": "5.9.3", + "single-spa-react": "2.14.0", + "webpack-bundle-analyzer": "^4.5.0" + }, + "devDependencies": { + "@babel/core": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.4.0", + "@babel/plugin-proposal-decorators": "^7.4.0", + "@babel/plugin-proposal-export-default-from": "^7.2.0", + "@babel/plugin-proposal-export-namespace-from": "^7.5.2", + "@babel/plugin-proposal-object-rest-spread": "^7.4.3", + "@babel/plugin-proposal-private-methods": "^7.14.5", + "@babel/plugin-transform-object-assign": "^7.12.1", + "@babel/plugin-transform-runtime": "^7.4.3", + "@babel/polyfill": "^7.12.1", + "@babel/preset-env": "^7.4.2", + "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.14.5", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", + "@types/crypto-js": "^4.1.0", + "@types/lodash": "^4.14.171", + "@types/node": "^12.12.25", + "@types/pubsub-js": "^1.5.18", + "@typescript-eslint/eslint-plugin": "4.13.0", + "@typescript-eslint/parser": "4.13.0", + "babel-eslint": "10.1.0", + "babel-loader": "^8.2.2", + "babel-plugin-import": "^1.12.0", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "clean-webpack-plugin": "^3.0.0", + "copy-webpack-plugin": "^5.1.2", + "cross-env": "^7.0.2", + "css-loader": "^2.1.0", + "eslint": "^7.30.0", + "eslint-config-prettier": "8.3.0", + "eslint-plugin-prettier": "3.4.0", + "eslint-plugin-react": "7.22.0", + "eslint-plugin-react-hooks": "4.2.0", + "file-loader": "^6.0.0", + "hard-source-webpack-plugin": "^0.13.1", + "husky": "4.3.7", + "less": "^3.9.0", + "less-loader": "^4.1.0", + "lint-staged": "10.5.3", + "mini-css-extract-plugin": "^1.3.0", + "optimize-css-assets-webpack-plugin": "^5.0.1", + "prettier": "2.3.2", + "progress-bar-webpack-plugin": "^1.12.1", + "query-string": "^7.0.1", + "react-refresh": "^0.10.0", + "react-router-dom": "5.2.1", + "ts-loader": "^8.0.11", + "typescript": "4.6.4", + "webpack": "^4.40.0", + "webpack-cli": "^3.2.3", + "webpack-dev-server": "^3.2.1", + "webpack-merge": "^4.2.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/compatible": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/compatible/-/compatible-1.1.2.tgz", + "integrity": "sha512-Qsx5Qw97eiSgcxyQDlY45QSbvGn0gUdpX8XFImPvzZpKwabqQ2HnXXuUlb8RbrkURswaPIoyLEGKDPeogIaURA==", + "dependencies": { + "@ant-design/icons": "^4.0.0", + "classnames": "^2.2.6", + "lodash.camelcase": "^4.3.0", + "lodash.upperfirst": "^4.3.1", + "omit.js": "^1.0.2", + "rc-animate": "^2.10.2", + "rc-editor-mention": "^1.1.13", + "rc-form": "^2.4.10", + "rc-util": "^4.10.0" + } + }, + "node_modules/@ant-design/icons": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-4.7.0.tgz", + "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-util": "^5.9.4" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz", + "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==", + "license": "MIT" + }, + "node_modules/@ant-design/icons/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/react-slick": { + "version": "0.28.4", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-0.28.4.tgz", + "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "lodash": "^4.17.21", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.19.1.tgz", + "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.19.1.tgz", + "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-compilation-targets": "^7.19.1", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.19.0.tgz", + "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.19.0", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", + "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.19.1", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", + "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", + "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "license": "MIT", + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.19.0.tgz", + "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.19.1.tgz", + "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", + "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.19.1.tgz", + "integrity": "sha512-LfIKNBBY7Q1OX5C4xAgRQffOg2OnhAo9fnbcOHgOC9Yytm2Sw+4XqHufRYU86tHomzepxtvuVaNO+3EVKR4ivw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.18.10", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", + "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-default-from": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", + "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", + "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", + "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", + "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.19.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.18.13", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz", + "integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "license": "MIT", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", + "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-flow": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz", + "integrity": "sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==", + "license": "MIT", + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-assign": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz", + "integrity": "sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz", + "integrity": "sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz", + "integrity": "sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-typescript": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/polyfill": { + "version": "7.12.1", + "resolved": "https://registry.npmmirror.com/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.19.1.tgz", + "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.19.1", + "@babel/helper-compilation-targets": "^7.19.1", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.19.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.19.0", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.13", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.0", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.19.0", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.19.0.tgz", + "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.19.1", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.19.1.tgz", + "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.19.1", + "@babel/types": "^7.19.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.19.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz", + "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@formatjs/intl-displaynames": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@formatjs/intl-displaynames/-/intl-displaynames-1.2.10.tgz", + "integrity": "sha512-GROA2RP6+7Ouu0WnHFF78O5XIU7pBfI19WM1qm93l6MFWibUk67nCfVCK3VAYJkLy8L8ZxjkYT11VIAfvSz8wg==", + "license": "MIT", + "dependencies": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "node_modules/@formatjs/intl-listformat": { + "version": "1.4.8", + "resolved": "https://registry.npmmirror.com/@formatjs/intl-listformat/-/intl-listformat-1.4.8.tgz", + "integrity": "sha512-WNMQlEg0e50VZrGIkgD5n7+DAMGt3boKi1GJALfhFMymslJb5i+5WzWxyj/3a929Z6MAFsmzRIJjKuv+BxKAOQ==", + "license": "MIT", + "dependencies": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "node_modules/@formatjs/intl-relativetimeformat": { + "version": "4.5.16", + "resolved": "https://registry.npmmirror.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.5.16.tgz", + "integrity": "sha512-IQ0haY97oHAH5OYUdykNiepdyEWj3SAT+Fp9ZpR85ov2JNiFx+12WWlxlVS8ehdyncC2ZMt/SwFIy2huK2+6/A==", + "license": "MIT", + "dependencies": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "node_modules/@formatjs/intl-unified-numberformat": { + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.7.tgz", + "integrity": "sha512-KnWgLRHzCAgT9eyt3OS34RHoyD7dPDYhRcuKn+/6Kv2knDF8Im43J6vlSW6Hm1w63fNq3ZIT1cFk7RuVO3Psag==", + "license": "MIT", + "dependencies": { + "@formatjs/intl-utils": "^2.3.0" + } + }, + "node_modules/@formatjs/intl-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz", + "integrity": "sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==", + "license": "MIT" + }, + "node_modules/@gilbarbara/deep-equal": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@gilbarbara/deep-equal/-/deep-equal-0.1.1.tgz", + "integrity": "sha512-SjSBspHXlclODLtSoPIQwBhfeBjncC05NlNoFELJ6xZQkyYDJsVCcs7+f+etHR2cYPbHLjnh1C06lQlCbMEWEA==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@knowdesign/icons": { + "version": "1.0.2", + "resolved": "http://registry.npm.xiaojukeji.com/@knowdesign/icons/download/@knowdesign/icons-1.0.2.tgz", + "integrity": "sha1-OqNaeMDVsGShJYux2KMW86erHjk=", + "license": "ISC", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons": "^4.7.0", + "react": "16.12.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz", + "integrity": "sha512-bcKCAzF0DV2IIROp9ZHkRJa6O4jy7NlnHdWL3GmcUxYWNjLXkK5kfELELwEfSP5hXPfVL/qOGMAROuMQb9GG8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "core-js-pure": "^3.8.1", + "error-stack-parser": "^2.0.6", + "find-up": "^5.0.0", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <3.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-dnd/asap": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@react-dnd/asap/-/asap-4.0.1.tgz", + "integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==", + "license": "MIT" + }, + "node_modules/@react-dnd/invariant": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@react-dnd/invariant/-/invariant-2.0.0.tgz", + "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==", + "license": "MIT" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", + "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==", + "license": "MIT" + }, + "node_modules/@types/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmmirror.com/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "license": "MIT" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", + "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", + "license": "MIT" + }, + "node_modules/@types/invariant": { + "version": "2.2.35", + "resolved": "https://registry.npmmirror.com/@types/invariant/-/invariant-2.2.35.tgz", + "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.14.185", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.185.tgz", + "integrity": "sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "license": "MIT" + }, + "node_modules/@types/pubsub-js": { + "version": "1.8.3", + "resolved": "https://registry.npmmirror.com/@types/pubsub-js/-/pubsub-js-1.8.3.tgz", + "integrity": "sha512-6BqY04dh2UV1dNV690tyJVJYQ0U6qBH4tU+FCwY1Mhl8jOPOP9qiIvgLnB59cVik/E6/R002oXZpGiDm+2C8eA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "17.0.50", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-17.0.50.tgz", + "integrity": "sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-copy-to-clipboard": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz", + "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.17", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-17.0.17.tgz", + "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "license": "MIT", + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/react-highlight-words": { + "version": "0.16.4", + "resolved": "https://registry.npmmirror.com/@types/react-highlight-words/-/react-highlight-words-0.16.4.tgz", + "integrity": "sha512-KITBX3xzheQLu2s3bUgLmRE7ekmhc52zRjRTwkKayQARh30L4fjEGzGm7ULK9TuX2LgxWWavZqyQGDGjAHbL3w==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.18", + "resolved": "https://registry.npmmirror.com/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-virtualized": { + "version": "9.21.21", + "resolved": "https://registry.npmmirror.com/@types/react-virtualized/-/react-virtualized-9.21.21.tgz", + "integrity": "sha512-Exx6I7p4Qn+BBA1SRyj/UwQlZ0I0Pq7g7uhAp0QQ4JWzZunqEqNBGTmCmMmS/3N9wFgAGWuBD16ap7k8Y14VPA==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/react": "^17" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "license": "MIT" + }, + "node_modules/@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "license": "MIT" + }, + "node_modules/@types/tapable": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/tapable/-/tapable-1.0.8.tgz", + "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", + "license": "MIT" + }, + "node_modules/@types/uglify-js": { + "version": "3.17.0", + "resolved": "https://registry.npmmirror.com/@types/uglify-js/-/uglify-js-3.17.0.tgz", + "integrity": "sha512-3HO6rm0y+/cqvOyA8xcYLweF0TKXlAxmQASjbOi49Co51A1N4nR4bEwBgRoD9kNM+rqFGArjKr654SLp2CoGmQ==", + "license": "MIT", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "license": "MIT" + }, + "node_modules/@types/webpack": { + "version": "4.41.32", + "resolved": "https://registry.npmmirror.com/@types/webpack/-/webpack-4.41.32.tgz", + "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + } + }, + "node_modules/@types/webpack-sources/node_modules/@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", + "license": "MIT" + }, + "node_modules/@types/webpack-sources/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/webpack/node_modules/@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz", + "integrity": "sha512-ygqDUm+BUPvrr0jrXqoteMqmIaZ/bixYOc3A4BRwzEPTZPi6E+n44rzNZWaB0YvtukgP+aoj0i/fyx7FkM2p1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/experimental-utils": "4.13.0", + "@typescript-eslint/scope-manager": "4.13.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.13.0.tgz", + "integrity": "sha512-/ZsuWmqagOzNkx30VWYV3MNB/Re/CGv/7EzlqZo5RegBN8tMuPaBgNK6vPBCQA8tcYrbsrTdbx3ixMRRKEEGVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.13.0", + "@typescript-eslint/types": "4.13.0", + "@typescript-eslint/typescript-estree": "4.13.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-4.13.0.tgz", + "integrity": "sha512-KO0J5SRF08pMXzq9+abyHnaGQgUJZ3Z3ax+pmqz9vl81JxmTTOUfQmq7/4awVfq09b6C4owNlOgOwp61pYRBSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "4.13.0", + "@typescript-eslint/types": "4.13.0", + "@typescript-eslint/typescript-estree": "4.13.0", + "debug": "^4.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-4.13.0.tgz", + "integrity": "sha512-UpK7YLG2JlTp/9G4CHe7GxOwd93RBf3aHO5L+pfjIrhtBvZjHKbMhBXTIQNkbz7HZ9XOe++yKrXutYm5KmjWgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "4.13.0", + "@typescript-eslint/visitor-keys": "4.13.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-4.13.0.tgz", + "integrity": "sha512-/+aPaq163oX+ObOG00M0t9tKkOgdv9lq0IQv/y4SqGkAXmhFmCfgsELV7kOCTb2vVU5VOmVwXBXJTDr353C1rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.13.0.tgz", + "integrity": "sha512-9A0/DFZZLlGXn5XA349dWQFwPZxcyYyCFX5X88nWs2uachRDwGeyPz46oTsm9ZJE66EALvEns1lvBwa4d9QxMg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "4.13.0", + "@typescript-eslint/visitor-keys": "4.13.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.13.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.13.0.tgz", + "integrity": "sha512-6RoxWK05PAibukE7jElqAtNMq+RWZyqJ6Q/GdIxaiUj2Ept8jh8+FUVlbq9WxMYxkmEOPvCE5cRSyupMpwW31g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "4.13.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "node_modules/@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "license": "ISC" + }, + "node_modules/@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "license": "MIT", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "license": "MIT", + "dependencies": { + "object-assign": "4.x" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "license": "MIT", + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/antd": { + "version": "4.9.2", + "resolved": "https://registry.npmmirror.com/antd/-/antd-4.9.2.tgz", + "integrity": "sha512-unZxnwihmIzHDGchZxuJDJe2tfeKWpsZHrrIdQR2hIie7I1lYSn4GyFZvn8MAkQvpum7Mj41eTJKib5TVfYxQw==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^5.0.0", + "@ant-design/icons": "^4.3.0", + "@ant-design/react-slick": "~0.27.0", + "@babel/runtime": "^7.11.2", + "array-tree-filter": "^2.1.0", + "classnames": "^2.2.6", + "copy-to-clipboard": "^3.2.0", + "lodash": "^4.17.20", + "moment": "^2.25.3", + "omit.js": "^2.0.2", + "rc-cascader": "~1.4.0", + "rc-checkbox": "~2.3.0", + "rc-collapse": "~3.1.0", + "rc-dialog": "~8.4.0", + "rc-drawer": "~4.1.0", + "rc-dropdown": "~3.2.0", + "rc-field-form": "~1.17.0", + "rc-image": "~4.2.0", + "rc-input-number": "~6.1.0", + "rc-mentions": "~1.5.0", + "rc-menu": "~8.10.0", + "rc-motion": "^2.4.0", + "rc-notification": "~4.5.2", + "rc-pagination": "~3.1.2", + "rc-picker": "~2.4.1", + "rc-progress": "~3.1.0", + "rc-rate": "~2.9.0", + "rc-resize-observer": "^0.2.3", + "rc-select": "~11.5.3", + "rc-slider": "~9.6.1", + "rc-steps": "~4.1.0", + "rc-switch": "~3.2.0", + "rc-table": "~7.11.0", + "rc-tabs": "~11.7.0", + "rc-textarea": "~0.3.0", + "rc-tooltip": "~5.0.0", + "rc-tree": "~4.0.0", + "rc-tree-select": "~4.2.0", + "rc-upload": "~3.3.1", + "rc-util": "^5.1.0", + "scroll-into-view-if-needed": "^2.2.25", + "warning": "^4.0.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd-img-crop": { + "version": "3.16.0", + "resolved": "https://registry.npmmirror.com/antd-img-crop/-/antd-img-crop-3.16.0.tgz", + "integrity": "sha512-BVYH5VQewhK60yMEzBTi0/zBEJrX10aWet4ItGDvPJvzOYHPejhkchF9CZgYFzPJ/yPe6h1vYyevf5gjfhvFqA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.15.3", + "react-easy-crop": "^3.5.2" + }, + "peerDependencies": { + "antd": ">=4.0.0", + "prop-types": ">=15.6.2", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/antd/node_modules/@ant-design/colors": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-5.1.1.tgz", + "integrity": "sha512-Txy4KpHrp3q4XZdfgOBqLl+lkQIc3tEvHXOimRN1giX1AEC7mGtyrO9p8iRGJ3FLuVMGa2gNEzQyghVymLttKQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.3.1" + } + }, + "node_modules/antd/node_modules/@ant-design/react-slick": { + "version": "0.27.14", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-0.27.14.tgz", + "integrity": "sha512-s6JVexqFmU5rs5Pm828ojtm5rCp8jDXyrc5OxEtCE2z58SIyQlkpnU9BJh98LEeBZyj02WFkGN8CWpSaD+G4PA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "lodash": "^4.17.15", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/antd/node_modules/async-validator": { + "version": "3.5.2", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-3.5.2.tgz", + "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==", + "license": "MIT" + }, + "node_modules/antd/node_modules/omit.js": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/omit.js/-/omit.js-2.0.2.tgz", + "integrity": "sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg==", + "license": "MIT" + }, + "node_modules/antd/node_modules/rc-cascader": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-1.4.3.tgz", + "integrity": "sha512-Q4l9Mv8aaISJ+giVnM9IaXxDeMqHUGLvi4F+LksS6pHlaKlN4awop/L+IMjIXpL+ug/ojaCyv/ixcVopJYYCVA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "array-tree-filter": "^2.1.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.0.1", + "warning": "^4.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-dialog": { + "version": "8.4.6", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-8.4.6.tgz", + "integrity": "sha512-tTWp54h4zoCNoZFtTz+z1pOQYZCMsYazVEmHXJxWGOr3TgZmL/j9Wl81oKXW5PfqQ5g5JJjbUdKf72n+V+uvwA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-drawer": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-4.1.0.tgz", + "integrity": "sha512-kjeQFngPjdzAFahNIV0EvEBoIKMOnvUsAxpkSPELoD/1DuR4nLafom5ryma+TIxGwkFJ92W6yjsMi1U9aiOTeQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/antd/node_modules/rc-field-form": { + "version": "1.17.4", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.17.4.tgz", + "integrity": "sha512-QI9fe0F9YAmEX946lQpxTs6Qc/FwaLeakWquiBNEmhtqurj/qDdrv+eLb4TfnHTjkdyxU3G7p901WEuuBrrdkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4", + "async-validator": "^3.0.3", + "rc-util": "^5.0.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">= 16.9.0", + "react-dom": ">= 16.9.0" + } + }, + "node_modules/antd/node_modules/rc-image": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-4.2.0.tgz", + "integrity": "sha512-yGqq6wPrIn86hMfC1Hl7M3NNS6zqnl9dvFWJg/StuI86jZBU0rm9rePTfKs+4uiwU3HXxpfsXlaG2p8GWRDLiw==", + "license": "MIT", + "dependencies": { + "@ant-design/icons": "^4.2.2", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-dialog": "~8.4.0", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-input-number": { + "version": "6.1.3", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-6.1.3.tgz", + "integrity": "sha512-qCLWK9NuuKGTsPXjRU/XvSOX7EKdnHlOpg59nPjYSDdH/czsAHZyYq50O6b6RF2TMPOjVpmsZQoMjNJYcnn6JA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-mentions": { + "version": "1.5.3", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-1.5.3.tgz", + "integrity": "sha512-NG/KB8YiKBCJPHHvr/QapAb4f9YzLJn7kDHtmI1K6t7ZMM5YgrjIxNNhoRKKP9zJvb9PdPts69Hbg4ZMvLVIFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-menu": "^8.0.1", + "rc-textarea": "^0.3.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-menu": { + "version": "8.10.8", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-8.10.8.tgz", + "integrity": "sha512-0gnSR0nmR/60NnK+72EGd+QheHyPSQ3wYg1TwX1zl0JJ9Gm0purFFykCXVv/G0Jynpt0QySPAos+bpHpjMZdoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "mini-store": "^3.0.1", + "rc-motion": "^2.0.1", + "rc-trigger": "^5.1.2", + "rc-util": "^5.7.0", + "resize-observer-polyfill": "^1.5.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-picker": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-2.4.3.tgz", + "integrity": "sha512-tOIHslTQKpoGNmbpp6YOBwS39dQSvtAuhOm3bWCkkc4jCqUqeR/velCwqefZX1BX4+t1gUMc1dIia9XvOKrEkg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "^2.15.0", + "dayjs": "^1.8.30", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-resize-observer": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-0.2.6.tgz", + "integrity": "sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.0.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-select": { + "version": "11.5.3", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-11.5.3.tgz", + "integrity": "sha512-ASSO4J/ayfbQQ+KOEounIMGhySDHpQtrIuH1WEABOBy8HgKec8kOLmyLH+YIXSUDnTf/gtxmflgFtl7sQ9pkSw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-trigger": "^5.0.4", + "rc-util": "^5.0.1", + "rc-virtual-list": "^3.2.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-slider": { + "version": "9.6.5", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-9.6.5.tgz", + "integrity": "sha512-XRUJDK668hy8MwGnHzZlXCQXXIOUnEs4m2vwk1jgDILVBxI0GwGOlC6T499pYY+NEWg8YgdCOAucFs/+X5WHpg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-tooltip": "^5.0.1", + "rc-util": "^5.0.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-table": { + "version": "7.11.3", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.11.3.tgz", + "integrity": "sha512-YyZry1CdqUrcH7MmWtLQZVvVZWbmTEbI5m650AZ+zYw4D5VF701samkMYl5z/H9yQFr+ugvDtXcya+e3vwRkMQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-resize-observer": "^0.2.0", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tabs": { + "version": "11.7.3", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-11.7.3.tgz", + "integrity": "sha512-5nd2NVss9TprPRV9r8N05SjQyAE7zDrLejxFLcbJ+BdLxSwnGnk3ws/Iq0smqKZUnPQC0XEvnpF3+zlllUUT2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "^3.1.3", + "rc-menu": "^8.6.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tabs/node_modules/rc-resize-observer": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz", + "integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.15.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tooltip": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-5.0.2.tgz", + "integrity": "sha512-A4FejSG56PzYtSNUU4H1pVzfhtkV/+qMT2clK0CsSj+9mbc4USEtpWeX6A/jjVL+goBOMKj8qlH7BCZmZWh/Nw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "rc-trigger": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tree": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-4.0.0.tgz", + "integrity": "sha512-C2xlkA+/IypkHBPzbpAJGVWJh2HjeRbYCusA/m5k09WT6hQT0nC7LtLVmnb7QZecdBQPhoOgQh8gPwBR+xEMjQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.0.0", + "rc-virtual-list": "^3.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-tree-select": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-4.2.0.tgz", + "integrity": "sha512-VrrvBiOov6WR44RTGMqSw1Dmodg6Y++EH6a6R0ew43qsV4Ob0FGYRgoX811kImtt2Z+oAPJ6zZXN4WKtsQd3Gw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-select": "^11.1.1", + "rc-tree": "^4.0.0", + "rc-util": "^5.0.5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-upload": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-3.3.4.tgz", + "integrity": "sha512-v2sirR4JL31UTHD/f0LGUdd+tpFaOVUTPeIEjAXRP9kRN8TFhqOgcXl5ixtyqj90FmtRUmKmafCv0EmhBQUHqQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "license": "ISC" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-move": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/array-move/-/array-move-4.0.0.tgz", + "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "license": "ISC" + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmmirror.com/util/-/util-0.10.3.tgz", + "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmmirror.com/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "1.11.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-1.11.5.tgz", + "integrity": "sha512-XNtCsMAeAH1pdLMEg1z8/Bb3a8cdCbui9QbJATRFHHHW5kT6+NPI3zSVQUXgikTFITzsg+kYY5NTWhM2Orwt9w==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-import": { + "version": "1.13.5", + "resolved": "https://registry.npmmirror.com/babel-plugin-import/-/babel-plugin-import-1.13.5.tgz", + "integrity": "sha512-IkqnoV+ov1hdJVofly9pXRJmeDm9EtROfrc5i6eII0Hix2xMs5FEm8FG3ExMvazbnZBbgHIt6qdO8And6lCloQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "license": "MIT" + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "license": "MIT", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/buffer/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmmirror.com/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "license": "ISC", + "dependencies": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "license": "MIT", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001400", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001400.tgz", + "integrity": "sha512-Mv659Hn65Z4LgZdJ7ge5JTVbE3rqbJaaXgW5LEI9/tOaXclfIZ8DW7D7FCWWWmWiiPS7AC48S8kf3DApSxQdgA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/webpack": "^4.4.31", + "del": "^4.1.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "webpack": "*" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cli-truncate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/codemirror": { + "version": "5.65.8", + "resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.8.tgz", + "integrity": "sha512-TNGkSkkoAsmZSf6W6g35LMVQJBHKasc2CKwhr/fTxSYun7cn6J+CbtyNjV/MYlFVkNTsqZoviegyCZimWhoMMA==", + "license": "MIT" + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "license": "MIT", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==", + "license": "MIT", + "dependencies": { + "component-indexof": "0.0.3" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "license": "MIT" + }, + "node_modules/component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.17", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", + "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "license": "ISC", + "dependencies": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz", + "integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacache": "^12.0.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.1", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "webpack-log": "^2.0.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/copy-webpack-plugin/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/globby/-/globby-7.1.1.tgz", + "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/copy-webpack-plugin/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/copy-webpack-plugin/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/copy-webpack-plugin/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.25.1", + "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.25.1.tgz", + "integrity": "sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.25.1", + "resolved": "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.25.1.tgz", + "integrity": "sha512-7Fr74bliUDdeJCBMxkkIuQ4xfxn/SwrVg+HkJUAoNEXVqYLv55l6Af0dJ5Lq2YBUW9yKqSkLXaS5SYPK6MGa/A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmmirror.com/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", + "license": "MIT" + }, + "node_modules/css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "license": "MIT", + "dependencies": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, + "node_modules/css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmmirror.com/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + }, + "engines": { + "node": ">4" + } + }, + "node_modules/css-loader": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-2.1.1.tgz", + "integrity": "sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^5.2.0", + "icss-utils": "^4.1.0", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.14", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^2.0.6", + "postcss-modules-scope": "^2.1.0", + "postcss-modules-values": "^2.0.0", + "postcss-value-parser": "^3.3.0", + "schema-utils": "^1.0.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/css-loader/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "4.1.11", + "resolved": "https://registry.npmmirror.com/cssnano/-/cssnano-4.1.11.tgz", + "integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.8", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-preset-default": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", + "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.3", + "postcss-unique-selectors": "^4.0.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "license": "MIT" + }, + "node_modules/cxs": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/cxs/-/cxs-6.2.0.tgz", + "integrity": "sha512-RGatb1BUwVMBzV8DRo9Kapc55bdGfAxMcukVk+ZzE3Ts8xaTve0GVz730kBDxjhEBU2LK+RPuAcjZb00Q3O24w==", + "license": "MIT" + }, + "node_modules/cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "license": "MIT", + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.5", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.5.tgz", + "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/default-gateway/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/default-gateway/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/default-gateway/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/default-gateway/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "license": "MIT", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/del/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dnd-core": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-14.0.1.tgz", + "integrity": "sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==", + "license": "MIT", + "dependencies": { + "@react-dnd/asap": "^4.0.0", + "@react-dnd/invariant": "^2.0.0", + "redux": "^4.1.1" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-indexof": "^1.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-align": { + "version": "1.12.3", + "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.3.tgz", + "integrity": "sha512-Gj9hZN3a07cbR6zviMUBOMPdWxYhbMI+x+WS0NAIu2zFZmbK8ys9R79g+iG9qLnlCwpFoaB+fKy8Pdv470GsPA==", + "license": "MIT" + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-scroll-into-view": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz", + "integrity": "sha512-LwNVg3GJOprWDO+QhLL1Z9MMgWe/KAFLxVWKzjRTxNSPn8/LLDIfmuG71YHznXCqaqTjvHJDYO1MEAgX6XCNbQ==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "license": "MIT", + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.0.2", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.2.tgz", + "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/draft-js": { + "version": "0.10.5", + "resolved": "https://registry.npmmirror.com/draft-js/-/draft-js-0.10.5.tgz", + "integrity": "sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg==", + "license": "BSD-3-Clause", + "dependencies": { + "fbjs": "^0.8.15", + "immutable": "~3.7.4", + "object-assign": "^4.1.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0", + "react-dom": "^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/echarts": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.3.3.tgz", + "integrity": "sha512-BRw2serInRwO5SIwRviZ6Xgm5Lb7irgz+sLiFMmy/HOaf4SQ+7oYqxKzRHAKp4xHQ05AuHw1xvoQWJjDQq/FGw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.3.2" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz", + "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.251", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.251.tgz", + "integrity": "sha512-k4o4cFrWPv4SoJGGAydd07GmlRVzmeDIJ6MaEChTUjk4Dmomn189tCicSzil2oyvbPoGgg2suwPDNWq4gWRhoQ==", + "license": "ISC" + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmmirror.com/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.20.2", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.20.2.tgz", + "integrity": "sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.2", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "license": "MIT" + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.22.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz", + "integrity": "sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.18.1", + "string.prototype.matchall": "^4.0.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmmirror.com/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==", + "license": "BSD-3-Clause" + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "license": "MIT", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "license": "MIT", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmmirror.com/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fbjs": { + "version": "0.8.18", + "resolved": "https://registry.npmmirror.com/fbjs/-/fbjs-0.8.18.tgz", + "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", + "license": "MIT", + "dependencies": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.30" + } + }, + "node_modules/fbjs/node_modules/core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "license": "MIT" + }, + "node_modules/fetch-jsonp": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/fetch-jsonp/-/fetch-jsonp-1.2.3.tgz", + "integrity": "sha512-C13k1o7R9JTN1wmhKkrW5bU/00LwixXnkufQUR6Rbf4KCS0i8mycQaovt4WVbHnA2NKgi7Ryp9Whpy/CGcij6Q==", + "license": "MIT" + }, + "node_modules/figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmmirror.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "license": "ISC" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "optional": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^3.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/findup-sync/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/findup-sync/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "license": "MIT", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "license": "MIT" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-modules/node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-modules/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "license": "ISC" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hard-source-webpack-plugin": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.13.1.tgz", + "integrity": "sha512-r9zf5Wq7IqJHdVAQsZ4OP+dcUSvoHqDMxJlIzaE2J0TZWn3UjMMrHqwDHR8Jr/pzPfG7XxSe36E7Y8QGNdtuAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^2.4.1", + "find-cache-dir": "^2.0.0", + "graceful-fs": "^4.1.11", + "lodash": "^4.15.0", + "mkdirp": "^0.5.1", + "node-object-hash": "^1.2.0", + "parse-json": "^4.0.0", + "pkg-dir": "^3.0.0", + "rimraf": "^2.6.2", + "semver": "^5.6.0", + "tapable": "^1.0.0-beta.5", + "webpack-sources": "^1.0.1", + "write-json-file": "^2.3.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "webpack": "*" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-source-webpack-plugin/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "license": "MIT", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/highlight-words-core": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz", + "integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==", + "license": "MIT" + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmmirror.com/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.20", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + }, + "engines": { + "node": ">=6.9" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-middleware/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/husky": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/husky/-/husky-4.3.7.tgz", + "integrity": "sha512-0fQlcCDq/xypoyYSJvEuzbDPHFf8ZF9IXKJxlrnvxABTSzK1VPT2RKYQKrcgJ+YD39swgoB6sbzywUqFxUiqjw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^7.0.0", + "find-versions": "^4.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "bin": { + "husky-run": "bin/run.js", + "husky-upgrade": "lib/upgrader/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/husky" + } + }, + "node_modules/husky/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/husky/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/husky/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/husky/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/husky/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/husky/node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/husky/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==", + "dev": true, + "license": "ISC" + }, + "node_modules/icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmmirror.com/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "3.7.6", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-3.7.6.tgz", + "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", + "dev": true, + "license": "MIT" + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.6.tgz", + "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-lite": { + "version": "0.9.2", + "resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.9.2.tgz", + "integrity": "sha512-qZuxbaEiKLOKhX4sbHLfhFN9iA3YciuZLb37/DfXCpWnz8p7qNL2lwkpxYMXfjlS8eEEjpULPZxAUI8N6FYvYQ==" + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsencrypt": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.2.1.tgz", + "integrity": "sha512-k1sD5QV0KPn+D8uG9AdGzTQuamt82QZ3A3l6f7TRwMU6Oi2Vg0BsL+wZIQBONcraO1pc78ExMdvmBBJ8WhNYUA==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jsx-runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/jsx-runtime/-/jsx-runtime-1.2.0.tgz", + "integrity": "sha512-iCxmRTlUAWmXwHZxN0JSx/T7eRi0SkKAskE0lp+j4W1mzdNp49ja/9QI2ZmlggPM95RqnDw5ioYjw0EcvpIClw==", + "license": "MIT", + "dependencies": { + "object-assign": "^3.0.0" + } + }, + "node_modules/jsx-runtime/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/killable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true, + "license": "ISC" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/knowdesign": { + "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/knowdesign/-/knowdesign-1.3.7.tgz", + "integrity": "sha512-pv4imTV3My0z94bZROOVLoAwD0IrdVQpzBgdpK7vsEcRm2/kp+KeGCCF/fAfoX2SA7d5k6OD1HHc/dyEdexvQg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons": "^4.7.0", + "@ant-design/react-slick": "~0.28.1", + "@babel/runtime": "^7.12.5", + "@ctrl/tinycolor": "^3.4.0", + "@types/qs": "^6.9.7", + "antd-img-crop": "^3.14.1", + "array-move": "^4.0.0", + "array-tree-filter": "^2.1.0", + "axios": "^0.21.1", + "classnames": "^2.2.6", + "codemirror": "^5.56.0", + "copy-to-clipboard": "^3.2.0", + "crypto-js": "^4.1.1", + "echarts": "^5.1.2", + "echarts-for-react": "^3.0.1", + "fetch-jsonp": "^1.1.3", + "hoist-non-react-statics": "^3.3.2", + "jsencrypt": "^3.2.0", + "jsx-runtime": "^1.2.0", + "lodash": "^4.17.21", + "memoize-one": "^6.0.0", + "metismenujs": "^1.3.1", + "moment": "^2.25.3", + "omit.js": "^1.0.2", + "qs": "^6.10.1", + "query-string": "^7.0.1", + "rc-cascader": "~2.3.0", + "rc-checkbox": "~2.3.0", + "rc-collapse": "~3.1.0", + "rc-dialog": "~8.6.0", + "rc-drawer": "^4.4.3", + "rc-dropdown": "~3.2.0", + "rc-field-form": "~1.21.0", + "rc-image": "~5.2.5", + "rc-input-number": "~7.3.6", + "rc-mentions": "~1.6.1", + "rc-menu": "~9.0.12", + "rc-motion": "^2.4.4", + "rc-notification": "~4.5.7", + "rc-pagination": "~3.1.9", + "rc-picker": "~2.5.17", + "rc-progress": "~3.1.0", + "rc-rate": "~2.9.0", + "rc-resize-observer": "^1.1.0", + "rc-select": "~13.2.1", + "rc-slider": "~9.7.4", + "rc-steps": "~4.1.0", + "rc-switch": "~3.2.0", + "rc-table": "~7.19.0", + "rc-tabs": "~11.10.0", + "rc-textarea": "~0.3.0", + "rc-tooltip": "~5.1.1", + "rc-tree": "~5.3.0", + "rc-tree-select": "~4.8.0", + "rc-trigger": "^5.2.10", + "rc-upload": "~4.3.0", + "rc-util": "^5.14.0", + "react": "16.12.0", + "react-codemirror2": "7.2.1", + "react-color": "2.18.1", + "react-copy-to-clipboard": "^5.0.4", + "react-dnd": "^14.0.4", + "react-dnd-html5-backend": "^14.0.2", + "react-document-title": "^2.0.3", + "react-dom": "16.12.0", + "react-draggable": "^4.4.3", + "react-fast-compare": "^3.2.0", + "react-fast-marquee": "^1.2.1", + "react-github-button": "^0.1.11", + "react-helmet-async": "~1.2.0", + "react-highlight-words": "^0.16.0", + "react-infinite-scroll-component": "^6.1.0", + "react-infinite-scroller": "^1.2.4", + "react-intl": "^3.2.1", + "react-is": "^16.13.1", + "react-resizable": "^3.0.1", + "react-router-cache-route": "^1.11.1", + "react-router-dom": "5.2.1", + "react-sortable-hoc": "^2.0.0", + "react-sticky": "^6.0.3", + "react-syntax-highlighter": "13.4.0", + "react-test-renderer": "^17.0.1", + "react-text-loop": "^2.3.0", + "react-text-loop-next": "0.0.3", + "react-virtualized": "^9.22.3", + "react-window": "^1.8.5", + "reactstrap": "^9.0.1", + "scroll-into-view-if-needed": "^2.2.25" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/knowdesign/node_modules/rc-util": { + "version": "5.24.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "node_modules/less": { + "version": "3.13.1", + "resolved": "https://registry.npmmirror.com/less/-/less-3.13.1.tgz", + "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "tslib": "^1.10.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">= 4.8 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "less": "^2.3.1 || ^3.0.0", + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/less-loader/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "10.5.3", + "resolved": "https://registry.npmmirror.com/lint-staged/-/lint-staged-10.5.3.tgz", + "integrity": "sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "cli-truncate": "^2.1.0", + "commander": "^6.2.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.2.0", + "dedent": "^0.7.0", + "enquirer": "^2.3.6", + "execa": "^4.1.0", + "listr2": "^3.2.2", + "log-symbols": "^4.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", + "stringify-object": "^3.3.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/lint-staged/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/lint-staged/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmmirror.com/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "license": "MIT", + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/loader-utils/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loglevel": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lowlight/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "license": "MIT", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", + "license": "ISC" + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "license": "MIT", + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/metismenujs": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/metismenujs/-/metismenujs-1.3.1.tgz", + "integrity": "sha512-bb2f78827KWj/zeY9ZmnEnuEU9kRaW/WL1r5jJ2ALAtY34qSIvygE/HzNw1QXWQrW6t4ILELZGS6CcDz6gr5mw==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz", + "integrity": "sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "webpack-sources": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/mini-store": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/mini-store/-/mini-store-3.0.6.tgz", + "integrity": "sha512-YzffKHbYsMQGUWQRKdsearR79QsMzzJcDDmZKlJBqt5JNkqpyJHYlK6gP61O36X+sLf76sO9G6mhKBe83gIZIQ==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.2", + "shallowequal": "^1.0.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "license": "MIT" + }, + "node_modules/mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", + "license": "ISC", + "dependencies": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.16.0", + "resolved": "https://registry.npmmirror.com/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "license": "MIT", + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/native-request": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/native-request/-/native-request-1.1.0.tgz", + "integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "license": "MIT", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "license": "MIT", + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, + "node_modules/node-object-hash": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/node-object-hash/-/node-object-hash-1.4.2.tgz", + "integrity": "sha512-UdS4swXs85fCGWWf6t6DMGgpN/vnlKeSGEQ7hJcrs7PBFoxoKLmibc3QRb7fwiYsjdL7PX8iI/TMSlZ90dgHhQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-wheel": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz", + "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==", + "license": "BSD-3-Clause" + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "license": "MIT", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", + "dependencies": { + "array.prototype.reduce": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/omit.js": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/omit.js/-/omit.js-1.0.2.tgz", + "integrity": "sha512-/QPc6G2NS+8d4L/cQhbk6Yit1WTB6Us2g84A7A/1+w9d/eRGHyEqC5kkQtHVoHZ5NFWGG7tUGgrhVZwgZanKrQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.23.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "dev": true, + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/opn": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optimize-css-assets-webpack-plugin": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.8.tgz", + "integrity": "sha512-mgFS1JdOtEGzD8l+EuISqL57cKO+We9GcoiQEmdCWRqqck+FGNmYJtx9qfAPzEz+lRrlThWMuGDaRkI/yWNx/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano": "^4.1.10", + "last-call-webpack-plugin": "^3.0.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "license": "MIT" + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "^0.12.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "license": "MIT", + "dependencies": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "license": "ISC", + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "license": "MIT" + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmmirror.com/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/postcss-calc/node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss": "^7.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz", + "integrity": "sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0", + "postcss-value-parser": "^3.3.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-values": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz", + "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^7.0.6" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz", + "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "license": "MIT", + "dependencies": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true, + "license": "ISC" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-error": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^2.0.4" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/progress-bar-webpack-plugin": { + "version": "1.12.1", + "resolved": "https://registry.npmmirror.com/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-1.12.1.tgz", + "integrity": "sha512-tVbPB5xBbqNwdH3mwcxzjL1r1Vrm/xGu93OsqVSAbCaXGoKFvfWIh0gpMDpn2kYsPVRSAIK0pBkP9Vfs+JJibQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.1", + "object.assign": "^4.0.1", + "progress": "^1.1.8" + }, + "peerDependencies": { + "webpack": "^1.3.0 || ^2 || ^3 || ^4" + } + }, + "node_modules/progress-bar-webpack-plugin/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/progress-bar-webpack-plugin/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/progress-bar-webpack-plugin/node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/progress-bar-webpack-plugin/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmmirror.com/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc-align": { + "version": "4.0.12", + "resolved": "https://registry.npmmirror.com/rc-align/-/rc-align-4.0.12.tgz", + "integrity": "sha512-3DuwSJp8iC/dgHzwreOQl52soj40LchlfUHtgACOUtwGuoFIOVh6n/sCpfqCU8kO5+iz6qR0YKvjgB8iPdE3aQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "dom-align": "^1.7.0", + "lodash": "^4.17.21", + "rc-util": "^5.3.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-align/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmmirror.com/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + } + }, + "node_modules/rc-cascader": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-2.3.3.tgz", + "integrity": "sha512-ckD8rKJjS8mdXxylWh1PtIHSFhbj/yf1NimyooqeJlvtLhzRZXIHCj4IuOjwYZ6J1DqoOCCdJfVtc7UeZia38w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "array-tree-filter": "^2.1.0", + "classnames": "^2.3.1", + "rc-tree-select": "~4.8.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.6.1", + "warning": "^4.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-cascader/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-2.3.2.tgz", + "integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.1.4.tgz", + "integrity": "sha512-WayrhswKMwuJab9xbqFxXTgV0m6X8uOPEO6zm/GJ5YJiJ/wIh/Dd2VtWeI06HYUEnTFv0HNcYv+zWbB+p6OD2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.2.1", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "8.6.0", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-8.6.0.tgz", + "integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.6.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-4.4.3.tgz", + "integrity": "sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.7.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "3.2.5", + "resolved": "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-3.2.5.tgz", + "integrity": "sha512-dVO2eulOSbEf+F4OyhCY5iGiMVhUYY/qeXxL7Ex2jDBt/xc89jU07mNoowV6aWxwVOc70pxEINff0oM2ogjluA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-trigger": "^5.0.4" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-editor-core": { + "version": "0.8.10", + "resolved": "https://registry.npmmirror.com/rc-editor-core/-/rc-editor-core-0.8.10.tgz", + "integrity": "sha512-T3aHpeMCIYA1sdAI7ynHHjXy5fqp83uPlD68ovZ0oClTSc3tbHmyCxXlA+Ti4YgmcpCYv7avF6a+TIbAka53kw==", + "dependencies": { + "babel-runtime": "^6.26.0", + "classnames": "^2.2.5", + "draft-js": "^0.10.0", + "immutable": "^3.7.4", + "lodash": "^4.16.5", + "prop-types": "^15.5.8", + "setimmediate": "^1.0.5" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" + } + }, + "node_modules/rc-editor-mention": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/rc-editor-mention/-/rc-editor-mention-1.1.13.tgz", + "integrity": "sha512-3AOmGir91Fi2ogfRRaXLtqlNuIwQpvla7oUnGHS1+3eo7b+fUp5IlKcagqtwUBB5oDNofoySXkLBxzWvSYNp/Q==", + "dependencies": { + "babel-runtime": "^6.23.0", + "classnames": "^2.2.5", + "dom-scroll-into-view": "^1.2.0", + "draft-js": "~0.10.0", + "immutable": "~3.7.4", + "prop-types": "^15.5.8", + "rc-animate": "^2.3.0", + "rc-editor-core": "~0.8.3" + }, + "peerDependencies": { + "react": ">=15.x", + "react-dom": ">=15.x" + } + }, + "node_modules/rc-field-form": { + "version": "1.21.2", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.21.2.tgz", + "integrity": "sha512-LR/bURt/Tf5g39mb0wtMtQuWn42d/7kEzpzlC5fNC7yaRVmLTtlPP4sBBlaViETM9uZQKLoaB0Pt9Mubhm9gow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4", + "async-validator": "^4.0.2", + "rc-util": "^5.8.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">= 16.9.0", + "react-dom": ">= 16.9.0" + } + }, + "node_modules/rc-field-form/node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/rc-field-form/node_modules/rc-util": { + "version": "5.24.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-form": { + "version": "2.4.12", + "resolved": "https://registry.npmmirror.com/rc-form/-/rc-form-2.4.12.tgz", + "integrity": "sha512-sHfyWRrnjCHkeCYfYAGop2GQBUC6CKMPcJF9h/gL/vTmZB/RN6fNOGKjXrXjFbwFwKXUWBoPtIDDDmXQW9xNdw==", + "license": "MIT", + "dependencies": { + "async-validator": "~1.11.3", + "babel-runtime": "6.x", + "create-react-class": "^15.5.3", + "dom-scroll-into-view": "1.x", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.4", + "rc-util": "^4.15.3", + "react-is": "^16.13.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0" + } + }, + "node_modules/rc-image": { + "version": "5.2.5", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-5.2.5.tgz", + "integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-dialog": "~8.6.0", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input-number": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-7.3.7.tgz", + "integrity": "sha512-W9jDwfhJyNjg0iZX401r0GctTGX4ETURzF6SisC42GR0AkJxtaPD89eGwbTdAudUjEx0Pkn2rGmfvVGGdQACKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.23.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input-number/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "1.6.5", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-1.6.5.tgz", + "integrity": "sha512-CUU4+q+awG2pA0l/tG2kPB2ytWbKQUkFxVeKwacr63w7crE/yjfzrFXxs/1fxhyEbQUWdAZt/L25QBieukYQ5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-menu": "~9.3.2", + "rc-textarea": "^0.3.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions/node_modules/rc-menu": { + "version": "9.3.2", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.3.2.tgz", + "integrity": "sha512-h3m45oY1INZyqphGELkdT0uiPnFzxkML8m0VMhJnk2fowtqfiT7F5tJLT3znEVaPIY80vMy1bClCkgq8U91CzQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.0.14", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.0.14.tgz", + "integrity": "sha512-CIox5mZeLDAi32SlHrV7UeSjv7tmJJhwRyxQtZCKt351w3q59XlL4WMFOmtT9gwIfP9h0XoxdBZUMe/xzkp78A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu/node_modules/rc-util": { + "version": "5.24.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.6.2.tgz", + "integrity": "sha512-4w1FaX3dtV749P8GwfS4fYnFG4Rb9pxvCYPc/b2fw1cmlHJWNNgOFIz7ysiD+eOrzJSvnLJWlNQQncpNMXwwpg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "4.5.7", + "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-4.5.7.tgz", + "integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.2.0", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.2.8.tgz", + "integrity": "sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.19.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "3.1.17", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-3.1.17.tgz", + "integrity": "sha512-/BQ5UxcBnW28vFAcP2hfh+Xg15W0QZn8TWYwdCApchMH1H0CxiaUUcULP8uXcFM1TygcdKWdt3JqsL9cTAfdkQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "2.5.19", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-2.5.19.tgz", + "integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "2.x", + "dayjs": "1.x", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-progress": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/rc-progress/-/rc-progress-3.1.4.tgz", + "integrity": "sha512-XBAif08eunHssGeIdxMXOmRQRULdHaDdIFENQ578CMb4dyewahmmfJRyab+hw4KH4XssEzzYOkAInTLS7JJG+Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.9.2.tgz", + "integrity": "sha512-SaiZFyN8pe0Fgphv8t3+kidlej+cq/EALkAJAc3A0w0XcPaH2L1aggM8bhe1u6GAGuQNAoFvTLjw4qLPGRKV5g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz", + "integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.15.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-select": { + "version": "13.2.1", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-13.2.1.tgz", + "integrity": "sha512-L2cJFAjVEeDiNVa/dlOVKE79OUb0J7sUBvWN3Viav3XHcjvv9Ovn4D8J9QhBSlDXeGuczZ81CZI3BbdHD25+Gg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.0.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.9.8", + "rc-virtual-list": "^3.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-select/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-slider": { + "version": "9.7.5", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-9.7.5.tgz", + "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-tooltip": "^5.0.1", + "rc-util": "^5.16.1", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-slider/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/rc-steps/-/rc-steps-4.1.4.tgz", + "integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.2", + "classnames": "^2.2.3", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/rc-switch/-/rc-switch-3.2.2.tgz", + "integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.19.2", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.19.2.tgz", + "integrity": "sha512-NdpnoM50MK02H5/hGOsObfxCvGFUG5cHB9turE5BKJ81T5Ycbq193w5tLhnpILXe//Oanzr47MdMxkUnVGP+qg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.14.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table/node_modules/rc-util": { + "version": "5.24.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "11.10.8", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-11.10.8.tgz", + "integrity": "sha512-uK+x+eJ8WM4jiXoqGa+P+JUQX2Wlkj9f0o/5dyOw42B6YLnHJN80uTVcCeAmtA1N0xjPW0GNSZvUm4SU3jAYpw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "^3.2.0", + "rc-menu": "~9.3.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs/node_modules/rc-menu": { + "version": "9.3.2", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.3.2.tgz", + "integrity": "sha512-h3m45oY1INZyqphGELkdT0uiPnFzxkML8m0VMhJnk2fowtqfiT7F5tJLT3znEVaPIY80vMy1bClCkgq8U91CzQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "0.3.7", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-0.3.7.tgz", + "integrity": "sha512-yCdZ6binKmAQB13hc/oehh0E/QRwoPP1pjF21aHBxlgXO3RzPF6dUu4LG2R4FZ1zx/fQd2L1faktulrXOM/2rw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.7.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-5.1.1.tgz", + "integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "rc-trigger": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.3.8", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.3.8.tgz", + "integrity": "sha512-YuobEryPymqPmHFUOvsoOrYdm24psaj0CrGEUuDUQUeG/nNcTGw6FA2YmF4NsEaNBvNSJUSzwfZnFHrKa/xv0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.4.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "4.8.0", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-4.8.0.tgz", + "integrity": "sha512-evuVIF7GHCGDdvISdBWl4ZYmG/8foof/RDtzCu/WFLA1tFKZD77RRC3khEsjh4WgsB0vllLe7j+ODJ7jHRcDRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-select": "~13.2.1", + "rc-tree": "~5.3.0", + "rc-util": "^5.7.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select/node_modules/rc-tree": { + "version": "5.3.8", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.3.8.tgz", + "integrity": "sha512-YuobEryPymqPmHFUOvsoOrYdm24psaj0CrGEUuDUQUeG/nNcTGw6FA2YmF4NsEaNBvNSJUSzwfZnFHrKa/xv0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.4.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree/node_modules/rc-util": { + "version": "5.24.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz", + "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-trigger": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/rc-trigger/-/rc-trigger-5.3.1.tgz", + "integrity": "sha512-5gaFbDkYSefZ14j2AdzucXzlWgU2ri5uEjkHvsf1ynRhdJbKxNOnw4PBZ9+FVULNGFiDzzlVF8RJnR9P/xrnKQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.6", + "rc-align": "^4.0.0", + "rc-motion": "^2.0.0", + "rc-util": "^5.19.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-trigger/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-upload": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.3.4.tgz", + "integrity": "sha512-uVbtHFGNjHG/RyAfm9fluXB6pvArAGyAx8z7XzXXyorEgVIWj6mOlriuDm0XowDHYz4ycNK0nE0oP3cbFnzxiQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-upload/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.4.8", + "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.4.8.tgz", + "integrity": "sha512-qSN+Rv4i/E7RCTvTMr1uZo7f3crJJg/5DekoCagydo9zsXrxj07zsFSxqizqW+ldGA16lwa8So/bIbV9Ofjddg==", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.15.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-virtual-list/node_modules/rc-util": { + "version": "5.24.2", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", + "integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "16.12.0", + "resolved": "https://registry.npmmirror.com/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-codemirror2": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz", + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "license": "MIT", + "peerDependencies": { + "codemirror": "5.x", + "react": ">=15.5 <=16.x" + } + }, + "node_modules/react-color": { + "version": "2.18.1", + "resolved": "https://registry.npmmirror.com/react-color/-/react-color-2.18.1.tgz", + "integrity": "sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==", + "license": "MIT", + "dependencies": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.11", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "license": "MIT", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, + "node_modules/react-cron-antd": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/react-cron-antd/-/react-cron-antd-1.1.2.tgz", + "integrity": "sha512-SnSDDWoqBii1XMVkkJqY2L/EsKD/RL3jIGyHF4LSR3Z+UbvmwwTtZ13wsqk+FYADCXdfI5r0M7eGUJo8Dh444Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.12.5", + "antd": "4.9.2", + "react": "17.0.1", + "react-dom": "17.0.1" + } + }, + "node_modules/react-cron-antd/node_modules/@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/react-cron-antd/node_modules/react": { + "version": "17.0.1", + "resolved": "https://registry.npmmirror.com/react/-/react-17.0.1.tgz", + "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-cron-antd/node_modules/react-dom": { + "version": "17.0.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-17.0.1.tgz", + "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.1" + }, + "peerDependencies": { + "react": "17.0.1" + } + }, + "node_modules/react-cron-antd/node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/react-dnd": { + "version": "14.0.5", + "resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-14.0.5.tgz", + "integrity": "sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A==", + "license": "MIT", + "dependencies": { + "@react-dnd/invariant": "^2.0.0", + "@react-dnd/shallowequal": "^2.0.0", + "dnd-core": "14.0.1", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "@types/hoist-non-react-statics": ">= 3.3.1", + "@types/node": ">= 12", + "@types/react": ">= 16", + "react": ">= 16.14" + }, + "peerDependenciesMeta": { + "@types/hoist-non-react-statics": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "14.1.0", + "resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz", + "integrity": "sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==", + "license": "MIT", + "dependencies": { + "dnd-core": "14.0.1" + } + }, + "node_modules/react-document-title": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/react-document-title/-/react-document-title-2.0.3.tgz", + "integrity": "sha512-T5y+quDAybtD7JhvVyc2BDW3a9xj6MoW6/VZU6OJkbASqwEMo5G4nB0RqFJCEHOqjQMcQI+wGRPDhUADnaHlQw==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.6", + "react-side-effect": "^1.0.2" + } + }, + "node_modules/react-dom": { + "version": "16.12.0", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-16.12.0.tgz", + "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.18.0" + }, + "peerDependencies": { + "react": "^16.0.0" + } + }, + "node_modules/react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-easy-crop": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/react-easy-crop/-/react-easy-crop-3.5.3.tgz", + "integrity": "sha512-ApTbh+lzKAvKqYW81ihd5J6ZTNN3vPDwi6ncFuUrHPI4bko2DlYOESkRm+0NYoW0H8YLaD7bxox+Z3EvIzAbUA==", + "license": "MIT", + "dependencies": { + "normalize-wheel": "^1.0.1", + "tslib": "2.0.1" + }, + "peerDependencies": { + "react": ">=16.4.0", + "react-dom": ">=16.4.0" + } + }, + "node_modules/react-easy-crop/node_modules/tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "license": "0BSD" + }, + "node_modules/react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==", + "license": "MIT" + }, + "node_modules/react-fast-marquee": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/react-fast-marquee/-/react-fast-marquee-1.3.5.tgz", + "integrity": "sha512-eOqLoz4iVVBvi2wN/web8hd2XX9y2Z6CYR7g++7nTVHlTOXBtqyARQJ9rYNpbp179hAzloMx0yBFAo8LpNYmKQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.8.0 || 18.0.0", + "react-dom": ">= 16.8.0 || 18.0.0" + } + }, + "node_modules/react-floater": { + "version": "0.7.6", + "resolved": "https://registry.npmmirror.com/react-floater/-/react-floater-0.7.6.tgz", + "integrity": "sha512-tt/15k/HpaShbtvWCwsQYLR+ebfUuYbl+oAUJ3DcEDkgYKeUcSkDey2PdAIERdVwzdFZANz47HbwoET2/Rduxg==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "exenv": "^1.2.2", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "react-proptype-conditional-require": "^1.0.4", + "tree-changes": "^0.9.1" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-floater/node_modules/is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==", + "license": "MIT" + }, + "node_modules/react-freeze": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/react-freeze/-/react-freeze-1.0.3.tgz", + "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, + "node_modules/react-github-button": { + "version": "0.1.11", + "resolved": "https://registry.npmmirror.com/react-github-button/-/react-github-button-0.1.11.tgz", + "integrity": "sha512-KL/kieQiR5DXd1RxWMegr4Igyz9+Lm6ZVwjpN5rQyttz/sdEq8DF1R/vzLl2f58nChJe0sKE3U3A7QRK+Zb01w==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.10" + } + }, + "node_modules/react-helmet-async": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-1.2.3.tgz", + "integrity": "sha512-mCk2silF53Tq/YaYdkl2sB+/tDoPnaxN7dFS/6ZLJb/rhUY2EWGI5Xj2b4jHppScMqY45MbgPSwTxDchKpZ5Kw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0", + "react-dom": "^16.6.0 || ^17.0.0" + } + }, + "node_modules/react-highlight-words": { + "version": "0.16.0", + "resolved": "https://registry.npmmirror.com/react-highlight-words/-/react-highlight-words-0.16.0.tgz", + "integrity": "sha512-q34TwCSJOL+5pVDv6LUj3amaoyXdNDwd7zRqVAvceOrO9g1haWLAglK6WkGLMNUa3PFN8EgGedLg/k8Gpndxqg==", + "license": "MIT", + "dependencies": { + "highlight-words-core": "^1.2.0", + "memoize-one": "^4.0.0", + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0-0" + } + }, + "node_modules/react-highlight-words/node_modules/memoize-one": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-4.0.3.tgz", + "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==", + "license": "MIT" + }, + "node_modules/react-infinite-scroll-component": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz", + "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==", + "license": "MIT", + "dependencies": { + "throttle-debounce": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/react-infinite-scroller": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz", + "integrity": "sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-intl": { + "version": "3.12.1", + "resolved": "https://registry.npmmirror.com/react-intl/-/react-intl-3.12.1.tgz", + "integrity": "sha512-cgumW29mwROIqyp8NXStYsoIm27+8FqnxykiLSawWjOxGIBeLuN/+p2srei5SRIumcJefOkOIHP+NDck05RgHg==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/intl-displaynames": "^1.2.0", + "@formatjs/intl-listformat": "^1.4.1", + "@formatjs/intl-relativetimeformat": "^4.5.9", + "@formatjs/intl-unified-numberformat": "^3.2.0", + "@formatjs/intl-utils": "^2.2.0", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/invariant": "^2.2.31", + "hoist-non-react-statics": "^3.3.2", + "intl-format-cache": "^4.2.21", + "intl-messageformat": "^7.8.4", + "intl-messageformat-parser": "^3.6.4", + "shallow-equal": "^1.2.1" + }, + "peerDependencies": { + "react": "^16.3.0" + } + }, + "node_modules/react-intl/node_modules/intl-format-cache": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/intl-format-cache/-/intl-format-cache-4.3.1.tgz", + "integrity": "sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q==", + "license": "BSD-3-Clause" + }, + "node_modules/react-intl/node_modules/intl-messageformat": { + "version": "7.8.4", + "resolved": "https://registry.npmmirror.com/intl-messageformat/-/intl-messageformat-7.8.4.tgz", + "integrity": "sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA==", + "license": "BSD-3-Clause", + "dependencies": { + "intl-format-cache": "^4.2.21", + "intl-messageformat-parser": "^3.6.4" + } + }, + "node_modules/react-intl/node_modules/intl-messageformat-parser": { + "version": "3.6.4", + "resolved": "https://registry.npmmirror.com/intl-messageformat-parser/-/intl-messageformat-parser-3.6.4.tgz", + "integrity": "sha512-RgPGwue0mJtoX2Ax8EmMzJzttxjnva7gx0Q7mKJ4oALrTZvtmCeAw5Msz2PcjW4dtCh/h7vN/8GJCxZO1uv+OA==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/intl-unified-numberformat": "^3.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-joyride": { + "version": "2.5.3", + "resolved": "https://registry.npmmirror.com/react-joyride/-/react-joyride-2.5.3.tgz", + "integrity": "sha512-DKKvb/JAAsHm0x/RWO3WI6NOtTMHDso5v8MTauxTSz2dFs7Tu1rWg1BDBWmEMj6pUCvem7hblFbCiDAcvhs8tQ==", + "dependencies": { + "deepmerge": "^4.2.2", + "exenv": "^1.2.2", + "is-lite": "^0.9.2", + "prop-types": "^15.8.1", + "react-floater": "^0.7.6", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.0.1", + "tree-changes": "^0.9.2" + } + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-motion": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/react-motion/-/react-motion-0.5.2.tgz", + "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", + "license": "MIT", + "dependencies": { + "performance-now": "^0.2.0", + "prop-types": "^15.5.8", + "raf": "^3.1.0" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0" + } + }, + "node_modules/react-motion/node_modules/performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha512-YHk5ez1hmMR5LOkb9iJkLKqoBlL7WD5M8ljC75ZfzXriuBIVNuecaXuU7e+hOwyqf24Wxhh7Vxgt7Hnw9288Tg==", + "license": "MIT" + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-proptype-conditional-require": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz", + "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.10.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.10.0.tgz", + "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==", + "license": "MIT", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + }, + "peerDependencies": { + "react": ">= 16.3" + } + }, + "node_modules/react-router": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-5.2.1.tgz", + "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-cache-route": { + "version": "1.12.11", + "resolved": "https://registry.npmmirror.com/react-router-cache-route/-/react-router-cache-route-1.12.11.tgz", + "integrity": "sha512-BexDIFju1v3+LTbJznls/CkmXiZ7zLTzPsSbhTY+Jcys9LQXJnLkZFDWMe+g5SxOUklzbrp4xpY2uQvHH3iWsA==", + "license": "ISC", + "dependencies": { + "mini-create-react-context": "^0.4.1", + "react-freeze": "^1.0.0" + }, + "peerDependencies": { + "prop-types": ">=15", + "react": ">=15", + "react-router-dom": ">=4" + } + }, + "node_modules/react-router-dom": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-5.2.1.tgz", + "integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.1", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmmirror.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-side-effect": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/react-side-effect/-/react-side-effect-1.2.0.tgz", + "integrity": "sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w==", + "license": "MIT", + "dependencies": { + "shallowequal": "^1.0.1" + }, + "peerDependencies": { + "react": "^0.13.0 || ^0.14.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/react-sortable-hoc": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", + "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + }, + "peerDependencies": { + "prop-types": "^15.5.7", + "react": "^16.3.0 || ^17.0.0", + "react-dom": "^16.3.0 || ^17.0.0" + } + }, + "node_modules/react-sticky": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/react-sticky/-/react-sticky-6.0.3.tgz", + "integrity": "sha512-LNH4UJlRatOqo29/VHxDZOf6fwbgfgcHO4mkEFvrie5FuaZCSTGtug5R8NGqJ0kSnX8gHw8qZN37FcvnFBJpTQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.8", + "raf": "^3.3.0" + }, + "peerDependencies": { + "react": ">=15", + "react-dom": ">=15" + } + }, + "node_modules/react-syntax-highlighter": { + "version": "13.4.0", + "resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-13.4.0.tgz", + "integrity": "sha512-LL4idMEOyGNro/BCaW1yRjGB1OO8n0OrXnuGg/NJoWvP1dtkHTy5paJfRc/SQdAFfvl0pPRRxvATzkxkKegOcQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.1.1", + "lowlight": "^1.14.0", + "prismjs": "^1.21.0", + "refractor": "^3.0.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/react-syntax-highlighter/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/react-test-renderer": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz", + "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^17.0.2", + "react-shallow-renderer": "^16.13.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/react-test-renderer/node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/react-text-loop": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/react-text-loop/-/react-text-loop-2.3.0.tgz", + "integrity": "sha512-tRLxdkhc1ojAICxERJNOWj3Ry7NIGmFQF4tR6cRVyL+5zVD+gj+8uGPvOgEBLuj2vmjTXLPvBMVVCnoAIy1+DA==", + "license": "MIT", + "dependencies": { + "cxs": "^6.2.0", + "react-fast-compare": "2.0.4", + "react-motion": "^0.5.2" + }, + "peerDependencies": { + "react": "^15.0.1 || ^16.0.1", + "react-dom": "^15.0.1 || ^16.0.1" + } + }, + "node_modules/react-text-loop-next": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/react-text-loop-next/-/react-text-loop-next-0.0.3.tgz", + "integrity": "sha512-TbkzFLArOCrBcnyLFhB7Z+KOQvP9fmZYJ3x6gSdG6BE7wxrSBJ+LQaF+WqAQqTYpmSaHBDso7kKVRnGsrBYijQ==", + "license": "MIT", + "dependencies": { + "cxs": "^6.2.0", + "react-motion": "^0.5.2" + }, + "peerDependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + } + }, + "node_modules/react-text-loop/node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", + "license": "MIT" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-virtualized": { + "version": "9.22.3", + "resolved": "https://registry.npmmirror.com/react-virtualized/-/react-virtualized-9.22.3.tgz", + "integrity": "sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha", + "react-dom": "^15.3.0 || ^16.0.0-alpha" + } + }, + "node_modules/react-window": { + "version": "1.8.7", + "resolved": "https://registry.npmmirror.com/react-window/-/react-window-1.8.7.tgz", + "integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-window/node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.0.1" + } + }, + "node_modules/reactstrap": { + "version": "9.1.4", + "resolved": "https://registry.npmmirror.com/reactstrap/-/reactstrap-9.1.4.tgz", + "integrity": "sha512-kn+Ex58V4tZatogn472n5fkgvCkXwhQvlqCRGTjpM1MzkD9wv0rp5W0VcM60purpdtzkud2ku6KHvJrqYqnv0w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@popperjs/core": "^2.6.0", + "classnames": "^2.2.3", + "prop-types": "^15.5.8", + "react-popper": "^2.2.4", + "react-transition-group": "^4.4.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redux": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.2.1.tgz", + "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/renderkid": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/renderkid/-/renderkid-2.0.7.tgz", + "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^3.0.1" + } + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "license": "ISC" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-dir/node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "license": "MIT" + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "license": "ISC", + "dependencies": { + "aproba": "^1.1.1" + } + }, + "node_modules/rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.18.0", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.29", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz", + "integrity": "sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.17" + } + }, + "node_modules/scrollparent": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/scrollparent/-/scrollparent-2.0.1.tgz", + "integrity": "sha512-HSdN78VMvFCSGCkh0oYX/tY4R3P1DW61f8+TeZZ4j2VLgfwvw0bpRSOv4PCVKisktIwbzHCfZsx+rLbbDBqIBA==", + "license": "ISC" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "1.10.14", + "resolved": "https://registry.npmmirror.com/selfsigned/-/selfsigned-1.10.14.tgz", + "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-forge": "^0.10.0" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver-regex": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/semver-regex/-/semver-regex-3.1.4.tgz", + "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", + "license": "MIT" + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/single-spa": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/single-spa/-/single-spa-5.9.3.tgz", + "integrity": "sha512-qMGraRzIBsodV6569Fob4cQ4/yQNrcZ5Achh3SAQDljmqUtjAZ7BAA7GAyO/l5eizb7GtTmVq9Di7ORyKw82CQ==", + "license": "MIT" + }, + "node_modules/single-spa-react": { + "version": "2.14.0", + "resolved": "https://registry.npmmirror.com/single-spa-react/-/single-spa-react-2.14.0.tgz", + "integrity": "sha512-KQ2/y7/JBIquK0WUiwb1/Y7f4qTZITNotw+JwNPesj0WKeCi91u0LOZe2ps56QMJbyB4UrA5IzMBwbYWDr1pIw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmmirror.com/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/size-sensor": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.1.tgz", + "integrity": "sha512-QTy7MnuugCFXIedXRpUSk9gUnyNiaxIdxGfUjr8xxXOqIB3QvBUYP9+b51oCg2C4dnhaeNk/h57TxjbvoJrJUA==", + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "license": "MIT", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "license": "MIT", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://tidelift.com/funding/github/npm/sockjs-client" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "license": "MIT" + }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "license": "ISC", + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true, + "license": "MIT" + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT" + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "license": "MIT", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "license": "MIT" + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/svgo/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmmirror.com/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmmirror.com/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "license": "BSD-2-Clause", + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "license": "MIT", + "dependencies": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser-webpack-plugin/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser-webpack-plugin/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser-webpack-plugin/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser-webpack-plugin/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser-webpack-plugin/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser-webpack-plugin/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/terser-webpack-plugin/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "license": "MIT", + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/terser-webpack-plugin/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz", + "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "license": "MIT", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmmirror.com/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + }, + "node_modules/tree-changes/node_modules/is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==", + "license": "MIT" + }, + "node_modules/ts-loader": { + "version": "8.4.0", + "resolved": "https://registry.npmmirror.com/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "*" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmmirror.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "license": "MIT", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "license": "MIT", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "license": "MIT", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "license": "MIT" + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/url/-/url-0.11.0.tgz", + "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "license": "MIT", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "license": "MIT" + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmmirror.com/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "license": "MIT" + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "optionalDependencies": { + "chokidar": "^3.4.1", + "watchpack-chokidar2": "^2.0.1" + } + }, + "node_modules/watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^2.1.8" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "license": "ISC", + "optional": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "license": "MIT", + "optional": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "license": "MIT", + "optional": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "license": "ISC", + "optional": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT", + "optional": true + }, + "node_modules/watchpack-chokidar2/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "license": "MIT", + "optional": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "4.46.0", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.5.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + }, + "webpack-command": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.6.1", + "resolved": "https://registry.npmmirror.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.1.tgz", + "integrity": "sha512-oKz9Oz9j3rUciLNfpGFjOb49/jEpXNmWdVH8Ls//zNcnLlQdTGXQQMsBbb/gR7Zl8WNLxVCq+0Hqbx3zv6twBw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npmmirror.com/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=6.11.5" + }, + "peerDependencies": { + "webpack": "4.x.x" + } + }, + "node_modules/webpack-cli/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/webpack-cli/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webpack-cli/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "3.7.3", + "resolved": "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", + "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "3.11.3", + "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz", + "integrity": "sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-html-community": "0.0.8", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.8", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.21", + "sockjs-client": "^1.5.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "^13.3.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 6.11.5" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "license": "ISC", + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/webpack-dev-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-dev-server/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/webpack-dev-server/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-log/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/webpack/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "license": "MIT", + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/webpack/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "license": "MIT", + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/webpack/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "deprecated": "[WARNING] Use 1.0.0 instead of 1.1.0, reason: https://github.com/zkochan/packages/commit/e7af82f03f1afe6086312d80a0fa47eb8d1c6bff", + "dev": true, + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "license": "MIT", + "dependencies": { + "errno": "~0.1.7" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/write-json-file": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/write-json-file/-/write-json-file-2.3.0.tgz", + "integrity": "sha512-84+F0igFp2dPD6UpAQjOUX3CdKUOqUzn6oE9sDBNzUXINR5VceJ1rauZltqQB/bcYsx3EpKys4C7/PivKUAiWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-indent": "^5.0.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "pify": "^3.0.0", + "sort-keys": "^2.0.0", + "write-file-atomic": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-json-file/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zrender": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.3.2.tgz", + "integrity": "sha512-8IiYdfwHj2rx0UeIGZGGU4WEVSDEdeVCaIg/fomejg1Xu6OifAL1GVzIPHg2D+MyUkbNgPWji90t0a8IDk+39w==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + } + }, "dependencies": { "@ampproject/remapping": { "version": "2.2.0", @@ -1352,7 +21817,8 @@ "@icons/material": { "version": "0.2.4", "resolved": "https://registry.npmmirror.com/@icons/material/-/material-0.2.4.tgz", - "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "requires": {} }, "@jridgewell/gen-mapping": { "version": "0.1.1", @@ -1389,8 +21855,8 @@ }, "@knowdesign/icons": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz", - "integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==", + "resolved": "http://registry.npm.xiaojukeji.com/@knowdesign/icons/download/@knowdesign/icons-1.0.2.tgz", + "integrity": "sha1-OqNaeMDVsGShJYux2KMW86erHjk=", "requires": { "@ant-design/colors": "^6.0.0", "@ant-design/icons": "^4.7.0", @@ -1554,7 +22020,7 @@ "version": "12.20.55", "resolved": "https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz", "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true + "devOptional": true }, "@types/parse-json": { "version": "4.0.0", @@ -1833,7 +22299,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.9.0.tgz", "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "dev": true, "requires": { "@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/helper-wasm-bytecode": "1.9.0", @@ -1843,26 +22308,22 @@ "@webassemblyjs/floating-point-hex-parser": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" }, "@webassemblyjs/helper-api-error": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" }, "@webassemblyjs/helper-buffer": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" }, "@webassemblyjs/helper-code-frame": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, "requires": { "@webassemblyjs/wast-printer": "1.9.0" } @@ -1870,14 +22331,12 @@ "@webassemblyjs/helper-fsm": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" }, "@webassemblyjs/helper-module-context": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0" } @@ -1885,14 +22344,12 @@ "@webassemblyjs/helper-wasm-bytecode": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" }, "@webassemblyjs/helper-wasm-section": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-buffer": "1.9.0", @@ -1904,7 +22361,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -1913,7 +22369,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -1921,14 +22376,12 @@ "@webassemblyjs/utf8": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "dev": true + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" }, "@webassemblyjs/wasm-edit": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-buffer": "1.9.0", @@ -1944,7 +22397,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-wasm-bytecode": "1.9.0", @@ -1957,7 +22409,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-buffer": "1.9.0", @@ -1969,7 +22420,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-api-error": "1.9.0", @@ -1983,7 +22433,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/floating-point-hex-parser": "1.9.0", @@ -1997,7 +22446,6 @@ "version": "1.9.0", "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/wast-parser": "1.9.0", @@ -2007,14 +22455,12 @@ "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "accepts": { "version": "1.3.8", @@ -2035,7 +22481,8 @@ "version": "5.3.2", "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "8.2.0", @@ -2064,7 +22511,6 @@ "version": "6.12.6", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2076,13 +22522,13 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "requires": {} }, "alphanum-sort": { "version": "1.0.2", @@ -2470,8 +22916,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "argparse": { "version": "1.0.10", @@ -2485,20 +22930,17 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" }, "array-flatten": { "version": "2.1.2", @@ -2544,8 +22986,7 @@ "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmmirror.com/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" }, "array.prototype.flatmap": { "version": "1.3.0", @@ -2580,7 +23021,6 @@ "version": "5.4.1", "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz", "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -2591,8 +23031,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -2600,7 +23039,6 @@ "version": "1.5.0", "resolved": "https://registry.npmmirror.com/assert/-/assert-1.5.0.tgz", "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, "requires": { "object-assign": "^4.1.1", "util": "0.10.3" @@ -2609,14 +23047,12 @@ "inherits": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", - "dev": true + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" }, "util": { "version": "0.10.3", "resolved": "https://registry.npmmirror.com/util/-/util-0.10.3.tgz", "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", - "dev": true, "requires": { "inherits": "2.0.1" } @@ -2626,8 +23062,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==" }, "astral-regex": { "version": "2.0.0", @@ -2648,7 +23083,7 @@ "version": "1.0.3", "resolved": "https://registry.npmmirror.com/async-each/-/async-each-1.0.3.tgz", "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true + "devOptional": true }, "async-limiter": { "version": "1.0.1", @@ -2664,8 +23099,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "axios": { "version": "0.21.4", @@ -2834,14 +23268,12 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", "resolved": "https://registry.npmmirror.com/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -2856,7 +23288,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -2865,7 +23296,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2874,7 +23304,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2883,7 +23312,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -2895,8 +23323,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "batch": { "version": "0.6.1", @@ -2913,14 +23340,12 @@ "version": "2.2.0", "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "optional": true }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, "optional": true, "requires": { "file-uri-to-path": "1.0.0" @@ -2929,14 +23354,12 @@ "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "body-parser": { "version": "1.20.0", @@ -3022,7 +23445,6 @@ "version": "1.1.11", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3032,7 +23454,7 @@ "version": "3.0.2", "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "requires": { "fill-range": "^7.0.1" } @@ -3040,14 +23462,12 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -3061,7 +23481,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, "requires": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -3072,7 +23491,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -3084,7 +23502,6 @@ "version": "4.1.0", "resolved": "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz", "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, "requires": { "bn.js": "^5.0.0", "randombytes": "^2.0.1" @@ -3094,7 +23511,6 @@ "version": "4.2.1", "resolved": "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.1.tgz", "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, "requires": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", @@ -3111,7 +23527,6 @@ "version": "3.6.0", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3121,8 +23536,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -3130,7 +23544,6 @@ "version": "0.2.0", "resolved": "https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, "requires": { "pako": "~1.0.5" } @@ -3150,7 +23563,6 @@ "version": "4.9.2", "resolved": "https://registry.npmmirror.com/buffer/-/buffer-4.9.2.tgz", "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -3160,8 +23572,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" } } }, @@ -3179,14 +23590,12 @@ "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dev": true + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" }, "bytes": { "version": "3.0.0", @@ -3198,7 +23607,6 @@ "version": "12.0.4", "resolved": "https://registry.npmmirror.com/cacache/-/cacache-12.0.4.tgz", "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, "requires": { "bluebird": "^3.5.5", "chownr": "^1.1.1", @@ -3221,7 +23629,6 @@ "version": "5.1.1", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "requires": { "yallist": "^3.0.2" } @@ -3229,8 +23636,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -3238,7 +23644,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -3358,7 +23763,6 @@ "version": "3.5.3", "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "optional": true, "requires": { "anymatch": "~3.1.2", @@ -3374,14 +23778,12 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "ci-info": { "version": "2.0.0", @@ -3393,7 +23795,6 @@ "version": "1.0.4", "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3403,7 +23804,6 @@ "version": "0.3.6", "resolved": "https://registry.npmmirror.com/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -3415,7 +23815,6 @@ "version": "0.2.5", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -3600,7 +23999,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -3664,8 +24062,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, "compare-versions": { "version": "3.6.0", @@ -3684,8 +24081,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "component-indexof": { "version": "0.0.3", @@ -3741,14 +24137,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -3765,14 +24159,12 @@ "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dev": true + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==" }, "content-disposition": { "version": "0.5.4", @@ -3830,7 +24222,6 @@ "version": "1.0.5", "resolved": "https://registry.npmmirror.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, "requires": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", @@ -3843,8 +24234,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmmirror.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==" }, "copy-to-clipboard": { "version": "3.3.2", @@ -4078,8 +24468,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cosmiconfig": { "version": "7.0.1", @@ -4097,7 +24486,6 @@ "version": "4.0.4", "resolved": "https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz", "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, "requires": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" @@ -4106,8 +24494,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -4115,7 +24502,6 @@ "version": "1.2.0", "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -4128,7 +24514,6 @@ "version": "1.1.7", "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -4171,7 +24556,6 @@ "version": "3.12.0", "resolved": "https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz", "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, "requires": { "browserify-cipher": "^1.0.0", "browserify-sign": "^4.0.0", @@ -4444,8 +24828,7 @@ "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", - "dev": true + "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==" }, "date-fns": { "version": "2.29.3", @@ -4614,7 +24997,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -4624,7 +25006,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -4633,7 +25014,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -4642,7 +25022,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -4708,7 +25087,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/des.js/-/des.js-1.0.1.tgz", "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -4742,7 +25120,6 @@ "version": "5.0.3", "resolved": "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", @@ -4752,8 +25129,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -4850,8 +25226,7 @@ "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, "domelementtype": { "version": "2.3.0", @@ -4926,7 +25301,6 @@ "version": "3.7.1", "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-3.7.1.tgz", "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -4974,7 +25348,6 @@ "version": "6.5.4", "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -4988,8 +25361,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -5022,7 +25394,6 @@ "version": "1.4.4", "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -5031,7 +25402,6 @@ "version": "4.5.0", "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "memory-fs": "^0.5.0", @@ -5064,7 +25434,6 @@ "version": "0.1.8", "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, "requires": { "prr": "~1.0.1" } @@ -5313,7 +25682,8 @@ "version": "8.3.0", "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-prettier": { "version": "3.4.0", @@ -5358,7 +25728,8 @@ "version": "4.2.0", "resolved": "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -5445,7 +25816,6 @@ "version": "4.3.0", "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "requires": { "estraverse": "^5.2.0" }, @@ -5453,16 +25823,14 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" } } }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, "esutils": { "version": "2.0.3", @@ -5484,8 +25852,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "eventsource": { "version": "2.0.2", @@ -5497,7 +25864,6 @@ "version": "1.0.3", "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -5537,7 +25903,6 @@ "version": "2.1.4", "resolved": "https://registry.npmmirror.com/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -5552,7 +25917,6 @@ "version": "2.6.9", "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -5561,7 +25925,6 @@ "version": "0.2.5", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -5570,7 +25933,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -5578,8 +25940,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -5679,7 +26040,6 @@ "version": "3.0.2", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -5689,7 +26049,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -5700,7 +26059,6 @@ "version": "2.0.4", "resolved": "https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -5716,7 +26074,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -5725,7 +26082,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -5734,7 +26090,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -5743,7 +26098,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -5752,7 +26106,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -5788,8 +26141,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -5852,8 +26204,7 @@ "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmmirror.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" }, "file-entry-cache": { "version": "6.0.1", @@ -5891,14 +26242,13 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, "optional": true }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -6124,7 +26474,6 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz", "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" @@ -6138,8 +26487,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" }, "format": { "version": "0.2.2", @@ -6156,7 +26504,6 @@ "version": "0.2.1", "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -6171,7 +26518,6 @@ "version": "2.3.0", "resolved": "https://registry.npmmirror.com/from2/-/from2-2.3.0.tgz", "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -6181,7 +26527,6 @@ "version": "1.0.10", "resolved": "https://registry.npmmirror.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", @@ -6192,14 +26537,12 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "function-bind": { @@ -6277,14 +26620,12 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmmirror.com/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6298,7 +26639,7 @@ "version": "5.1.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "requires": { "is-glob": "^4.0.1" } @@ -6380,8 +26721,7 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "gzip-size": { "version": "6.0.0", @@ -6561,7 +26901,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/has-value/-/has-value-1.0.0.tgz", "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -6572,7 +26911,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/has-values/-/has-values-1.0.0.tgz", "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -6581,14 +26919,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -6597,7 +26933,6 @@ "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -6608,7 +26943,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -6619,7 +26953,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -6630,7 +26963,6 @@ "version": "3.6.0", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6640,8 +26972,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -6649,7 +26980,6 @@ "version": "1.1.7", "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -6693,7 +27023,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -6950,8 +27279,7 @@ "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "dev": true + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" }, "human-signals": { "version": "1.1.1", @@ -7063,14 +27391,12 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "iferr": { "version": "0.1.5", "resolved": "https://registry.npmmirror.com/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", - "dev": true + "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==" }, "ignore": { "version": "5.2.0", @@ -7166,8 +27492,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, "indent-string": { "version": "4.0.0", @@ -7184,14 +27509,12 @@ "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7200,8 +27523,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -7271,7 +27593,6 @@ "version": "0.1.6", "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -7279,14 +27600,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -7334,7 +27653,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "optional": true, "requires": { "binary-extensions": "^2.0.0" @@ -7380,7 +27698,6 @@ "version": "0.1.4", "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -7388,14 +27705,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -7419,7 +27734,6 @@ "version": "0.1.6", "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -7429,8 +27743,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -7443,14 +27756,13 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "devOptional": true }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -7462,7 +27774,7 @@ "version": "4.0.3", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "requires": { "is-extglob": "^2.1.1" } @@ -7486,7 +27798,7 @@ "version": "7.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "devOptional": true }, "is-number-object": { "version": "1.0.7", @@ -7530,7 +27842,6 @@ "version": "2.0.4", "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -7608,14 +27919,12 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==" }, "isarray": { "version": "0.0.1", @@ -7631,8 +27940,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "isomorphic-fetch": { "version": "2.2.1", @@ -7671,8 +27979,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -7682,8 +27989,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7738,8 +28044,7 @@ "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knowdesign": { "version": "1.3.7", @@ -8046,8 +28351,7 @@ "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" }, "loader-utils": { "version": "1.4.0", @@ -8306,14 +28610,12 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmmirror.com/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==" }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -8327,7 +28629,6 @@ "version": "1.3.5", "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -8355,7 +28656,6 @@ "version": "0.5.0", "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.5.0.tgz", "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" @@ -8404,7 +28704,6 @@ "version": "4.0.1", "resolved": "https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz", "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" @@ -8413,8 +28712,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -8490,20 +28788,17 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8517,7 +28812,6 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/mississippi/-/mississippi-3.0.0.tgz", "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, "requires": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", @@ -8535,7 +28829,6 @@ "version": "1.3.2", "resolved": "https://registry.npmmirror.com/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -8545,7 +28838,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -8556,7 +28848,6 @@ "version": "0.5.6", "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, "requires": { "minimist": "^1.2.6" } @@ -8570,7 +28861,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/move-concurrently/-/move-concurrently-1.0.1.tgz", "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", - "dev": true, "requires": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", @@ -8610,14 +28900,12 @@ "version": "2.16.0", "resolved": "https://registry.npmmirror.com/nan/-/nan-2.16.0.tgz", "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", - "dev": true, "optional": true }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -8654,8 +28942,7 @@ "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "nice-try": { "version": "1.0.5", @@ -8691,7 +28978,6 @@ "version": "2.2.1", "resolved": "https://registry.npmmirror.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz", "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, "requires": { "assert": "^1.1.1", "browserify-zlib": "^0.2.0", @@ -8721,8 +29007,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" } } }, @@ -8779,7 +29064,6 @@ "version": "0.1.0", "resolved": "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -8790,7 +29074,6 @@ "version": "0.2.5", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -8798,14 +29081,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -8836,7 +29117,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, "requires": { "isobject": "^3.0.0" } @@ -8889,7 +29169,6 @@ "version": "1.3.0", "resolved": "https://registry.npmmirror.com/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -8938,7 +29217,6 @@ "version": "1.4.0", "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -8999,8 +29277,7 @@ "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==" }, "p-finally": { "version": "1.0.0", @@ -9044,20 +29321,17 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parallel-transform": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/parallel-transform/-/parallel-transform-1.2.0.tgz", "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, "requires": { "cyclist": "^1.0.1", "inherits": "^2.0.3", @@ -9085,7 +29359,6 @@ "version": "5.1.6", "resolved": "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.6.tgz", "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, "requires": { "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", @@ -9129,20 +29402,18 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmmirror.com/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==" }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/path-dirname/-/path-dirname-1.0.2.tgz", "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true + "devOptional": true }, "path-exists": { "version": "4.0.0", @@ -9153,8 +29424,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-is-inside": { "version": "1.0.2", @@ -9190,7 +29460,6 @@ "version": "3.1.2", "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -9217,8 +29486,7 @@ "pify": { "version": "4.0.1", "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pinkie": { "version": "2.0.4", @@ -9322,8 +29590,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==" }, "postcss": { "version": "7.0.39", @@ -9771,14 +30038,12 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -9841,8 +30106,7 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" }, "prop-types": { "version": "15.8.1", @@ -9875,14 +30139,12 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz", "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -9895,8 +30157,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -9904,7 +30165,6 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -9914,7 +30174,6 @@ "version": "1.5.1", "resolved": "https://registry.npmmirror.com/pumpify/-/pumpify-1.5.1.tgz", "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, "requires": { "duplexify": "^3.6.0", "inherits": "^2.0.3", @@ -9925,7 +30184,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/pump/-/pump-2.0.1.tgz", "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -9936,8 +30194,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { "version": "1.5.1", @@ -9967,14 +30224,12 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "dev": true + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==" }, "querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==" }, "querystringify": { "version": "2.2.0", @@ -10000,7 +30255,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -10009,7 +30263,6 @@ "version": "1.0.4", "resolved": "https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -10886,7 +31139,8 @@ "react-codemirror2": { "version": "7.2.1", "resolved": "https://registry.npmmirror.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz", - "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==" + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "requires": {} }, "react-color": { "version": "2.18.1", @@ -11032,7 +31286,8 @@ "react-fast-marquee": { "version": "1.3.5", "resolved": "https://registry.npmmirror.com/react-fast-marquee/-/react-fast-marquee-1.3.5.tgz", - "integrity": "sha512-eOqLoz4iVVBvi2wN/web8hd2XX9y2Z6CYR7g++7nTVHlTOXBtqyARQJ9rYNpbp179hAzloMx0yBFAo8LpNYmKQ==" + "integrity": "sha512-eOqLoz4iVVBvi2wN/web8hd2XX9y2Z6CYR7g++7nTVHlTOXBtqyARQJ9rYNpbp179hAzloMx0yBFAo8LpNYmKQ==", + "requires": {} }, "react-floater": { "version": "0.7.6", @@ -11058,7 +31313,8 @@ "react-freeze": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/react-freeze/-/react-freeze-1.0.3.tgz", - "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==" + "integrity": "sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==", + "requires": {} }, "react-github-button": { "version": "0.1.11", @@ -11441,7 +31697,6 @@ "version": "2.3.7", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11455,8 +31710,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" } } }, @@ -11464,7 +31718,6 @@ "version": "3.6.0", "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "optional": true, "requires": { "picomatch": "^2.2.1" @@ -11550,7 +31803,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -11614,7 +31866,7 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true + "devOptional": true }, "renderkid": { "version": "2.0.7", @@ -11631,14 +31883,12 @@ "repeat-element": { "version": "1.1.4", "resolved": "https://registry.npmmirror.com/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" }, "require-directory": { "version": "2.1.1", @@ -11732,8 +31982,7 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "dev": true + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==" }, "restore-cursor": { "version": "3.1.0", @@ -11748,8 +31997,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "retry": { "version": "0.12.0", @@ -11785,7 +32033,6 @@ "version": "2.7.1", "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -11794,7 +32041,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -11813,7 +32059,6 @@ "version": "1.0.3", "resolved": "https://registry.npmmirror.com/run-queue/-/run-queue-1.0.3.tgz", "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", - "dev": true, "requires": { "aproba": "^1.1.1" } @@ -11836,7 +32081,6 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -11972,7 +32216,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz", "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -12067,7 +32310,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -12079,7 +32321,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12101,7 +32342,6 @@ "version": "2.4.11", "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -12173,7 +32413,8 @@ "single-spa-react": { "version": "2.14.0", "resolved": "https://registry.npmmirror.com/single-spa-react/-/single-spa-react-2.14.0.tgz", - "integrity": "sha512-KQ2/y7/JBIquK0WUiwb1/Y7f4qTZITNotw+JwNPesj0WKeCi91u0LOZe2ps56QMJbyB4UrA5IzMBwbYWDr1pIw==" + "integrity": "sha512-KQ2/y7/JBIquK0WUiwb1/Y7f4qTZITNotw+JwNPesj0WKeCi91u0LOZe2ps56QMJbyB4UrA5IzMBwbYWDr1pIw==", + "requires": {} }, "sirv": { "version": "1.0.19", @@ -12237,7 +32478,6 @@ "version": "0.8.2", "resolved": "https://registry.npmmirror.com/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -12253,7 +32493,6 @@ "version": "2.6.9", "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -12262,7 +32501,6 @@ "version": "0.2.5", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -12271,7 +32509,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12279,14 +32516,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" } } }, @@ -12294,7 +32529,6 @@ "version": "2.1.1", "resolved": "https://registry.npmmirror.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -12305,7 +32539,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-1.0.0.tgz", "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -12314,7 +32547,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -12323,7 +32555,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -12332,7 +32563,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -12345,7 +32575,6 @@ "version": "3.0.1", "resolved": "https://registry.npmmirror.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" }, @@ -12353,14 +32582,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -12430,8 +32657,7 @@ "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" }, "source-map": { "version": "0.6.1", @@ -12442,7 +32668,6 @@ "version": "0.5.3", "resolved": "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, "requires": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0", @@ -12463,8 +32688,7 @@ "source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, "space-separated-tokens": { "version": "1.1.5", @@ -12520,7 +32744,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -12535,7 +32758,6 @@ "version": "6.0.2", "resolved": "https://registry.npmmirror.com/ssri/-/ssri-6.0.2.tgz", "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, "requires": { "figgy-pudding": "^3.5.1" } @@ -12556,7 +32778,6 @@ "version": "0.1.2", "resolved": "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -12566,7 +32787,6 @@ "version": "0.2.5", "resolved": "https://registry.npmmirror.com/define-property/-/define-property-0.2.5.tgz", "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -12583,7 +32803,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-2.0.2.tgz", "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" @@ -12593,7 +32812,6 @@ "version": "1.2.3", "resolved": "https://registry.npmmirror.com/stream-each/-/stream-each-1.2.3.tgz", "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" @@ -12603,7 +32821,6 @@ "version": "2.8.3", "resolved": "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.3.tgz", "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, "requires": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.1", @@ -12615,14 +32832,21 @@ "stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz", @@ -12698,15 +32922,6 @@ "es-abstract": "^1.19.5" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/stringify-object/-/stringify-object-3.3.0.tgz", @@ -12939,7 +33154,6 @@ "version": "1.4.5", "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", @@ -12956,7 +33170,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz", "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -12967,7 +33180,6 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -12976,7 +33188,6 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -12986,7 +33197,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, "requires": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -12996,7 +33206,6 @@ "version": "2.3.0", "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -13005,7 +33214,6 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -13013,14 +33221,12 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz", "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, "requires": { "find-up": "^3.0.0" } @@ -13029,7 +33235,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, "requires": { "ajv": "^6.1.0", "ajv-errors": "^1.0.0", @@ -13039,8 +33244,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -13065,7 +33269,6 @@ "version": "2.0.5", "resolved": "https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -13081,7 +33284,6 @@ "version": "2.0.12", "resolved": "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz", "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, "requires": { "setimmediate": "^1.0.4" } @@ -13110,8 +33312,7 @@ "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", - "dev": true + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==" }, "to-fast-properties": { "version": "2.0.0", @@ -13122,7 +33323,6 @@ "version": "0.3.0", "resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -13130,14 +33330,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -13148,7 +33346,6 @@ "version": "3.0.2", "resolved": "https://registry.npmmirror.com/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -13160,7 +33357,7 @@ "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "requires": { "is-number": "^7.0.0" } @@ -13306,8 +33503,7 @@ "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", - "dev": true + "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==" }, "type-check": { "version": "0.4.0", @@ -13337,8 +33533,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typescript": { "version": "4.6.4", @@ -13390,7 +33585,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -13414,7 +33608,6 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, "requires": { "unique-slug": "^2.0.0" } @@ -13423,7 +33616,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, "requires": { "imurmurhash": "^0.1.4" } @@ -13444,7 +33636,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -13454,7 +33645,6 @@ "version": "0.3.1", "resolved": "https://registry.npmmirror.com/has-value/-/has-value-0.3.1.tgz", "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -13465,7 +33655,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/isobject/-/isobject-2.1.0.tgz", "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -13475,14 +33664,12 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==" }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" } } }, @@ -13490,7 +33677,7 @@ "version": "1.2.0", "resolved": "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true + "devOptional": true }, "update-browserslist-db": { "version": "1.0.9", @@ -13505,7 +33692,6 @@ "version": "4.4.1", "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -13513,14 +33699,12 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "dev": true + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==" }, "url": { "version": "0.11.0", "resolved": "https://registry.npmmirror.com/url/-/url-0.11.0.tgz", "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", - "dev": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -13529,8 +33713,7 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", - "dev": true + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" } } }, @@ -13547,14 +33730,12 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "util": { "version": "0.11.1", "resolved": "https://registry.npmmirror.com/util/-/util-0.11.1.tgz", "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, "requires": { "inherits": "2.0.3" }, @@ -13562,16 +33743,14 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" } } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "util.promisify": { "version": "1.0.0", @@ -13619,8 +33798,7 @@ "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, "warning": { "version": "4.0.3", @@ -13634,7 +33812,6 @@ "version": "1.7.5", "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-1.7.5.tgz", "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dev": true, "requires": { "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", @@ -13646,7 +33823,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, "optional": true, "requires": { "chokidar": "^2.1.8" @@ -13656,7 +33832,6 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, "optional": true, "requires": { "micromatch": "^3.1.4", @@ -13667,7 +33842,6 @@ "version": "2.1.1", "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, "optional": true, "requires": { "remove-trailing-separator": "^1.0.1" @@ -13679,14 +33853,12 @@ "version": "1.13.1", "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, "optional": true }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "optional": true, "requires": { "arr-flatten": "^1.1.0", @@ -13705,7 +33877,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "optional": true, "requires": { "is-extendable": "^0.1.0" @@ -13717,7 +33888,6 @@ "version": "2.1.8", "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-2.1.8.tgz", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, "optional": true, "requires": { "anymatch": "^2.0.0", @@ -13738,7 +33908,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, "optional": true, "requires": { "extend-shallow": "^2.0.1", @@ -13751,7 +33920,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "optional": true, "requires": { "is-extendable": "^0.1.0" @@ -13763,7 +33931,6 @@ "version": "1.2.13", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, "optional": true, "requires": { "bindings": "^1.5.0", @@ -13774,7 +33941,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-3.1.0.tgz", "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, "optional": true, "requires": { "is-glob": "^3.1.0", @@ -13785,7 +33951,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-3.1.0.tgz", "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, "optional": true, "requires": { "is-extglob": "^2.1.0" @@ -13797,7 +33962,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, "optional": true, "requires": { "binary-extensions": "^1.0.0" @@ -13807,14 +33971,12 @@ "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, "optional": true }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, "optional": true, "requires": { "kind-of": "^3.0.2" @@ -13824,7 +33986,6 @@ "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "optional": true, "requires": { "is-buffer": "^1.1.5" @@ -13836,7 +33997,6 @@ "version": "3.1.10", "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, "optional": true, "requires": { "arr-diff": "^4.0.0", @@ -13858,7 +34018,6 @@ "version": "2.2.1", "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-2.2.1.tgz", "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, "optional": true, "requires": { "graceful-fs": "^4.1.11", @@ -13870,7 +34029,6 @@ "version": "2.1.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, "optional": true, "requires": { "is-number": "^3.0.0", @@ -13892,7 +34050,6 @@ "version": "4.46.0", "resolved": "https://registry.npmmirror.com/webpack/-/webpack-4.46.0.tgz", "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", @@ -13922,14 +34079,12 @@ "acorn": { "version": "6.4.2", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmmirror.com/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -13947,7 +34102,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -13958,7 +34112,6 @@ "version": "4.0.3", "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-4.0.3.tgz", "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" @@ -13968,7 +34121,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -13980,7 +34132,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -13990,14 +34141,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -14006,7 +34155,6 @@ "version": "3.2.2", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -14017,7 +34165,6 @@ "version": "0.4.1", "resolved": "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", - "dev": true, "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" @@ -14027,7 +34174,6 @@ "version": "3.1.10", "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -14048,7 +34194,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, "requires": { "ajv": "^6.1.0", "ajv-errors": "^1.0.0", @@ -14059,7 +34204,6 @@ "version": "2.1.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -14566,7 +34710,6 @@ "version": "1.4.3", "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz", "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" @@ -14637,7 +34780,6 @@ "version": "1.7.0", "resolved": "https://registry.npmmirror.com/worker-farm/-/worker-farm-1.7.0.tgz", "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, "requires": { "errno": "~0.1.7" } @@ -14697,8 +34839,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "2.4.3", @@ -14745,7 +34886,8 @@ "ws": { "version": "7.5.9", "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} }, "xtend": { "version": "4.0.2", @@ -14755,8 +34897,7 @@ "y18n": { "version": "4.0.3", "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yallist": { "version": "4.0.0", diff --git a/km-console/packages/layout-clusters-fe/package.json b/km-console/packages/layout-clusters-fe/package.json index af1faf35..62fe6f9c 100644 --- a/km-console/packages/layout-clusters-fe/package.json +++ b/km-console/packages/layout-clusters-fe/package.json @@ -50,7 +50,7 @@ "crypto-js": "^4.1.1", "dotenv": "^16.0.1", "html-webpack-plugin": "^4.0.0", - "knowdesign": "^1.3.7", + "knowdesign": "1.3.7", "lodash": "^4.17.21", "moment": "^2.24.0", "react": "16.12.0", diff --git a/km-console/packages/layout-clusters-fe/src/api/index.ts b/km-console/packages/layout-clusters-fe/src/api/index.ts index 0dcc912a..4d185ea9 100755 --- a/km-console/packages/layout-clusters-fe/src/api/index.ts +++ b/km-console/packages/layout-clusters-fe/src/api/index.ts @@ -15,6 +15,8 @@ export enum MetricType { Partition = 104, Replication = 105, Zookeeper = 110, + Connect = 120, + Connectors = 121, Controls = 901, } @@ -163,7 +165,7 @@ const api = { getApi(`/clusters/${clusterPhyId}/${MetricType[type].toLowerCase()}s-metadata`), // 集群节点信息 getDashboardMetricList: (clusterPhyId: string, type: MetricType) => getApi(`/clusters/${clusterPhyId}/types/${type}/user-metric-config`), // 默认选中的指标项 getDashboardMetricChartData: (clusterPhyId: string, type: MetricType) => - getApi(`/clusters/${clusterPhyId}/${MetricType[type].toLowerCase()}-metrics`), // 图表数据Z + getApi(`/clusters/${clusterPhyId}/${MetricType[type].toLowerCase()}-metrics`), // 图表数据 // ! Jobs 集群任务相关接口 getJobsList: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/jobs-overview`), @@ -211,6 +213,44 @@ const api = { 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`), + + // Connector 接口 + getConnectState: (clusterPhyId: string) => getApi(`/kafka-clusters/${clusterPhyId}/connect-state`), + getConnectorsList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/connectors-overview`), + // Connector 详情 + getConnectDetailMetricPoints: (connectorName: number | string, connectClusterId: number | string) => + getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/latest-metrics`), + getConnectDetailTasks: (connectorName: number | string, connectClusterId: number | string) => + getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/tasks`), + getConnectDetailState: (connectorName: number | string, connectClusterId: number | string) => + getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/state`), + optionTasks: () => getApi(`/kafka-connect/tasks`), + // Workers 接口 + getWorkersList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/workers-overview`), + // Connector + getConnectClusters: (clusterPhyId: string) => getApi(`/kafka-clusters/${clusterPhyId}/connect-clusters-basic`), + getConnectClusterMetrics: (clusterPhyId: string) => getApi(`/kafka-clusters/${clusterPhyId}/connect-cluster-metrics`), + getConnectors: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/connectors-basic`), + getConnectorMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/connectors-metrics`), + getConnectorPlugins: (connectClusterId: number) => getApi(`/kafka-connect/clusters/${connectClusterId}/connector-plugins`), + getConnectorPluginConfig: (connectClusterId: number, pluginName: string) => + getApi(`/kafka-connect/clusters/${connectClusterId}/connector-plugins/${pluginName}/config`), + getCurPluginConfig: (connectClusterId: number, connectorName: string) => + getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/config`), + isConnectorExist: (connectClusterId: number, connectorName: string) => + getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/basic-combine-exist`), + validateConnectorConfig: getApi('/kafka-connect/connectors-config/validate'), + // Connector 操作接口 新增、暂停、重启、删除 + connectorsOperates: getApi('/kafka-connect/connectors'), + // 修改 Connector 配置 + updateConnectorConfig: getApi('/kafka-connect/connectors-config'), + // Cluster首页修改Connect集群 + batchConnectClusters: getApi(`/kafka-connect/batch-connect-clusters`), + // Cluster首页删除Connect集群 + deleteConnectClusters: getApi(`/kafka-connect/connect-clusters`), + + getConnectClusterBasicExit: (clusterPhyId: string, clusterPhyName: string) => + getApi(`/kafka-clusters/${clusterPhyId}/connect-clusters/${clusterPhyName}/basic-combine-exist`), }; export default api; diff --git a/km-console/packages/layout-clusters-fe/src/assets/no-data.png b/km-console/packages/layout-clusters-fe/src/assets/no-data.png new file mode 100644 index 0000000000000000000000000000000000000000..2dac47abca03a1e32ec652eaf017acb144c9ac74 GIT binary patch literal 56362 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 { }); }, [routeParams.clusterId]); - return ; + return ; }; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectCard.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectCard.tsx new file mode 100644 index 00000000..9992ccbf --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectCard.tsx @@ -0,0 +1,119 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import CardBar, { healthDataProps } from './index'; +import { Tooltip, Utils } from 'knowdesign'; +import api from '@src/api'; +import { HealthStateEnum } from '../HealthState'; +import { InfoCircleOutlined } from '@ant-design/icons'; + +interface ConnectState { + connectClusterCount: number; + workerCount: number; + aliveConnectorCount: number; + aliveTaskCount: number; + healthCheckPassed: number; + healthCheckTotal: number; + healthState: number; + totalConnectorCount: string; + totalTaskCount: number; + totalServerCount: number; +} + +const getVal = (val: string | number | undefined | null) => { + return val === undefined || val === null || val === '' ? '0' : val; +}; + +const ConnectCard = ({ state }: { state?: boolean }) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [loading, setLoading] = useState(false); + const [cardData, setCardData] = useState([]); + const [healthData, setHealthData] = useState({ + state: HealthStateEnum.UNKNOWN, + passed: 0, + total: 0, + }); + + const getHealthData = () => { + return Utils.post(api.getMetricPointsLatest(Number(clusterId)), [ + 'HealthCheckPassed_Connector', + 'HealthCheckTotal_Connector', + 'HealthState_Connector', + ]).then((data: any) => { + setHealthData({ + state: data?.metrics?.['HealthState_Connector'], + passed: data?.metrics?.['HealthCheckPassed_Connector'] || 0, + total: data?.metrics?.['HealthCheckTotal_Connector'] || 0, + }); + }); + }; + + const getCardInfo = () => { + return Utils.request(api.getConnectState(clusterId)).then((res: ConnectState) => { + const { connectClusterCount, aliveConnectorCount, aliveTaskCount, totalConnectorCount, totalTaskCount, workerCount } = res || {}; + const cardMap = [ + { + title: 'Connect集群数', + value: getVal(connectClusterCount), + customStyle: { + // 自定义cardbar样式 + marginLeft: 0, + }, + }, + { + title: 'Workers', + value: getVal(workerCount), + }, + { + title() { + return ( +
+ Connectors + + + +
+ ); + }, + value() { + return ( + + {getVal(aliveConnectorCount)}/{getVal(totalConnectorCount)} + + ); + }, + }, + { + title() { + return ( +
+ Tasks + + + +
+ ); + }, + value() { + return ( + + {getVal(aliveTaskCount)}/{getVal(totalTaskCount)} + + ); + }, + }, + ]; + setCardData(cardMap); + }); + }; + useEffect(() => { + setLoading(true); + Promise.all([getHealthData(), getCardInfo()]).finally(() => { + setLoading(false); + }); + }, [clusterId, state]); + return ; +}; + +export default ConnectCard; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectDetailCard.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectDetailCard.tsx new file mode 100644 index 00000000..f3f9f545 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/ConnectDetailCard.tsx @@ -0,0 +1,122 @@ +/* eslint-disable react/display-name */ +import React, { useState, useEffect } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; +import CardBar from '@src/components/CardBar'; +import { healthDataProps } from '.'; +import { Tooltip, Utils } from 'knowdesign'; +import Api from '@src/api'; +import { hashDataParse } from '@src/constants/common'; +import { HealthStateEnum } from '../HealthState'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { stateEnum } from '@src/pages/Connect/config'; +const getVal = (val: string | number | undefined | null) => { + return val === undefined || val === null || val === '' ? '0' : val; +}; + +const ConnectDetailCard = (props: { record: any }) => { + const { record } = props; + const urlParams = useParams<{ clusterId: string; brokerId: string }>(); + const urlLocation = useLocation(); + const [loading, setLoading] = useState(false); + const [cardData, setCardData] = useState([]); + const [healthData, setHealthData] = useState({ + state: HealthStateEnum.UNKNOWN, + passed: 0, + total: 0, + }); + + const getHealthData = () => { + return Utils.post(Api.getConnectDetailMetricPoints(record.connectorName, record?.connectClusterId), [ + '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.getConnectDetailState(record.connectorName, record?.connectClusterId)).then((res: any) => { + const { type, aliveTaskCount, state, totalTaskCount, totalWorkerCount } = res || {}; + const cordRightMap = [ + { + title: 'Type', + value: () => { + return ( + <> + { + + {Utils.firstCharUppercase(type) || '-'} + + } + + ); + }, + }, + { + title: 'Status', + // value: Utils.firstCharUppercase(state) || '-', + value: () => { + return ( + <> + { + + {Utils.firstCharUppercase(state) || '-'} + + } + + ); + }, + }, + + { + title() { + return ( +
+ Tasks + + + +
+ ); + }, + value() { + return ( + + {getVal(aliveTaskCount)}/{getVal(totalTaskCount)} + + ); + }, + }, + { + title: 'Workers', + value: getVal(totalWorkerCount), + }, + ]; + setCardData(cordRightMap); + }); + }; + + useEffect(() => { + setLoading(true); + Promise.all([getHealthData(), getCardInfo()]).finally(() => { + setLoading(false); + }); + }, [record]); + return ( + + ); +}; + +export default ConnectDetailCard; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicHealthCheck.tsx index 38da5b03..cb0dbf28 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicHealthCheck.tsx @@ -66,5 +66,5 @@ export default () => { }); }); }, []); - return ; + return ; }; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index e18f8530..472cafa6 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -12,7 +12,8 @@ display: flex; align-items: center; .card-bar-health { - width: 240px; + // width: 240px; // 去掉固定宽度自适应 + margin-right: 10px; height: 70px; display: flex; align-items: center; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx index 51fc9057..f35de692 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx @@ -18,7 +18,7 @@ export interface CardBarProps { cardColumns?: any[]; healthData?: healthDataProps; showCardBg?: boolean; - scene: 'topic' | 'broker' | 'group' | 'zookeeper'; + scene: 'topics' | 'brokers' | 'topic' | 'broker' | 'group' | 'zookeeper' | 'connect' | 'connector'; record?: any; loading?: boolean; needProgress?: boolean; @@ -27,16 +27,26 @@ const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Elemen return typeof v === 'function' ? v(visibleType) : v; }; const sceneCodeMap = { - broker: { + brokers: { code: 1, fieldName: 'brokerId', alias: 'Brokers', }, - topic: { + broker: { + code: 1, + fieldName: 'brokerId', + alias: 'Broker', + }, + topics: { code: 2, fieldName: 'topicName', alias: 'Topics', }, + topic: { + code: 2, + fieldName: 'topicName', + alias: 'Topic', + }, group: { code: 3, fieldName: 'groupName', @@ -47,6 +57,16 @@ const sceneCodeMap = { fieldName: 'zookeeperId', alias: 'Zookeeper', }, + connect: { + code: 5, + fieldName: 'connectClusterId', + alias: 'Connect', + }, + connector: { + code: 6, + fieldName: 'connectorName', + alias: 'Connector', + }, }; const CardColumnsItem: any = (cardItem: any) => { const { cardColumnsItemData, showCardBg } = cardItem; @@ -87,12 +107,17 @@ const CardBar = (props: CardBarProps) => { useEffect(() => { const sceneObj = sceneCodeMap[scene]; const path = record - ? api.getResourceHealthDetail(Number(routeParams.clusterId), sceneObj.code, record[sceneObj.fieldName]) + ? api.getResourceHealthDetail( + scene === 'connector' ? Number(record?.connectClusterId) : Number(routeParams.clusterId), + sceneObj.code, + record[sceneObj.fieldName] + ) : api.getResourceListHealthDetail(Number(routeParams.clusterId)); const promise = record ? Utils.request(path) : Utils.request(path, { - params: { dimensionCode: sceneObj.code }, + method: 'POST', + data: scene === 'connect' ? JSON.parse(JSON.stringify([5, 6])) : JSON.parse(JSON.stringify([sceneObj.code])), }); promise.then((data: any[]) => { setHealthCheckDetailList(data); @@ -102,6 +127,7 @@ const CardBar = (props: CardBarProps) => { { title: '检查项', dataIndex: 'checkConfig', + width: '40%', render(config: any, record: any) { let valueGroup = {}; try { @@ -109,7 +135,12 @@ const CardBar = (props: CardBarProps) => { } catch (e) { // } - return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-'; + return ( + getConfigItemDetailDesc(record.configItem, valueGroup) || + getConfigItemDetailDesc(config.configItem, valueGroup) || + record.configDesc || + '-' + ); }, }, // { @@ -119,13 +150,15 @@ const CardBar = (props: CardBarProps) => { { title: '检查时间', dataIndex: 'updateTime', + width: '30%', render: (value: number) => { - return moment(value).format('YYYY-MM-DD HH:mm:ss'); + return value ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-'; }, }, { title: '检查结果', dataIndex: 'passed', + width: '30%', render(value: boolean, record: any) { const icon = value ? : ; const txt = value ? '已通过' : '未通过'; @@ -145,7 +178,7 @@ const CardBar = (props: CardBarProps) => {
- {!loading && healthData && needProgress && ( + {healthData && needProgress && (
@@ -181,7 +214,7 @@ const CardBar = (props: CardBarProps) => { onClose={(_) => setDetailDrawerVisible(false)} visible={detailDrawerVisible} > - +
); diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx index badae226..628275cd 100644 --- a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx @@ -1,18 +1,25 @@ -import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; +import React, { useState, useEffect, forwardRef, useImperativeHandle, useRef } from 'react'; import { Drawer, Button, Space, Divider, AppContainer, ProTable, Utils } from 'knowdesign'; import { IconFont } from '@knowdesign/icons'; -import { MetricSelect } from './index'; +import { arrayMoveImmutable } from 'array-move'; import './style/indicator-drawer.less'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; +import api, { MetricType } from '@src/api'; +import { MetricInfo, resolveMetricsRank } from '@src/constants/chartConfig'; -interface PropsType extends React.HTMLAttributes { - metricSelect: MetricSelect; +export interface Inode { + name: string; + desc: string; } -interface MetricInfo { - name: string; - unit: string; - desc: string; +export interface MetricSelectProps extends React.HTMLAttributes { + metricType: MetricType; + hide?: boolean; + drawerTitle?: string; + selectedRows: (string | number)[]; + checkboxProps?: (record: any) => { [props: string]: any }; + tableData?: Inode[]; + submitCallback?: (value: (string | number)[]) => Promise; } interface SelectedMetrics { @@ -21,10 +28,14 @@ interface SelectedMetrics { type CategoryData = { category: string; - metrics: MetricInfo[]; + metrics: { + name: string; + unit: string; + desc: string; + }[]; }; -const expandedRowColumns = [ +export const expandedRowColumns = [ { title: '指标名称', dataIndex: 'name', @@ -44,16 +55,7 @@ const expandedRowColumns = [ const ExpandedRow = ({ metrics, category, selectedMetrics, selectedMetricChange }: any) => { return ( -
+
{ +export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) => { const [global] = AppContainer.useGlobalValue(); const { pathname } = useLocation(); const [confirmLoading, setConfirmLoading] = useState(false); @@ -96,7 +98,11 @@ const MetricSelect = forwardRef(({ metricSelect }: PropsType, ref) => { const formateTableData = () => { const tableData = metricSelect.tableData; const categoryData: { - [category: string]: MetricInfo[]; + [category: string]: { + name: string; + unit: string; + desc: string; + }[]; } = {}; tableData.forEach(({ name, desc }) => { @@ -328,4 +334,106 @@ const MetricSelect = forwardRef(({ metricSelect }: PropsType, ref) => { ); }); -export default MetricSelect; +interface MetricsFilterProps { + metricType: MetricType; + onSelectChange: (list: (string | number)[], rankList: string[]) => void; +} + +const MetricsFilter = forwardRef((props: MetricsFilterProps, ref) => { + const { metricType, onSelectChange } = props; + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [metricsList, setMetricsList] = useState([]); // 指标列表 + const [metricRankList, setMetricRankList] = useState([]); + const [selectedMetricNames, setSelectedMetricNames] = useState<(string | number)[]>(undefined); // 默认选中的指标的列表 + const metricSelectRef = useRef(null); + + // 更新指标 + const setMetricList = (metricDetailDTOList: { metric: string; rank: number; set: boolean }[]) => { + return Utils.request(api.getDashboardMetricList(clusterId, metricType), { + method: 'POST', + data: { + metricDetailDTOList, + }, + }); + }; + + // 图表展示顺序变更 + const rankChange = (oldIndex: number, newIndex: number) => { + const newList = arrayMoveImmutable(metricRankList, oldIndex, newIndex); + setMetricRankList(newList); + setMetricList(newList.map((metric, rank) => ({ metric, rank, set: metricsList.find(({ name }) => metric === name)?.set || false }))); + }; + + // 更新 rank + const updateRank = (metricList: MetricInfo[]) => { + const { list, listInfo, shouldUpdate } = resolveMetricsRank(metricList); + setMetricRankList(list); + if (shouldUpdate) { + setMetricList(listInfo); + } + }; + + // 获取指标列表 + const getMetricList = () => { + Utils.request(api.getDashboardMetricList(clusterId, metricType)).then((res: MetricInfo[] | null) => { + if (!res) return; + const supportMetrics = res.filter((metric) => metric.support); + const selectedMetrics = supportMetrics.filter((metric) => metric.set).map((metric) => metric.name); + updateRank([...supportMetrics]); + setMetricsList(supportMetrics); + setSelectedMetricNames(selectedMetrics); + }); + }; + + // 指标选中项更新回调 + const metricSelectCallback = (newMetricNames: (string | number)[]) => { + const updateMetrics: { metric: string; set: boolean; rank: number }[] = []; + // 需要选中的指标 + newMetricNames.forEach( + (name) => + !selectedMetricNames.includes(name) && + updateMetrics.push({ metric: name as string, set: true, rank: metricsList.find(({ name: metric }) => metric === name)?.rank }) + ); + // 取消选中的指标 + selectedMetricNames.forEach( + (name) => + !newMetricNames.includes(name) && + updateMetrics.push({ metric: name as string, set: false, rank: metricsList.find(({ name: metric }) => metric === name)?.rank }) + ); + + const requestPromise = Object.keys(updateMetrics).length ? setMetricList(updateMetrics) : Promise.resolve(); + requestPromise.then( + () => getMetricList(), + () => getMetricList() + ); + + return requestPromise; + }; + + useEffect(() => { + onSelectChange(selectedMetricNames, metricRankList); + }, [selectedMetricNames, metricRankList]); + + useEffect(() => { + getMetricList(); + }, []); + + useImperativeHandle(ref, () => ({ + rankChange, + open: () => metricSelectRef.current?.open(), + })); + + return ( + + ); +}); + +export default MetricsFilter; diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeScope.tsx b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeScope.tsx deleted file mode 100644 index 80f160c3..00000000 --- a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeScope.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Radio, Input, Popover, Space, Checkbox, Row, Col, Button } from 'knowdesign'; -import { IconFont } from '@knowdesign/icons'; -import { InodeScopeModule } from './index'; -import './style/node-scope.less'; - -interface propsType { - change: Function; - nodeScopeModule: InodeScopeModule; -} - -const OptionsDefault = [ - { - label: 'Top 5', - value: 5, - }, - { - label: 'Top 10', - value: 10, - }, - { - label: 'Top 15', - value: 15, - }, -]; - -const NodeScope = ({ nodeScopeModule, change }: propsType) => { - const { - customScopeList: customList, - scopeName = '', - scopeLabel = '自定义范围', - searchPlaceholder = '输入内容进行搜索', - } = nodeScopeModule; - const [topNum, setTopNum] = useState(5); - const [isTop, setIsTop] = useState(true); - const [audioOptions, setAudioOptions] = useState(OptionsDefault); - const [scopeSearchValue, setScopeSearchValue] = useState(''); - const [inputValue, setInputValue] = useState(null); - const [indeterminate, setIndeterminate] = useState(false); - const [popVisible, setPopVisible] = useState(false); - const [checkAll, setCheckAll] = useState(false); - const [checkedListTemp, setCheckedListTemp] = useState([]); - const [checkedList, setCheckedList] = useState([]); - const [allCheckedList, setAllCheckedList] = useState([]); - - useEffect(() => { - const all = customList?.map((item) => item.value) || []; - setAllCheckedList(all); - }, [customList]); - - useEffect(() => { - if (topNum) { - const timeOption = audioOptions.find((item) => item.value === topNum); - - setInputValue(timeOption?.label); - setCheckedListTemp([]); - setCheckedList([]); - - setPopVisible(false); - } - }, [topNum]); - - useEffect(() => { - setIndeterminate(!!checkedListTemp.length && checkedListTemp.length < allCheckedList.length); - setCheckAll(checkedListTemp?.length === allCheckedList.length); - }, [checkedListTemp]); - - const customSure = () => { - if (checkedListTemp?.length > 0) { - setCheckedList(checkedListTemp); - change(checkedListTemp, false); - setIsTop(false); - setTopNum(null); - setInputValue(`${checkedListTemp?.length}项`); - setPopVisible(false); - } - }; - - const customCancel = () => { - setCheckedListTemp(checkedList); - setPopVisible(false); - }; - - const visibleChange = (visible: any) => { - setCheckedListTemp(checkedList); - setPopVisible(visible); - }; - - const periodtimeChange = (e: any) => { - const topNum = e.target.value; - setTopNum(topNum); - change(topNum, true); - setIsTop(true); - }; - - const onCheckAllChange = (e: any) => { - setCheckedListTemp(e.target.checked ? allCheckedList : []); - setIndeterminate(false); - setCheckAll(e.target.checked); - }; - - const checkChange = (val: any) => { - setCheckedListTemp(val); - // setIndeterminate(!!val.length && val.length < allCheckedList.length); - // setCheckAll(val?.length === allCheckedList.length); - }; - - const clickContent = ( -
- {/* 时间: */} -
-
-
选择 top 范围
- - - {audioOptions.map((item, index) => ( - - {item.label} - - ))} - - -
-
-
{scopeLabel}
-
-
- - 全选 - - } - size="small" - placeholder={searchPlaceholder} - onChange={(e) => setScopeSearchValue(e.target.value)} - /> -
-
- - - {customList - .filter((item) => item.label.includes(scopeSearchValue)) - .map((item) => ( -
- {item.label} - - ))} - - - - -
- - -
- - - - - ); - return ( -
-
{scopeName}筛选:
- - - } - /> - - -
- ); -}; - -export default NodeScope; diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeSelect.tsx b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeSelect.tsx new file mode 100644 index 00000000..0268c32e --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/NodeSelect.tsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect, useRef, PropsWithChildren } from 'react'; +import { Radio, Input, Popover, Space, Checkbox, Row, Col, Button } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import './style/node-scope.less'; + +interface NodeSelectProps { + name?: string; + onChange: (data: any, isTop: boolean) => void; +} + +const TOP_SELECT_OPTIONS = [ + { + label: 'Top 5', + value: 5, + }, + { + label: 'Top 10', + value: 10, + }, + { + label: 'Top 15', + value: 15, + }, +]; + +const NodeSelect = ({ name, onChange, children }: PropsWithChildren) => { + const [topNum, setTopNum] = useState(5); + const [isTop, setIsTop] = useState(true); + const [audioOptions] = useState(TOP_SELECT_OPTIONS); + const [inputValue, setInputValue] = useState(null); + const [popVisible, setPopVisible] = useState(false); + + useEffect(() => { + if (topNum) { + const timeOption = audioOptions.find((item) => item.value === topNum); + + setInputValue(timeOption?.label); + + setPopVisible(false); + } + }, [topNum]); + + const visibleChange = (visible: any) => { + setPopVisible(visible); + }; + + const periodtimeChange = (e: any) => { + const topNum = e.target.value; + setTopNum(topNum); + onChange(topNum, true); + setIsTop(true); + }; + + const clickContent = ( +
+
+
+
选择 top 范围
+ + + {audioOptions.map((item, index) => ( + + {item.label} + + ))} + + +
+
+ {children ? ( + React.Children.map(children, (child) => { + return React.cloneElement(child as React.ReactElement, { + isTop, + visibleChange: visibleChange, + onChange: (data: any, inputValue: string) => { + onChange(data, false); + setIsTop(false); + setTopNum(null); + setInputValue(inputValue); + setPopVisible(false); + }, + }); + }) + ) : ( + <> + )} +
+
+
+ ); + return ( +
+
{name}筛选:
+ + + } + /> + + +
+ ); +}; + +export default NodeSelect; diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/index.tsx b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/index.tsx index af8cdd61..bfff9b4b 100644 --- a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/index.tsx @@ -3,16 +3,8 @@ import { Select, Divider, Button } from 'knowdesign'; import { IconFont } from '@knowdesign/icons'; import moment from 'moment'; import { DRangeTime } from 'knowdesign'; -import MetricSelect from './MetricSelect'; -import NodeScope from './NodeScope'; - +import NodeSelect from './NodeSelect'; import './style/index.less'; -import { MetricType } from 'src/api'; - -export interface Inode { - name: string; - desc: string; -} export interface KsHeaderOptions { rangeTime: [number, number]; @@ -21,18 +13,9 @@ export interface KsHeaderOptions { gridNum?: number; scopeData?: { isTop: boolean; - data: number | number[]; + data: any; }; } -export interface MetricSelect { - metricType: MetricType; - hide?: boolean; - drawerTitle?: string; - selectedRows: (string | number)[]; - checkboxProps?: (record: any) => { [props: string]: any }; - tableData?: Inode[]; - submitCallback?: (value: (string | number)[]) => Promise; -} export interface IfilterData { hostName?: string; @@ -41,25 +24,15 @@ export interface IfilterData { agent?: string; } -export interface IcustomScope { - label: string; - value: string | number; -} - -export interface InodeScopeModule { - customScopeList: IcustomScope[]; - scopeName?: string; - scopeLabel?: string; - searchPlaceholder?: string; - change?: () => void; -} - interface PropsType { - metricSelect?: MetricSelect; hideNodeScope?: boolean; hideGridSelect?: boolean; - nodeScopeModule?: InodeScopeModule; + nodeSelect?: { + name?: string; + customContent?: React.ReactElement; + }; onChange: (options: KsHeaderOptions) => void; + openMetricFilter: () => void; } interface ScopeData { @@ -84,15 +57,12 @@ const GRID_SIZE_OPTIONS = [ ]; const MetricOperateBar = ({ - metricSelect, - nodeScopeModule = { - customScopeList: [], - }, + nodeSelect = {}, hideNodeScope = false, hideGridSelect = false, onChange: onChangeCallback, + openMetricFilter, }: PropsType): JSX.Element => { - const metricSelectRef = useRef(null); const [gridNum, setGridNum] = useState(GRID_SIZE_OPTIONS[1].value); const [rangeTime, setRangeTime] = useState<[number, number]>(() => { const curTimeStamp = moment().valueOf(); @@ -170,20 +140,22 @@ const MetricOperateBar = ({
{/* 节点范围 */} - {!hideNodeScope && } + {!hideNodeScope && ( + + {nodeSelect.customContent} + + )} {/* 分栏 */} {!hideGridSelect && ( } + size="small" + placeholder={searchPlaceholder} + onChange={(e) => setScopeSearchValue(e.target.value)} + /> +
+
+ + + {list + .filter((item) => item.label.includes(scopeSearchValue)) + .map((item) => ( +
+ {item.label} + + ))} + + + + +
+ + +
+ + + ); +}; const DraggableCharts = (props: PropsType): JSX.Element => { const [global] = AppContainer.useGlobalValue(); @@ -35,14 +139,14 @@ const DraggableCharts = (props: PropsType): JSX.Element => { }>(); const [loading, setLoading] = useState(true); const [scopeList, setScopeList] = useState([]); // 节点范围列表 - const [metricsList, setMetricsList] = useState([]); // 指标列表 - const [selectedMetricNames, setSelectedMetricNames] = useState<(string | number)[]>([]); // 默认选中的指标的列表 const [curHeaderOptions, setCurHeaderOptions] = useState(); + const [metricList, setMetricList] = useState<(string | number)[]>([]); const [metricChartData, setMetricChartData] = useState([]); // 指标图表数据列表 const [gridNum, setGridNum] = useState(12); // 图表列布局 - const metricRankList = useRef([]); - const chartDetailRef = useRef(null); const curFetchingTimestamp = useRef(0); + const metricRankList = useRef([]); + const metricFilterRef = useRef(null); + const chartDetailRef = useRef(null); // 获取节点范围列表 const getScopeList = async () => { @@ -61,40 +165,6 @@ const DraggableCharts = (props: PropsType): JSX.Element => { setScopeList(list); }; - // 更新 rank - const updateRank = (metricList: MetricInfo[]) => { - const { list, listInfo, shouldUpdate } = resolveMetricsRank(metricList); - metricRankList.current = list; - if (shouldUpdate) { - setMetricList(listInfo); - } - }; - - // 获取指标列表 - const getMetricList = () => { - Utils.request(api.getDashboardMetricList(clusterId, dashboardType)).then((res: MetricInfo[] | null) => { - if (!res) return; - const supportMetrics = res.filter((metric) => metric.support); - const selectedMetrics = supportMetrics.filter((metric) => metric.set).map((metric) => metric.name); - updateRank([...supportMetrics]); - setMetricsList(supportMetrics); - setSelectedMetricNames(selectedMetrics); - if (!selectedMetrics.length) { - setLoading(false); - } - }); - }; - - // 更新指标 - const setMetricList = (metricDetailDTOList: { metric: string; rank: number; set: boolean }[]) => { - return Utils.request(api.getDashboardMetricList(clusterId, dashboardType), { - method: 'POST', - data: { - metricDetailDTOList, - }, - }); - }; - // 根据筛选项获取图表信息 const getMetricChartData = () => { !curHeaderOptions.isAutoReload && setLoading(true); @@ -107,7 +177,7 @@ const DraggableCharts = (props: PropsType): JSX.Element => { { startTime, endTime, - metricsNames: selectedMetricNames, + metricsNames: metricList || [], }, dashboardType === MetricType.Broker || dashboardType === MetricType.Topic ? { @@ -168,65 +238,29 @@ const DraggableCharts = (props: PropsType): JSX.Element => { } }; - // 指标选中项更新回调 - const metricSelectCallback = (newMetricNames: (string | number)[]) => { - const updateMetrics: { metric: string; set: boolean; rank: number }[] = []; - // 需要选中的指标 - newMetricNames.forEach( - (name) => - !selectedMetricNames.includes(name) && - updateMetrics.push({ metric: name as string, set: true, rank: metricsList.find(({ name: metric }) => metric === name)?.rank }) - ); - // 取消选中的指标 - selectedMetricNames.forEach( - (name) => - !newMetricNames.includes(name) && - updateMetrics.push({ metric: name as string, set: false, rank: metricsList.find(({ name: metric }) => metric === name)?.rank }) - ); - - const requestPromise = Object.keys(updateMetrics).length ? setMetricList(updateMetrics) : Promise.resolve(); - requestPromise.then( - () => getMetricList(), - () => getMetricList() - ); - - return requestPromise; - }; - - // 拖拽开始回调,触发图表的 onDrag 事件( 设置为 true ),禁止同步展示图表的 tooltip - const dragStart = () => { - busInstance.emit('onDrag', true); - }; - - // 拖拽结束回调,更新图表顺序,并触发图表的 onDrag 事件( 设置为 false ),允许同步展示图表的 tooltip - const dragEnd = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => { - busInstance.emit('onDrag', false); + // 图表拖拽 + const dragCallback = (oldIndex: number, newIndex: number) => { const originFrom = metricRankList.current.indexOf(metricChartData[oldIndex].metricName); const originTarget = metricRankList.current.indexOf(metricChartData[newIndex].metricName); - const newList = arrayMoveImmutable(metricRankList.current, originFrom, originTarget); - metricRankList.current = newList; - setMetricList(newList.map((metric, rank) => ({ metric, rank, set: metricsList.find(({ name }) => metric === name)?.set || false }))); - setMetricChartData(arrayMoveImmutable(metricChartData, oldIndex, newIndex)); + metricFilterRef.current?.rankChange(originFrom, originTarget); }; - // 监听盒子宽度变化,重置图表宽度 - const observeDashboardWidthChange = () => { - const targetNode = document.getElementsByClassName('dcd-two-columns-layout-sider-footer')[0]; - targetNode && targetNode.addEventListener('click', () => busInstance.emit('chartResize')); + // 展开图表详情 + const onExpand = (metricName: string) => { + const linesName = scopeList.map((item) => item.value); + chartDetailRef.current.onOpen(dashboardType, metricName, linesName); }; + // 获取图表指标 useEffect(() => { - if (selectedMetricNames.length && curHeaderOptions) { + if (metricList?.length && curHeaderOptions) { getMetricChartData(); } - }, [curHeaderOptions, selectedMetricNames]); + }, [curHeaderOptions, metricList]); useEffect(() => { // 初始化页面,获取 scope 和 metric 信息 (dashboardType === MetricType.Broker || dashboardType === MetricType.Topic) && getScopeList(); - getMetricList(); - - setTimeout(() => observeDashboardWidthChange()); }, []); return ( @@ -234,95 +268,36 @@ const DraggableCharts = (props: PropsType): JSX.Element => { metricFilterRef.current?.open()} + nodeSelect={{ + name: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper', + customContent: ( + + ), }} /> -
- - {metricChartData && metricChartData.length ? ( -
- - {metricChartData.map((data) => { - const { metricName, metricUnit, metricLines, showLegend } = data; - - return ( -
-
- { - let content = ''; - const metricDefine = global.getMetricDefine(dashboardType, metricName); - if (metricDefine) { - content = metricDefine.desc; - } - return content; - }} - > - - {metricName} - ({metricUnit}) - - -
-
{ - const linesName = scopeList.map((item) => item.value); - chartDetailRef.current.onOpen(dashboardType, metricName, linesName); - }} - > - -
- -
- ); - })} -
-
- ) : loading ? ( - <> - ) : ( - - )} -
-
+ { + metricRankList.current = rankList; + setMetricList(list); + }} + /> + {/* 图表详情 */} diff --git a/km-console/packages/layout-clusters-fe/src/components/SwitchTab/index.tsx b/km-console/packages/layout-clusters-fe/src/components/SwitchTab/index.tsx index ae4008c9..e7d21688 100644 --- a/km-console/packages/layout-clusters-fe/src/components/SwitchTab/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/SwitchTab/index.tsx @@ -2,7 +2,8 @@ import React, { useLayoutEffect, useRef, useState } from 'react'; import './index.less'; interface SwitchTabProps { - defaultKey: string; + defaultKey?: string; + activeKey?: string | number; onChange: (key: string) => void; children: any; } @@ -18,9 +19,9 @@ const TabItem = (props: TabItemProps) => { }; const SwitchTab = (props: SwitchTabProps) => { - const { defaultKey, onChange, children } = props; + const { defaultKey, activeKey, onChange, children } = props; const tabRef = useRef(); - const [activeKey, setActiveKey] = useState(defaultKey); + const [active, setActive] = useState(activeKey || defaultKey); const [pos, setPos] = useState({ left: 0, width: 0, @@ -39,6 +40,10 @@ const SwitchTab = (props: SwitchTabProps) => { return false; }); } + }, [active]); + + useLayoutEffect(() => { + activeKey && setActive(activeKey); }, [activeKey]); return ( @@ -48,9 +53,10 @@ const SwitchTab = (props: SwitchTabProps) => { return (
{ - setActiveKey(key); + // 受控模式下不自动更新状态 + !activeKey && setActive(key); onChange(key); }} > diff --git a/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts b/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts index f566e850..4eb7fadf 100644 --- a/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts +++ b/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts @@ -38,6 +38,7 @@ export interface OriginMetricData { export interface FormattedMetricData { metricName: string; metricUnit: string; + metricType: MetricType; metricLines: { name: string; data: (string | number)[][]; @@ -240,6 +241,7 @@ export const formatChartData = ( // 初始化返回结构 const chartData: FormattedMetricData = { metricName, + metricType, metricUnit: curMetricInfo?.unit || '', metricLines: metricLines .sort((a, b) => Number(a.name < b.name) - 0.5) diff --git a/km-console/packages/layout-clusters-fe/src/constants/menu.tsx b/km-console/packages/layout-clusters-fe/src/constants/menu.tsx index 0bcfbb51..d0c0b592 100755 --- a/km-console/packages/layout-clusters-fe/src/constants/menu.tsx +++ b/km-console/packages/layout-clusters-fe/src/constants/menu.tsx @@ -56,12 +56,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({ clusterRunState && clusterRunState !== ClusterRunState.Raft ? { name: (intl: any) => { - return ( -
- {intl.formatMessage({ id: 'menu.cluster.zookeeper' })} -
-
- ); + return {intl.formatMessage({ id: 'menu.cluster.zookeeper' })}; }, path: 'zookeeper', icon: 'icon-Zookeeper', @@ -79,6 +74,35 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({ ], } : undefined, + { + name: (intl: any) => { + return ( +
+ {intl.formatMessage({ id: 'menu.cluster.connect' })} +
+
+ ); + }, + path: 'connect', + icon: 'icon-Operation', + children: [ + { + name: (intl: any) => {intl.formatMessage({ id: 'menu.cluster.connect.dashboard' })}, + path: '', + icon: 'icon-luoji', + }, + { + name: (intl: any) => {intl.formatMessage({ id: 'menu.cluster.connect.connectors' })}, + path: 'connectors', + icon: '#icon-luoji', + }, + { + name: (intl: any) => {intl.formatMessage({ id: 'menu.cluster.connect.workers' })}, + path: 'workers', + icon: 'icon-Jobs', + }, + ].filter((m) => m), + }, { name: 'consumer-group', path: 'consumers', diff --git a/km-console/packages/layout-clusters-fe/src/constants/reg.ts b/km-console/packages/layout-clusters-fe/src/constants/reg.ts index 0fbd11c6..fe3b51ab 100644 --- a/km-console/packages/layout-clusters-fe/src/constants/reg.ts +++ b/km-console/packages/layout-clusters-fe/src/constants/reg.ts @@ -5,6 +5,12 @@ export const regOddNumber = /^\d*[13579]$/; //奇数 export const regClusterName = /^[\u4E00-\u9FA5A-Za-z0-9\_\-\!\"\#\$\%&'()\*\+,./\:\;\<=\>?\@\[\\\]^\`\{\|\}~]*$/im; // 大、小写字母、数字、-、_ new RegExp('\[a-z0-9_-]$', 'g') export const regUsername = /^[_a-zA-Z-]*$/; // 大、小写字母、数字、-、_ new RegExp('\[a-z0-9_-]$', 'g') +export const regIpAddress = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; +export const regIpPort = /^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{1,5})|([0-9]{1,4}))$/; +export const regIpAndPort = + /^http(s|):\/\/(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{1,5})|([0-9]{1,4})))?$/; +export const regHttpOrHttpsAddress = + /^http(s|):\/\/(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; export const regExp = /^[ ]+$/; // 不能为空 export const regNonnegativeNumber = /^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/; // 非负数 diff --git a/km-console/packages/layout-clusters-fe/src/locales/zh.tsx b/km-console/packages/layout-clusters-fe/src/locales/zh.tsx index 99c6682d..5537c9d0 100755 --- a/km-console/packages/layout-clusters-fe/src/locales/zh.tsx +++ b/km-console/packages/layout-clusters-fe/src/locales/zh.tsx @@ -47,6 +47,11 @@ export default { [`menu.${systemKey}.operation.balance`]: 'Rebalance', [`menu.${systemKey}.operation.jobs`]: 'Job', + [`menu.${systemKey}.connect`]: 'Connect', + [`menu.${systemKey}.connect.dashboard`]: 'Overview', + [`menu.${systemKey}.connect.connectors`]: 'Connectors', + [`menu.${systemKey}.connect.workers`]: 'Workers', + [`menu.${systemKey}.acls`]: 'ACLs', [`menu.${systemKey}.jobs`]: 'Job', diff --git a/km-console/packages/layout-clusters-fe/src/pages/BrokerControllerChangeLog/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/BrokerControllerChangeLog/index.tsx index 7cd85c6a..9fb63190 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/BrokerControllerChangeLog/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/BrokerControllerChangeLog/index.tsx @@ -90,7 +90,7 @@ const ControllerChangeLogList: React.FC = (props: any) => {
-
+
{
-
+
void; +} + +export interface OperateInfo { + type: 'create' | 'edit'; + errors: { + [key: string]: string[]; + }; + detail?: { + connectClusterId: number; + connectorName: string; + connectorClassName: string; + connectorType: 'source' | 'sink'; + }; +} + +const existFormItems = { + basic: ['name', 'connector.class', 'tasks.max', 'key.converter', 'value.converter', 'header.converter'], + transforms: ['transforms'], + errorHandling: [ + 'errors.retry.timeout', + 'errors.retry.delay.max.ms', + 'errors.tolerance', + 'errors.log.enable', + 'errors.log.include.messages', + ], +}; + +const getExistFormItems = (type: 'source' | 'sink') => { + return [...existFormItems.basic, ...existFormItems.transforms, ...existFormItems.errorHandling, type === 'sink' ? 'topics' : ''].filter( + (k) => k + ); +}; + +const StepsFormContent = createContext< + OperateInfo & { + forms: { current: { [key: string]: FormInstance } }; + } +>({ + type: 'create', + errors: {}, + forms: { current: {} }, +}); + +function useStepForm(key: string | number) { + const { forms } = useContext(StepsFormContent); + const [form] = Form.useForm(); + let formInstace = form; + + if (forms.current[key]) { + formInstace = forms.current[key] as FormInstance; + } else { + forms.current[key] = formInstace; + } + + return [formInstace]; +} + +// 步骤一:设置插件类型 +const StepFormFirst = (props: SubFormProps) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [form] = useStepForm(0); + const { type, detail } = useContext(StepsFormContent); + const isEdit = type === 'edit'; + const [connectClusters, setConnectClusters] = useState<{ label: string; value: number }[]>([]); + const [selectedConnectClusterId, setSelectedConnectClusterId] = useState(detail?.connectClusterId); + const [input, setInput] = useState(''); + const [plugins, setPlugins] = useState([]); + const [selectedPlugin, setSelectedPlugin] = useState(detail?.connectorClassName); + const [pluginType, setPluginType] = useState<'source' | 'sink'>((detail?.connectorType.toLowerCase() as 'source' | 'sink') || 'source'); + const [loading, setLoading] = useState(false); + + const getConnectClusters = () => { + return Utils.request(api.getConnectClusters(clusterId)).then((res: ConnectCluster[]) => { + const arr = res.map(({ name, id }) => ({ + label: name || '-', + value: id, + })); + setConnectClusters(arr); + form.setFieldsValue({ + connectClusters: arr, + }); + }); + }; + + const getConnectorPlugins = () => { + setLoading(true); + return Utils.request(api.getConnectorPlugins(selectedConnectClusterId)) + .then((res: ConnectorPlugin[]) => { + setPlugins(res); + }) + .finally(() => setLoading(false)); + }; + + const getConnectorPluginConfig = (pluginName: string) => { + props.setSubmitLoading(true); + + Promise.all( + [ + Utils.request(api.getConnectorPluginConfig(selectedConnectClusterId, pluginName)), + isEdit ? Utils.request(api.getCurPluginConfig(selectedConnectClusterId, detail.connectorName)) : undefined, + ].filter((r) => r) + ) + .then((res: [ConnectorPluginConfig, { [key: string]: any }]) => { + const [pluginConfig, connectorConfigs] = res; + + // 格式化插件配置 + const result: FormConnectorConfigs = { + pluginConfig: {}, + }; + pluginConfig.configs.forEach(({ definition }) => { + if (!getExistFormItems(pluginType).includes(definition.name)) { + const pluginConfigs = result.pluginConfig; + const group = definition.group || 'Others'; + pluginConfigs[group] ? pluginConfigs[group].push(definition) : (pluginConfigs[group] = [definition]); + } + }); + Object.values(result.pluginConfig).forEach((arr) => arr.sort((a, b) => a.orderInGroup - b.orderInGroup)); + + // 加入当前 connector 的配置 + if (isEdit) { + result.connectorConfig = connectorConfigs; + } + + Object.keys(result).length && + form.setFieldsValue({ + configs: result, + }); + }) + .finally(() => props.setSubmitLoading(false)); + }; + + useEffect(() => { + if (selectedPlugin) { + getConnectorPluginConfig(selectedPlugin); + } + }, [selectedPlugin]); + + useEffect(() => { + if (selectedConnectClusterId) { + getConnectorPlugins(); + } + }, [selectedConnectClusterId]); + + useEffect(() => { + getConnectClusters(); + }, []); + + return ( +
+
+ + { + setInput(e.target.value); + }} + /> +
+
{ + return ( + + {value} + {record?.helpDocLink && ( + window.open(record.helpDocLink)} + > + help + + )} + + ); + }, + }, + ]} + dataSource={plugins.filter((plugin) => plugin.type === pluginType && (!input || plugin.className.includes(input)))} + pagination={false} + rowSelection={{ + type: 'radio', + preserveSelectedRowKeys: false, + selectedRowKeys: [selectedPlugin], + getCheckboxProps: (record) => { + return { + disabled: isEdit && record.className !== selectedPlugin, + }; + }, + onChange: (keys) => { + setSelectedPlugin(keys[0] as string); + form.setFieldsValue({ + connectorClassName: keys[0], + }); + }, + }} + /> +
+ + + { + if (!value) { + return Promise.reject('请选择 Connector 插件'); + } + return Promise.resolve(); + }, + }, + ]} + /> + { + if (!form.getFieldValue('connectorClassName')) { + return Promise.resolve(true); + } + if (!value) { + return Promise.reject(isEdit ? '插件或 connector 配置获取失败' : '插件配置获取失败,请重新选择插件'); + } + return Promise.resolve(); + }, + }, + ]} + /> +
+ + ) : ( + <> + ); + }} + + + + ); +}; + +// 步骤二:基础设置 +const StepFormSecond = (props: SubFormProps) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [prevForm] = useStepForm(0); + const [form] = useStepForm(1); + const [topicData, setTopicData] = useState([]); + const { type, detail, errors } = useContext(StepsFormContent); + const isEdit = type === 'edit'; + const connectorConfig = (prevForm.getFieldValue('configs') as FormConnectorConfigs)?.connectorConfig; + + const getTopicList = () => { + Utils.request(api.getTopicMetaList(Number(clusterId)), { + method: 'GET', + }).then((res: any) => { + const dataDe = res || []; + const dataHandle = dataDe.map((item: any) => { + return { + ...item, + key: item.topicName, + label: item.topicName, + value: item.topicName, + }; + }); + setTopicData(dataHandle); + }); + }; + + useEffect(() => { + getTopicList(); + }, []); + + useEffect(() => { + connectorConfig && + form.setFieldsValue({ + topics: + typeof connectorConfig['topics'] === 'string' ? connectorConfig['topics'].split(',').map((i: string) => i.trim()) : undefined, + }); + }, [topicData, connectorConfig]); + + useEffect(() => { + const curConfig = connectorConfig || {}; + form.setFieldsValue({ + 'connector.class': curConfig['connector.class'] || prevForm.getFieldValue('connectorClassName'), + 'tasks.max': curConfig['tasks.max'] || 1, + 'key.converter': curConfig['key.converter'], + 'value.converter': curConfig['value.converter'], + 'header.converter': curConfig['header.converter'], + }); + }, [connectorConfig]); + + useEffect(() => { + form.setFieldsValue({ + 'connector.class': prevForm.getFieldValue('connectorClassName'), + }); + }, [prevForm.getFieldValue('connectorClassName')]); + + useEffect(() => { + form.setFields([ + ...existFormItems.basic.map((name) => ({ name, errors: errors[name] || [] })), + { name: 'topics', errors: prevForm.getFieldValue('connectorType') === 'sink' ? errors['topics'] || [] : [] }, + ]); + }, [errors]); + + return ( +
+
+ 64) { + return Promise.reject('Connector 名称长度限制在1~128字符'); + } + if (!new RegExp(regClusterName).test(value)) { + return Promise.reject( + 'Connector 名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~' + ); + } + return Utils.request(api.isConnectorExist(prevForm.getFieldValue('connectClusterId'), value)).then( + (res: any) => { + const data = res || {}; + return data?.exist ? Promise.reject('Connector 名称重复') : Promise.resolve(); + }, + () => Promise.reject('连接超时! 请重试或检查服务') + ); + }, + }, + ]} + > + + + +
+ Connector 插件类型: {form.getFieldValue('connector.class') || '-'} +
+
+ + + + value || null} + > + + + value || null} + > + + + value || null} + > + + + {/* Connector 类型为 Sink 时才有 */} + {prevForm.getFieldValue('connectorType') === 'sink' && ( + + */} + + ) : type.toUpperCase() === 'INT' || type.toUpperCase() === 'LONG' ? ( + + ) : type.toUpperCase() === 'BOOLEAN' ? ( + + ) : ( + + )} + + ); + })} + + ); + })} + + )} + +
+ ); +}; + +const steps = [ + { + title: '设置插件类型', + content: StepFormFirst, + }, + { + title: '基础设置', + content: StepFormSecond, + }, + { + title: 'Transforms', + content: StepFormThird, + }, + { + title: 'Error Handling', + content: StepFormForth, + }, + { + title: '高级设置', + content: StepFormFifth, + }, +]; + +export default forwardRef( + ( + props: { + refresh: () => void; + }, + ref + ) => { + const [visible, setVisible] = useState(false); + const [jsonRef, setJsonRef] = useState({}); + const [currentStep, setCurrentStep] = useState(0); + const [stepInitState, setStepInitState] = useState([1]); + const [submitLoading, setSubmitLoading] = useState(false); + const [operateInfo, setOperateInfo] = useState({ + type: undefined, + errors: {}, + }); + const stepsFormRef = useRef<{ + [key: string]: FormInstance; + }>({}); + + const onOpen = (type: OperateInfo['type'], jsonRef: any, detail?: OperateInfo['detail']) => { + if (type === 'create') { + setStepInitState([1]); + } else { + setStepInitState([1, 2, 3, 4]); + } + setOperateInfo({ + type, + detail, + errors: {}, + }); + setJsonRef(jsonRef); + setVisible(true); + }; + + const onClose = () => { + Object.values(stepsFormRef.current).forEach((form) => { + form.resetFields(); + }); + stepsFormRef.current = {}; + setVisible(false); + setCurrentStep(0); + setStepInitState([]); + }; + + const turnTo = (jumpStep: number) => { + if (submitLoading) { + message.warning('加载中,请稍后重试'); + return; + } + if (jumpStep > currentStep) { + const prevInit = stepInitState[jumpStep - 1]; + if (!prevInit) { + message.warning('请按照顺序填写'); + } else { + stepsFormRef.current[currentStep].validateFields().then(() => { + const prevStep = jumpStep - 1; + if (currentStep < prevStep) { + stepsFormRef.current[prevStep] + .validateFields() + .then(() => { + setStepInitState((prev) => { + const cur = [...prev]; + cur[jumpStep] = 1; + return cur; + }); + setCurrentStep(jumpStep); + }) + .catch(() => { + setCurrentStep(prevStep); + }); + } else { + setStepInitState((prev) => { + const cur = [...prev]; + cur[jumpStep] = 1; + return cur; + }); + setCurrentStep(jumpStep); + } + }); + } + } else { + setCurrentStep(jumpStep); + } + }; + + // 校验所有表单 + const validateForms = ( + callback: (info: { + success?: { + connectClusterId: number; + connectorName: string; + configs: { + [key: string]: any; + }; + }; + error?: any; + }) => void + ) => { + const promises: Promise[] = []; + Object.values(stepsFormRef.current).forEach((form, i) => { + const promise = form + .validateFields() + .then((res) => { + return res; + }) + .catch(() => { + return Promise.reject(i); + }); + promises.push(promise); + }); + + Promise.all(promises).then( + (res) => { + const result = { + ...res[1], + ...res[3], + ...res[4], + }; + // topics 配置格式化 + res[1].topics && (result.topics = (res[1].topics as string[]).join(', ')); + // transforms 配置格式化 + res[2].transforms && + (res[2].transforms as string) + .split('\n') + .filter((l) => l) + .forEach((l) => { + const [k, ...v] = l.split('='); + result[k] = v.join('='); + }); + callback({ + success: { + connectClusterId: res[0].connectClusterId, + connectorName: result['name'], + configs: result, + }, + }); + }, + (error) => { + callback({ + error, + }); + } + ); + }; + + const toJsonMode = () => { + validateForms((info) => { + if (info.error) { + message.warning('校验失败,请检查填写内容'); + setCurrentStep(info.error); + } else { + let curClusterName = ''; + stepsFormRef.current[0].getFieldValue('connectClusters').some((cluster: { label: string; value: number }) => { + if (cluster.value === info.success.connectClusterId) { + curClusterName = cluster.label; + } + }); + (jsonRef as any)?.onOpen(operateInfo.type, curClusterName, info.success.configs); + onClose(); + } + }); + }; + + const onSubmit = () => { + validateForms((info) => { + if (info.error) { + message.warning('校验失败,请检查填写内容'); + setCurrentStep(info.error); + } else { + setSubmitLoading(true); + Object.entries(info.success.configs).forEach(([key, val]) => { + if (val === null) { + delete info.success.configs[key]; + } + }); + Utils.put(api.validateConnectorConfig, info.success).then( + (res: ConnectorPluginConfig) => { + if (res) { + if (res?.errorCount > 0) { + const errors: OperateInfo['errors'] = {}; + res?.configs + ?.filter((config) => config.value.errors.length !== 0) + .forEach(({ value }) => { + if (value.name.includes('transforms.')) { + errors['transforms'] = (errors['transforms'] || []).concat(value.errors); + } else { + errors[value.name] = value.errors; + } + }); + setOperateInfo((cur) => ({ + ...cur, + errors, + })); + + // 步骤跳转 + const items = getExistFormItems(stepsFormRef.current[0].getFieldValue('connectorType')); + const keys = Object.keys(errors).filter((key) => items.includes(key)); + let jumpStep = 4; + keys.forEach((key) => { + Object.values(existFormItems).some((items, i) => { + if (items.includes(key)) { + jumpStep > i + 1 && (jumpStep = i + 1); + return true; + } + return false; + }); + }); + setCurrentStep(jumpStep); + setSubmitLoading(false); + message.warning('字段校验失败,请检查'); + } else { + if (operateInfo.type === 'create') { + Utils.post(api.connectorsOperates, info.success) + .then(() => { + message.success('新建成功'); + onClose(); + props?.refresh(); + }) + .finally(() => setSubmitLoading(false)); + } else { + Utils.put(api.updateConnectorConfig, info.success) + .then(() => { + message.success('编辑成功'); + props?.refresh(); + onClose(); + }) + .finally(() => setSubmitLoading(false)); + } + } + } else { + setSubmitLoading(false); + message.error('接口校验出错,请重新提交'); + } + }, + () => setSubmitLoading(false) + ); + } + }); + }; + + useImperativeHandle(ref, () => ({ + onOpen, + onClose, + })); + + return ( + + {operateInfo.type && visible && ( + <> + turnTo(cur)}> + {steps.map(({ title }) => ( + + ))} + +
+ + {steps.map((step, i) => { + return createElement(step.content, { + visible: i === currentStep, + setSubmitLoading, + }); + })} + + {currentStep === steps.length - 1 && ( + + 如果你想自定义更多配置,可以点击 + + 继续补充 + + } + /> + )} +
+ {currentStep > 0 && ( + + )} + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} +
+
+ + )} +
+ ); + } +); diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnectorUseJSON.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnectorUseJSON.tsx new file mode 100644 index 00000000..b9351346 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnectorUseJSON.tsx @@ -0,0 +1,262 @@ +import api from '@src/api'; +import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem'; +import customMessage from '@src/components/Message'; +import { Button, Divider, Drawer, Form, message, Space, Utils } from 'knowdesign'; +import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { ConnectCluster, ConnectorPlugin, ConnectorPluginConfig, OperateInfo } from './AddConnector'; + +const PLACEHOLDER = `配置格式如下 + +{ + "connectClusterName": "", // Connect Cluster 名称 + "configs": { // 具体配置项 + "name": "", + "connector.class": "", + "tasks.max": 1, + ... + } +}`; + +export default forwardRef((props: any, ref) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [visible, setVisible] = useState(false); + const [form] = Form.useForm(); + const [type, setType] = useState('create'); + const [connectClusters, setConnectClusters] = useState<{ label: string; value: number }[]>([]); + const [defaultConfigs, setDefaultConfigs] = useState<{ [key: string]: any }>({}); + const [submitLoading, setSubmitLoading] = useState(false); + + const getConnectClusters = () => { + return Utils.request(api.getConnectClusters(clusterId)).then((res: ConnectCluster[]) => { + setConnectClusters( + res.map(({ name, id }) => ({ + label: name || '-', + value: id, + })) + ); + }); + }; + + const onOpen = (type: 'create' | 'edit', connectClusterName?: string, defaultConfigs?: { [key: string]: any }) => { + if (defaultConfigs) { + setDefaultConfigs({ ...defaultConfigs, connectClusterName }); + form.setFieldsValue({ + configs: JSON.stringify( + { + connectClusterName, + configs: defaultConfigs, + }, + null, + 2 + ), + }); + } + setType(type); + setVisible(true); + }; + + const onSubmit = () => { + setSubmitLoading(true); + form.validateFields().then( + (data) => { + const postData = JSON.parse(data.configs); + postData.connectorName = postData.configs.name; + postData.connectClusterId = connectClusters.find((cluster) => cluster.label === postData.connectClusterName).value; + delete postData.connectClusterName; + + Object.entries(postData.configs).forEach(([key, val]) => { + if (val === null) { + delete postData.configs[key]; + } + }); + Utils.put(api.validateConnectorConfig, postData).then( + (res: ConnectorPluginConfig) => { + if (res) { + if (res?.errorCount > 0) { + const errors: OperateInfo['errors'] = {}; + res?.configs + ?.filter((config) => config.value.errors.length !== 0) + .forEach(({ value }) => { + if (value.name.includes('transforms.')) { + errors['transforms'] = (errors['transforms'] || []).concat(value.errors); + } else { + errors[value.name] = value.errors; + } + }); + form.setFields([ + { + name: 'configs', + errors: Object.entries(errors).map(([name, errorArr]) => `${name}: ${errorArr.join('; ')}\n`), + }, + ]); + setSubmitLoading(false); + } else { + if (type === 'create') { + Utils.post(api.connectorsOperates, postData) + .then(() => { + customMessage.success('新建成功'); + onClose(); + props?.refresh(); + }) + .finally(() => setSubmitLoading(false)); + } else { + Utils.put(api.updateConnectorConfig, postData) + .then(() => { + customMessage.success('编辑成功'); + props?.refresh(); + onClose(); + }) + .finally(() => setSubmitLoading(false)); + } + } + } else { + setSubmitLoading(false); + message.error('接口校验出错,请重新提交'); + } + }, + () => setSubmitLoading(false) + ); + }, + () => setSubmitLoading(false) + ); + }; + + const onClose = () => { + setVisible(false); + form.resetFields(); + }; + + useEffect(() => { + getConnectClusters(); + }, []); + + useImperativeHandle(ref, () => ({ + onOpen, + onClose, + })); + + return ( + + + + + + + + } + > +
+ cluster.label === v.connectClusterName); + if (!targetConnectCluster) { + return Promise.reject('connectClusterName 不存在,请检查'); + } else { + connectClusterId = targetConnectCluster.value; + } + } + } + + if (!v.configs || typeof v.configs !== 'object') { + return Promise.reject('内容缺少 configs 字段或字段格式错误'); + } else { + // 校验 connectorName 字段 + if (!v.configs.name) { + return Promise.reject('configs 字段下缺少 name 项'); + } else { + if (type === 'edit' && v.configs.name !== defaultConfigs.name) { + return Promise.reject('编辑模式下不允许修改 name 字段'); + } + } + if (!v.configs['connector.class']) { + return Promise.reject('configs 字段下缺少 connector.class 项'); + } else if (type === 'edit' && v.configs['connector.class'] !== defaultConfigs['connector.class']) { + return Promise.reject('编辑模式下不允许修改 connector.class 字段'); + } + } + + if (type === 'create') { + // 异步校验 connector 名称是否重复 以及 className 是否存在 + return Promise.all([ + Utils.request(api.isConnectorExist(connectClusterId, v.configs.name)), + Utils.request(api.getConnectorPlugins(connectClusterId)), + ]).then( + ([data, plugins]: [any, ConnectorPlugin[]]) => { + return data?.exist + ? Promise.reject('name 与已有 Connector 重复') + : plugins.every((plugin) => plugin.className !== v.configs['connector.class']) + ? Promise.reject('该 connectCluster 下不存在 connector.class 项配置的插件') + : Promise.resolve(); + }, + () => { + return Promise.reject('接口校验出错,请重试'); + } + ); + } else { + return Promise.resolve(); + } + } catch (e) { + return Promise.reject('输入内容必须为 JSON'); + } + }, + }, + ]} + > + {visible && ( +
+ { + form.setFieldsValue({ configs }); + }} + /> +
+ )} +
+ +
+ ); +}); diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/Delete.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/Delete.tsx new file mode 100644 index 00000000..ef6b58fe --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/Delete.tsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { Button, Form, Input, Modal, Utils } from 'knowdesign'; +import notification from '@src/components/Notification'; +import { IconFont } from '@knowdesign/icons'; +import Api from '@src/api/index'; + +// eslint-disable-next-line react/display-name +const DeleteConnector = (props: { record: any; onConfirm?: () => void }) => { + const { record, onConfirm } = props; + const [form] = Form.useForm(); + const [delDialogVisible, setDelDialogVisble] = useState(false); + const handleDelOk = () => { + form.validateFields().then((e) => { + const formVal = form.getFieldsValue(); + formVal.connectClusterId = Number(record.connectClusterId); + Utils.delete(Api.connectorsOperates, { data: formVal }).then((res: any) => { + if (res === null) { + notification.success({ + message: '删除成功', + }); + setDelDialogVisble(false); + onConfirm && onConfirm(); + } else { + notification.error({ + message: '删除失败', + }); + } + }); + }); + }; + return ( + <> + + { + setDelDialogVisble(false); + }} + okText="删除" + okButtonProps={{ + danger: true, + size: 'small', + style: { + paddingLeft: '16px', + paddingRight: '16px', + }, + }} + cancelButtonProps={{ + size: 'small', + style: { + paddingLeft: '16px', + paddingRight: '16px', + }, + }} + > +
+ {record.connectorName} + ({ + validator(_, value) { + if (!value) { + return Promise.reject(new Error('请输入ConnectorName名称')); + } else if (value !== record.connectorName) { + return Promise.reject(new Error('请输入正确的ConnectorName名称')); + } + return Promise.resolve(); + }, + }), + ]} + > + + + +
+ + ); +}; + +export default DeleteConnector; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/Detail.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/Detail.tsx new file mode 100644 index 00000000..d3f6b42e --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/Detail.tsx @@ -0,0 +1,111 @@ +import React, { useState, useEffect } from 'react'; +import { Drawer, Utils, AppContainer, ProTable } from 'knowdesign'; +import API from '@src/api'; +import ConnectDetailCard from '@src/components/CardBar/ConnectDetailCard'; +import { defaultPagination, getConnectorsDetailColumns } from './config'; +import notification from '@src/components/Notification'; +import './index.less'; + +const prefix = 'connect-detail'; +const { request } = Utils; +const ConnectorDetail = (props: any) => { + const { visible, setVisible, record } = props; + const [global] = AppContainer.useGlobalValue(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [pagination, setPagination] = useState(defaultPagination); + const onClose = () => { + setVisible(false); + setPagination(defaultPagination); + // clean hash + }; + + // 请求接口获取数据 + const genData = async () => { + if (global?.clusterInfo?.id === undefined) return; + + setLoading(true); + + request(API.getConnectDetailTasks(record.connectorName, record.connectClusterId)) + .then((res: any) => { + setData(res || []); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any) => { + setPagination(pagination); + }; + + const optionFn: any = (taskId: any) => { + const params = { + action: 'restart', + connectClusterId: record?.connectClusterId, + connectorName: record?.connectorName, + taskId, + }; + + request(API.optionTasks(), { method: 'PUT', data: params }).then((res: any) => { + if (res === null) { + notification.success({ + message: `任务重试成功`, + }); + genData(); + } else { + notification.error({ + message: `任务重试失败`, + }); + } + }); + }; + + const retryOption = Utils.useDebounce(optionFn, 500); + + useEffect(() => { + visible && record && genData(); + }, [visible]); + + return ( + + {record.connectorName ?? '-'} + + } + width={1080} + placement="right" + onClose={onClose} + visible={visible} + className={`${prefix}-drawer`} + destroyOnClose + maskClosable={false} + > + +
Tasks
+ + {/* */} +
+ ); +}; + +export default ConnectorDetail; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx new file mode 100644 index 00000000..18136475 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx @@ -0,0 +1,51 @@ +import React, { useLayoutEffect, useState } from 'react'; +import api from '@src/api'; +import { Spin, Utils } from 'knowdesign'; +import { useParams } from 'react-router-dom'; +import NodataImg from '@src/assets/no-data.png'; + +interface Props { + children: any; +} + +const NoConnector = () => { + return ( +
+ + 暂无数据,请先接入 Connect 集群 +
+ ); +}; + +export default (props: Props) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [loading, setLoading] = useState(true); + const [disabled, setDisabled] = useState(true); + + useLayoutEffect(() => { + Utils.request(api.getConnectors(clusterId)) + .then((res: any[]) => { + res?.length && setDisabled(false); + }) + .finally(() => setLoading(false)); + }, []); + + return disabled ? ( + {loading ?
: } + ) : ( + props.children + ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/Workers.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/Workers.tsx new file mode 100644 index 00000000..1ac89fb6 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/Workers.tsx @@ -0,0 +1,120 @@ +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 { getWorkersColumns, defaultPagination } from './config'; +import { tableHeaderPrefix } from '@src/constants/common'; +import ConnectCard from '@src/components/CardBar/ConnectCard'; +import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; +import './index.less'; +import HasConnector from './HasConnector'; +const { request } = Utils; + +const Workers: React.FC = () => { + const [global] = AppContainer.useGlobalValue(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [searchKeywords, setSearchKeywords] = useState(''); + const [pagination, setPagination] = useState(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.getWorkersList(global?.clusterInfo?.id), { params }) + .then((res: any) => { + setPagination({ + current: res.pagination?.pageNo, + pageSize: res.pagination?.pageSize, + total: res.pagination?.total, + }); + setData(res?.bizData || []); + 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 ( + <> +
+ +
+ + <> +
+ +
+
+
+
+
genData({ pageNo: pagination.current, pageSize: pagination.pageSize })} + > + +
+
+
+ +
+
+ +
+ +
+ + ); +}; + +export default Workers; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx new file mode 100644 index 00000000..82bfa59a --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx @@ -0,0 +1,364 @@ +import SmallChart from '@src/components/SmallChart'; +import TagsWithHide from '@src/components/TagsWithHide'; +import { Button, Tag, Tooltip, Utils, Popconfirm } from 'knowdesign'; +import React from 'react'; +import Delete from './Delete'; +export const defaultPagination = { + current: 1, + pageSize: 10, + position: 'bottomRight', + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50', '100', '200', '500'], +}; + +export const optionType: { [name: string]: string } = { + ['stop']: '暂停', + ['restart']: '重启', + ['resume']: '继续', +}; + +export const stateEnum: any = { + ['UNASSIGNED']: { + // 未分配 + name: 'Unassigned', + color: '#556EE6', + bgColor: '#EBEEFA', + }, + ['RUNNING']: { + // 运行 + name: 'Running', + color: '#00C0A2', + bgColor: 'rgba(0,192,162,0.10)', + }, + ['PAUSED']: { + // 暂停 + name: 'Paused', + color: '#495057', + bgColor: '#ECECF6', + }, + ['FAILED']: { + // 失败 + name: 'Failed', + color: '#F58342', + bgColor: '#fef3e5', + }, + ['DESTROYED']: { + // 销毁 + name: 'Destroyed', + color: '#FF7066', + bgColor: '#fdefee', + }, + ['RESTARTING']: { + // 重新启动 + name: 'Restarting', + color: '#3991FF', + bgColor: '#e9f5ff', + }, +}; + +const calcCurValue = (record: any, metricName: string) => { + // const item = (record.metricPoints || []).find((item: any) => item.metricName === metricName); + // return item?.value || ''; + // TODO 替换record + const orgVal = record?.latestMetrics?.metrics?.[metricName]; + if (orgVal !== undefined) { + if (metricName === 'TotalRecordErrors') { + return Math.round(orgVal).toLocaleString(); + } else { + return Number(Utils.formatAssignSize(orgVal, 'KB', orgVal > 1000 ? 2 : 3)).toLocaleString(); + // return Utils.formatAssignSize(orgVal, 'KB'); + } + } + return '-'; + // return orgVal !== undefined ? (metricName !== 'HealthScore' ? formatAssignSize(orgVal, 'KB') : orgVal) : '-'; +}; + +const renderLine = (record: any, metricName: string) => { + const points = record.metricLines?.find((item: any) => item.metricName === metricName)?.metricPoints || []; + return points.length ? ( +
+ ({ time: item.timeStamp, value: item.value })), + }} + /> + {calcCurValue(record, metricName)} +
+ ) : ( + {calcCurValue(record, metricName)} + ); +}; + +export const getConnectorsColumns = (arg?: any) => { + const columns = [ + { + title: 'Connect集群', + dataIndex: 'connectClusterName', + key: 'connectClusterName', + width: 200, + fixed: 'left', + lineClampOne: true, + needTooltip: true, + // render: (t: string, r: any) => { + // return ( + // + // {t} + // {r?.status ? Live : Down} + // + // ); + // }, + }, + { + title: 'Connector Name', + dataIndex: 'connectorName', + key: 'connectorName', + width: 160, + lineClampOne: true, + render: (t: string, r: any) => { + return t ? ( + <> + + { + arg.getDetailInfo(r); + }} + > + {t} + + + + ) : ( + '-' + ); + }, + }, + { + title: 'State', + dataIndex: 'state', + key: 'state', + width: 120, + render: (t: string, r: any) => { + return t ? ( + + {stateEnum[t]?.name} + + ) : ( + '-' + ); + }, + }, + { + title: 'Class', + dataIndex: 'connectorClassName', + key: 'connectorClassName', + width: 150, + lineClampOne: true, + needTooltip: true, + }, + { + title: 'Type', + dataIndex: 'connectorType', + key: 'connectorType', + width: 100, + render: (value: any, record: any) => Utils.firstCharUppercase(value), + }, + { + title: 'Tasks', + dataIndex: 'taskCount', + key: 'taskCount', + width: 100, + }, + { + title: '消息读取速率(KB/s)', + dataIndex: 'readRate', + key: 'readRate', + sorter: true, + width: 170, + render: (value: any, record: any) => + renderLine(record, record.connectorType === 'SINK' ? 'SinkRecordReadRate' : 'SourceRecordPollRate'), + }, + { + title: '消息写入速率(KB/s)', + dataIndex: 'writeRate', + key: 'writeRate', + sorter: true, + width: 170, + render: (value: any, record: any) => + renderLine(record, record.connectorType === 'SINK' ? 'SinkRecordSendRate' : 'SourceRecordWriteRate'), + }, + { + title: '消息处理错误次数(次)', + dataIndex: 'recordErrors', + key: 'recordErrors', + sorter: true, + width: 170, + render: (value: any, record: any) => renderLine(record, 'TotalRecordErrors'), + }, + { + title: 'Topics', + dataIndex: 'topicNameList', + key: 'topicNameList', + width: 200, + render(t: any, r: any) { + return t && t.length > 0 ? `共有${num}个`} /> : '-'; + }, + }, + { + title: '操作', + dataIndex: 'options', + key: 'options', + width: 200, + filterTitle: true, + fixed: 'right', + // eslint-disable-next-line react/display-name + render: (_t: any, r: any) => { + return ( +
+ arg?.optionConnect(r, 'restart')} + // onCancel={cancel} + okText="是" + cancelText="否" + overlayClassName="connect-popconfirm" + > + + + + {(r.state === 'RUNNING' || r.state === 'PAUSED') && ( + arg?.optionConnect(r, r.state === 'RUNNING' ? 'stop' : 'resume')} + // onCancel={cancel} + okText="是" + cancelText="否" + overlayClassName="connect-popconfirm" + > + + + )} + + + +
+ ); + }, + }, + ]; + return columns; +}; + +// Workers +export const getWorkersColumns = (arg?: any) => { + const columns = [ + { + title: 'Worker Host', + dataIndex: 'workerHost', + key: 'workerHost', + width: 200, + }, + { + title: '所属集群', + dataIndex: 'connectClusterName', + key: 'connectClusterName', + width: 200, + }, + { + title: 'Connectors', + dataIndex: 'connectorCount', + key: 'connectorCount', + width: 200, + }, + { + title: 'Tasks', + dataIndex: 'taskCount', + key: 'taskCount', + width: 200, + }, + ]; + return columns; +}; + +// Detail +export const getConnectorsDetailColumns = (arg?: any) => { + const columns = [ + { + title: 'Task ID', + dataIndex: 'taskId', + key: 'taskId', + width: 240, + render: (t: any, r: any) => { + return ( + + {t} + { + + {Utils.firstCharUppercase(r?.state as string)} + + } + + ); + }, + }, + { + title: 'Worker', + dataIndex: 'workerId', + key: 'workerId', + width: 240, + }, + { + title: '错误原因', + dataIndex: 'trace', + key: 'trace', + width: 400, + needTooltip: true, + lineClampOne: true, + }, + { + title: '操作', + dataIndex: 'role', + key: 'role', + width: 100, + render: (_t: any, r: any) => { + return ( +
+ arg?.retryOption(r.taskId)} + // onCancel={cancel} + okText="是" + cancelText="否" + overlayClassName="connect-popconfirm" + > + 重试 + +
+ ); + }, + }, + ]; + return columns; +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/index.less b/km-console/packages/layout-clusters-fe/src/pages/Connect/index.less new file mode 100644 index 00000000..c4e23094 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/index.less @@ -0,0 +1,193 @@ +// connect列表 图表 +.metric-data-wrap { + // display: flex; + // align-items: center; + width: 100%; + .cur-val { + display: block; + text-align: right; + } + .dcloud-spin-nested-loading { + flex: 1; + } +} + +// 新增按钮 +.add-connect { + .dcloud-btn-primary:hover, + .dcloud-btn-primary:focus { + // 可以控制新增按钮的hover和focus的样式 + // background: #556ee6; + // border-color: #556ee6; + } + &-btn { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-right: none; + } + &-dropdown-menu { + .dcloud-dropdown-menu { + border-radius: 8px; + &-item { + font-size: 13px; + } + } + } + &-json { + padding: 8px 12px !important; + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-left: none; + } +} + +// connect详情 +.connect-detail-drawer { + .card-bar-container { + background: rgba(86, 110, 230, 0.04) !important; + + .card-bar-colunms { + background-color: rgba(86, 110, 230, 0); + } + } + &-title { + margin: 20px 0 8px; + font-family: @font-family-bold; + font-size: 16px; + font-weight: 500; + } +} + +// 删除 +.del-connect-modal { + .tip-info { + display: flex; + color: #592d00; + padding: 6px 14px; + font-size: 13px; + background: #fffae0; + border-radius: 4px; + .anticon { + color: #ffc300; + margin-right: 4px; + margin-top: 3px; + } + .test-right-away { + color: #556ee6; + cursor: pointer; + } + .dcloud-alert-content { + flex: none; + } + } +} + +// 重启、继续/暂停 气泡卡片 +.connect-popconfirm { + .dcloud-popover-inner-content { + padding: 6px 16px; + } + .dcloud-popover-inner { + border-radius: 8px; + } + .dcloud-popover-message, + .dcloud-btn { + font-size: 12px !important; + } +} + +.operate-connector-drawer { + .connector-plugin-desc { + font-size: 13px; + .connector-plugin-title { + font-family: @font-family-bold; + } + } + .dcloud-collapse.add-connector-collapse { + .add-connector-collapse-panel, + .add-connector-collapse-panel:last-child { + margin-bottom: 8px; + overflow: hidden; + background: #f8f9fa; + border: 0px; + border-radius: 8px; + .dcloud-collapse-header { + padding: 8px 12px; + font-size: 14px; + color: #495057; + .dcloud-collapse-arrow { + margin-right: 8px !important; + font-size: 10px; + } + } + &:hover .dcloud-collapse-extra { + opacity: 1; + } + &:not(.dcloud-collapse-item-active) { + .dcloud-collapse-header:hover { + background: #f1f3ff; + } + } + .dcloud-collapse-content-box { + display: flex; + flex-flow: row wrap; + justify-content: space-between; + padding: 20px 14px 0 14px; + .dcloud-form-item { + flex: 1 0 50%; + .dcloud-input-number { + width: 100%; + } + &:nth-child(2n) { + padding-left: 6px; + } + &:nth-child(2n + 1) { + padding-right: 6px; + } + .dcloud-form-item-control-input { + height: 100%; + } + } + } + } + } + + .add-container-plugin-select { + height: 27px; + margin: 4px 0; + position: relative; + .dcloud-form-item { + position: absolute; + top: 0; + left: 0; + .dcloud-form-item-control-input { + min-height: 0; + } + } + } + + .dcloud-alert { + margin: 16px 0 24px 0; + padding: 0 12px; + border: unset; + border-radius: 8px; + background: #fffae0; + .dcloud-alert-message { + font-size: 13px; + color: #592d00; + .dcloud-btn { + padding: 0 4px; + font-size: 13px; + } + } + } +} + +.operate-connector-drawer-use-json { + .CodeMirror.cm-s-default { + height: calc(100vh - 146px); + } + .dcloud-form-item { + margin-bottom: 0 !important; + } +} diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/index.tsx new file mode 100644 index 00000000..4c8dbb88 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/index.tsx @@ -0,0 +1,228 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { ProTable, Dropdown, Button, Utils, AppContainer, SearchInput, Menu } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import API from '../../api'; +import { getConnectorsColumns, defaultPagination, optionType } from './config'; +import { tableHeaderPrefix } from '@src/constants/common'; +import ConnectCard from '@src/components/CardBar/ConnectCard'; +import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; +import AddConnector, { OperateInfo } from './AddConnector'; +import ConnectorDetail from './Detail'; +import notification from '@src/components/Notification'; +import './index.less'; +import AddConnectorUseJSON from './AddConnectorUseJSON'; +import HasConnector from './HasConnector'; +const { request } = Utils; + +const rateMap: any = { + readRate: ['SinkRecordReadRate', 'SourceRecordPollRate'], + writeRate: ['SinkRecordSendRate', 'SourceRecordWriteRate'], + recordErrors: ['TotalRecordErrors'], +}; + +const Connectors: 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(defaultPagination); + const [sortInfo, setSortInfo] = useState({}); + const [detailRecord, setDetailRecord] = useState(''); + const [healthType, setHealthType] = useState(true); + const addConnectorRef = useRef(null); + const addConnectorJsonRef = useRef(null); + + const getRecent1DayTimeStamp = () => [Date.now() - 24 * 60 * 60 * 1000, Date.now()]; + // 请求接口获取数据 + const genData = async ({ pageNo, pageSize, filters, sorter }: any) => { + const [startStamp, endStamp] = getRecent1DayTimeStamp(); + if (global?.clusterInfo?.id === undefined) return; + setLoading(true); + const params = { + metricLines: { + aggType: 'avg', + endTime: endStamp, + metricsNames: ['SourceRecordPollRate', 'SourceRecordWriteRate', 'SinkRecordReadRate', 'SinkRecordSendRate', 'TotalRecordErrors'], + startTime: startStamp, + topNu: 0, + }, + searchKeywords: searchKeywords.slice(0, 128), + pageNo, + pageSize, + latestMetricNames: ['SourceRecordPollRate', 'SourceRecordWriteRate', 'SinkRecordReadRate', 'SinkRecordSendRate', 'TotalRecordErrors'], + sortType: sorter?.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : 'desc', + sortMetricNameList: rateMap[sorter?.field] || [], + }; + + request(API.getConnectorsList(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, + key: item.connectClusterName + item.connectorName, + }; + }) || []; + setData(newData); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any) => { + setSortInfo(sorter); + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter }); + }; + + const menu = ( + + + addConnectorJsonRef.current?.onOpen('create')}>JSON 新增Connector + + + ); + + const getDetailInfo = (record: any) => { + setDetailRecord(record); + setDetailVisible(true); + }; + + // 编辑 + const editConnector = (detail: OperateInfo['detail']) => { + addConnectorRef.current?.onOpen('edit', addConnectorJsonRef.current, detail); + }; + + // 重启、暂停/继续 操作 + const optionConnect = (record: any, action: string) => { + const params = { + action, + connectClusterId: record?.connectClusterId, + connectorName: record?.connectorName, + }; + + request(API.connectorsOperates, { method: 'PUT', data: params }).then((res: any) => { + if (res === null) { + notification.success({ + message: `任务已${optionType[params.action]}`, + description: `任务状态更新会有至多1min延迟`, + }); + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo }); + setHealthType(!healthType); + } else { + notification.error({ + message: `${optionType[params.action]}任务失败`, + }); + } + }); + }; + + // 删除任务 + const deleteTesk = () => { + genData({ pageNo: 1, pageSize: pagination.pageSize }); + }; + + useEffect(() => { + genData({ + pageNo: 1, + pageSize: pagination.pageSize, + sorter: sortInfo, + }); + }, [searchKeywords]); + + return ( + <> +
+ +
+ + <> +
+ +
+
+
+
+
genData({ pageNo: pagination.current, pageSize: pagination.pageSize })} + > + +
+
+
+ + + + + + + +
+
+ +
+ +
+ + + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo })} + /> + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo })} + /> + + ); +}; + +export default Connectors; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricSelect.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricSelect.tsx new file mode 100644 index 00000000..09d68652 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricSelect.tsx @@ -0,0 +1,298 @@ +import React, { useState, useEffect, forwardRef, useImperativeHandle, useRef } from 'react'; +import { Drawer, Button, Space, Divider, ProTable } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import { ITableColumnsType } from 'knowdesign/lib/extend/d-table'; +import { cloneDeep } from 'lodash'; +import { MetricType } from '@src/api'; +import '../../components/ChartOperateBar/style/indicator-drawer.less'; + +export interface TreeTableDataSourceType { + set?: boolean; + children?: TreeTableData; + [key: string]: any; +} + +export interface TreeTableData { + showHeader: boolean; + rowKey: string; + columns: ITableColumnsType[]; + dataSource: TreeTableDataSourceType[]; +} + +export interface CheckboxStatus { + type: MetricType; + key: string; + status: boolean | 'indeterminate'; + children?: CheckboxStatus[]; +} + +export interface TreeTableProps { + tree: TreeTableData; + checkField: string; + submitCallback: ([leafs, checkboxData]: [any[], CheckboxStatus]) => Promise; +} + +const TreeTableItem = (props: { + treeData: TreeTableData; + checkboxData: CheckboxStatus[]; + keyTrace: (string | number)[]; + onKeyChange: any; +}) => { + const { treeData, checkboxData: data, keyTrace, onKeyChange } = props; + return ( + item.status === true).map((item) => item.key), + onChange: (keys: string[]) => { + onKeyChange(keyTrace, keys); + }, + getCheckboxProps: (record: TreeTableDataSourceType) => { + return { + indeterminate: data.some((item) => item.key === record[treeData.rowKey] && item.status === 'indeterminate'), + }; + }, + }, + }, + treeData.dataSource.some((item) => item.children) + ? { + expandable: { + childrenColumnName: 'notExist', + expandRowByClick: true, + expandedRowRender: (record: TreeTableDataSourceType) => { + return record?.children ? ( + record[treeData.rowKey] === item.key)?.children} + onKeyChange={onKeyChange} + /> + ) : ( + <> + ); + }, + rowExpandable: (record: TreeTableDataSourceType) => { + return !!record?.children; + }, + expandIcon: ({ expanded, onExpand, record }: { expanded: boolean; onExpand: any; record: TreeTableDataSourceType }) => { + return record?.children ? ( + expanded ? ( + { + onExpand(record, e); + }} + /> + ) : ( + { + onExpand(record, e); + }} + /> + ) + ) : ( + <> + ); + }, + }, + } + : {} + ), + }} + /> + ); +}; + +export const TreeTable = forwardRef((props: TreeTableProps, ref) => { + const [treeData, setTreeData] = useState(props.tree); + const [checkboxData, setCheckboxData] = useState(); + + // 根据链条查找指定选择框 + const findTargetCheckbox = (checkboxData: CheckboxStatus, keyTrace: (string | number)[]) => { + if (keyTrace.length === 0) { + return checkboxData; + } + + let targetCheckbox: CheckboxStatus = checkboxData; + keyTrace.forEach((key) => { + Object.values(targetCheckbox?.children).some((checkbox) => { + if (key === checkbox.key) { + targetCheckbox = checkbox; + return true; + } else { + return false; + } + }); + }); + return targetCheckbox; + }; + + const changeStatus = (checkbox: CheckboxStatus, type: CheckboxStatus['status']) => { + checkbox.status = type; + (checkbox?.children || []).forEach((c) => changeStatus(c, type)); + }; + + // 更新节点状态 + const updateCheckStatus = (data: CheckboxStatus, keyTrace: (string | number)[], keys: string[]) => { + // 1. 设置更新元素及其子元素的状态 + const targetCheckbox = findTargetCheckbox(data, keyTrace); + (targetCheckbox?.children || []).forEach((checkbox) => { + if (keys.includes(checkbox.key)) { + changeStatus(checkbox, true); + } else if (checkbox.status === true) { + changeStatus(checkbox, false); + } + }); + + // 2. 更新自身状态 + targetCheckbox.status = keys?.length ? (keys.length === targetCheckbox?.children?.length ? true : 'indeterminate') : false; + + // 3. 更新父级选中状态 + for (let i = 1; i < keyTrace.length; i++) { + const newTrace = keyTrace.slice(0, keyTrace.length - i); + const newCheckbox = findTargetCheckbox(data, newTrace); + const trueLen = newCheckbox?.children.map((item) => item.status === true).filter((s) => s).length; + newCheckbox.status = trueLen === 0 ? false : trueLen === newCheckbox?.children.length ? true : 'indeterminate'; + } + }; + + const onKeyChange = (keyTrace: (string | number)[], keys: string[]) => { + const clonedCheckboxData = cloneDeep(checkboxData); + updateCheckStatus(clonedCheckboxData, keyTrace, keys); + setCheckboxData(clonedCheckboxData); + }; + + // 生成初始选中结构 + const renderInitCheckboxData = ( + tree: TreeTableData, + curCheckboxData: CheckboxStatus, + wholeCheckboxData: CheckboxStatus, + keyTrace: (string | number)[], + callbacks: (() => void)[] + ) => { + curCheckboxData.children = []; + const selectedKeys: string[] = []; + + tree.dataSource.forEach((item) => { + const data = { + type: item.type, + key: item[tree.rowKey], + status: false, + }; + curCheckboxData.children.push(data); + if (item.children) { + renderInitCheckboxData(item.children, data, wholeCheckboxData, [...keyTrace, item[tree.rowKey]], callbacks); + } else if (item.set) { + selectedKeys.push(item[tree.rowKey]); + } + }); + + if (selectedKeys.length) { + callbacks.push(() => updateCheckStatus(wholeCheckboxData, keyTrace, selectedKeys)); + } + }; + + // 获取叶子节点状态数组 + const getLeafNodes = (checkboxData: CheckboxStatus, arr: any[]) => { + checkboxData?.children.forEach((item) => { + if (item?.children) { + getLeafNodes(item, arr); + } else { + arr.push({ ...item }); + } + }); + }; + + const getLeafCheckboxData = () => { + const leafs: any[] = []; + getLeafNodes(checkboxData, leafs); + return [leafs, checkboxData]; + }; + + // 初始化选中状态数据结构 + useEffect(() => { + const initCheckbox: CheckboxStatus = { + type: undefined, + key: undefined, + status: false, + }; + const callbacks: (() => void)[] = []; + renderInitCheckboxData(treeData, initCheckbox, initCheckbox, [], callbacks); + callbacks.forEach((cb) => cb()); + setCheckboxData(initCheckbox); + }, [treeData]); + + useEffect(() => { + setTreeData(props.tree); + }, [props.tree]); + + useImperativeHandle(ref, () => ({ + getLeafCheckboxData, + })); + + return checkboxData ? ( + + ) : ( + <> + ); +}); + +export const TreeTableDrawer = forwardRef((props: TreeTableProps, ref) => { + const [visible, setVisible] = useState(false); + const [confirmLoading, setConfirmLoading] = useState(false); + const treeTableRef = useRef(null); + + const onSubmit = () => { + setConfirmLoading(true); + props + .submitCallback(treeTableRef.current?.getLeafCheckboxData()) + .then(() => { + setVisible(false); + }) + .finally(() => { + setConfirmLoading(false); + }); + }; + + useImperativeHandle(ref, () => ({ + open: () => setVisible(true), + })); + + return ( + setVisible(false)} + visible={visible} + maskClosable={false} + destroyOnClose + extra={ + + + + + + } + > + + + ); +}); diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricsFilter.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricsFilter.tsx new file mode 100644 index 00000000..a4d9088a --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/MetricsFilter.tsx @@ -0,0 +1,235 @@ +import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; +import api, { MetricType } from '@src/api'; +import { arrayMoveImmutable } from 'array-move'; +import { expandedRowColumns } from '@src/components/ChartOperateBar/MetricSelect'; +import { TreeTableDataSourceType, TreeTableDrawer, TreeTableData, CheckboxStatus } from './MetricSelect'; +import { MetricInfo } from '@src/constants/chartConfig'; +import { forwardRef } from 'react'; +import { AppContainer, Utils } from 'knowdesign'; +import { useParams } from 'react-router-dom'; + +interface MetricsFilterProps { + metricType: MetricType[]; + onSelectChange: (list: MetricInfo[]) => void; + onRankChange: (rankList: string[]) => void; +} + +// 处理图表排序 +const resolveMetricsRank = (metricList: MetricInfo[]) => { + const isRanked = metricList.some(({ rank }) => rank !== null); + const listInfo: { [key: string]: { metric: string; rank: number; set: boolean }[] } = {}; + let shouldUpdate = false; + let sortedList: MetricInfo[] = []; + + if (isRanked) { + const rankedMetrics = metricList.filter(({ rank }) => rank !== null).sort((a, b) => a.rank - b.rank); + const unRankedMetrics = metricList.filter(({ rank }) => rank === null); + // 如果有新增/删除指标的情况,需要触发更新 + if (unRankedMetrics.length || rankedMetrics.some(({ rank }, i) => rank !== i)) { + shouldUpdate = true; + } + sortedList = [...rankedMetrics, ...unRankedMetrics.sort((a, b) => Number(a.name > b.name) - 0.5)]; + } else { + shouldUpdate = true; + // 按字母先后顺序初始化指标排序 + sortedList = metricList.sort((a, b) => a.type - b.type).sort((a, b) => (a.type !== b.type ? 1 : Number(a.name > b.name) - 0.5)); + } + + sortedList.forEach((metric, rank) => { + !listInfo[metric.type] && (listInfo[metric.type] = []); + listInfo[metric.type].push({ metric: metric.name, rank, set: metricList.find(({ name }) => metric.name === name)?.set || false }); + }); + + return { + list: sortedList.map(({ name }) => name), + listInfo, + shouldUpdate, + }; +}; + +const MetricsFilter = forwardRef((props: MetricsFilterProps, ref) => { + const [global] = AppContainer.useGlobalValue(); + const { metricType, onSelectChange, onRankChange } = props; + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [metricsList, setMetricsList] = useState([]); // 指标列表 + const [metricRankList, setMetricRankList] = useState([]); + const [tree, setTree] = useState(); + const metricSelectRef = useRef(null); + + // 更新指标 + const setMetricList = (list: { [key: string]: { metric: string; rank: number; set: boolean }[] }) => { + const reqArr: Promise[] = []; + Object.entries(list).forEach(([type, metricDetailDTOList]) => { + reqArr.push( + Utils.request(api.getDashboardMetricList(clusterId, type as unknown as MetricType), { + method: 'POST', + data: { + metricDetailDTOList, + }, + }) + ); + }); + return Promise.all(reqArr); + }; + + // TODO: 图表展示顺序变更 + const rankChange = (oldIndex: number, newIndex: number) => { + const newList = arrayMoveImmutable(metricRankList, oldIndex, newIndex); + setMetricRankList(newList); + const updates: { [key: string]: { metric: string; rank: number; set: boolean }[] } = {}; + newList.forEach((metric, rank) => { + const targetMetric = metricsList.find(({ name }) => metric === name); + if (targetMetric) { + const info = { metric, rank, set: targetMetric?.set || false }; + updates[targetMetric.type] ? updates[targetMetric.type].push(info) : (updates[targetMetric.type] = [info]); + } + }); + setMetricList(updates); + }; + + // 更新 rank + const updateRank = (metricList: MetricInfo[]) => { + const { list, listInfo, shouldUpdate } = resolveMetricsRank(metricList); + setMetricRankList(list); + if (shouldUpdate) { + setMetricList(listInfo); + } + }; + + // 获取指标列表 + const getMetricList = () => { + Promise.all(metricType.map((type) => Utils.request(api.getDashboardMetricList(clusterId, type)))).then((list) => { + let allSupportMetrics: MetricInfo[] = []; + + const treeData: TreeTableData = { + showHeader: true, + rowKey: 'category', + columns: [{ title: '分类', dataIndex: 'category' }], + dataSource: [], + }; + + (list as unknown as (MetricInfo[] | null)[]).forEach((res, i) => { + if (!res) return; + const supportMetrics = res.filter((metric) => metric.support); + allSupportMetrics = allSupportMetrics.concat(supportMetrics); + + // 处理结构 + const data: TreeTableDataSourceType = { + category: metricType[i] === MetricType.Connect ? 'Connect Cluster' : 'Connector', + }; + const categoryData: { + [category: string]: { + name: string; + unit: string; + desc: string; + }[]; + } = {}; + supportMetrics.forEach(({ name, desc, set }) => { + const metricDefine = global.getMetricDefine(metricType[i], name); + const returnData = { + type: metricType[i], + set, + name, + desc, + unit: metricDefine?.unit, + }; + if (metricDefine.category) { + if (!categoryData[metricDefine.category]) { + categoryData[metricDefine.category] = [returnData]; + } else { + categoryData[metricDefine.category].push(returnData); + } + } else { + if (!categoryData['Other']) { + categoryData['Other'] = [returnData]; + } else { + categoryData['Other'].push(returnData); + } + } + }); + + if (Object.keys(categoryData).length > 1) { + const returnData: TreeTableData = { + showHeader: false, + rowKey: 'category', + columns: [{ title: '分类', dataIndex: 'category' }], + dataSource: [], + }; + Object.entries(categoryData).forEach(([category, data]) => { + returnData.dataSource.push({ + category, + children: { + showHeader: true, + rowKey: 'name', + columns: expandedRowColumns, + dataSource: data, + }, + }); + }); + data.children = returnData; + } else { + data.children = { + showHeader: true, + rowKey: 'name', + columns: expandedRowColumns, + dataSource: Object.values(categoryData)[0] || [], + }; + } + treeData.dataSource.push(data); + }); + setTree(treeData); + + updateRank([...allSupportMetrics]); + setMetricsList(allSupportMetrics); + }); + }; + + // TODO: 指标选中项更新回调 + const metricSelectCallback = ([newMetrics]: [CheckboxStatus[], any]) => { + const updateMetrics: { [key: string]: { metric: string; set: boolean; rank: number }[] } = {}; + + newMetrics.forEach((metric) => { + const oldMetric = metricsList.find(({ type, name }) => type === metric.type && name === metric.key); + + if (oldMetric) { + if (oldMetric.set !== metric.status) { + if (updateMetrics[metric.type]) { + updateMetrics[metric.type].push({ metric: oldMetric.name, set: !oldMetric.set, rank: oldMetric.rank }); + } else { + updateMetrics[metric.type] = [{ metric: oldMetric.name, set: !oldMetric.set, rank: oldMetric.rank }]; + } + } + } + }); + + const requestPromise = Object.keys(updateMetrics).length ? setMetricList(updateMetrics) : Promise.resolve(); + requestPromise.then( + () => getMetricList(), + () => getMetricList() + ); + return requestPromise; + }; + + useEffect(() => { + onSelectChange(metricsList); + }, [metricsList]); + + useEffect(() => { + onRankChange(metricRankList); + }, [metricRankList]); + + useEffect(() => { + getMetricList(); + }, []); + + useImperativeHandle(ref, () => ({ + rankChange, + open: () => metricSelectRef.current?.open(), + })); + + return tree && ; +}); + +export default MetricsFilter; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/SelectContent.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/SelectContent.tsx new file mode 100644 index 00000000..145bcb34 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/SelectContent.tsx @@ -0,0 +1,233 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { DataNode } from 'knowdesign/lib/basic/tree'; +import { useParams } from 'react-router-dom'; +import { Tree, Input, Utils, Button } from 'knowdesign'; +import api from '@src/api'; +import type { ConnectCluster } from '../Connect/AddConnector'; + +export interface Connector { + connectClusterId: number; + connectClusterName: string; + connectorName: string; +} + +interface SelectContentProps { + title: string; + scopeList: { + connectClusters: ConnectCluster[]; + connectors: Connector[]; + }; + isTop?: boolean; + visibleChange?: (v: boolean) => void; + onChange?: (list: any, inputValue: string) => void; +} + +interface CheckedNodes { + connectClusters: number[]; + connectors: { connectClusterId: number; connectorName: string }[]; +} + +interface CheckedKeysProps { + checked: React.Key[]; + halfChecked: React.Key[]; +} + +const getParentKey = (key: React.Key, tree: DataNode[]): React.Key => { + let parentKey: React.Key; + for (let i = 0; i < tree.length; i++) { + const node = tree[i]; + if (node.children) { + if (node.children.some((item) => item.key === key)) { + parentKey = node.key; + } else if (getParentKey(key, node.children)) { + parentKey = getParentKey(key, node.children); + } + } + } + return parentKey; +}; + +const SelectContent = (props: SelectContentProps) => { + const { isTop, visibleChange, onChange } = props; + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [treeData, setTreeData] = useState([]); + const [expandedKeys, setExpandedKeys] = useState([]); + const [searchValue, setSearchValue] = useState(''); + const [autoExpandParent, setAutoExpandParent] = useState(true); + const [checkedKeys, setCheckedKeys] = useState({ + checked: [], + halfChecked: [], + }); + const [checkedNodes, setCheckedNodes] = useState({ + connectClusters: [], + connectors: [], + }); + const defaultChecked = useRef({ + checked: [], + halfChecked: [], + }); + + const onExpand = (newExpandedKeys: string[]) => { + setExpandedKeys(newExpandedKeys); + setAutoExpandParent(false); + }; + + const onCheck = (keys: CheckedKeysProps, { checkedNodes }: { checkedNodes: DataNode[] }) => { + const returnData: CheckedNodes = { + connectClusters: [], + connectors: [], + }; + checkedNodes.map((node) => { + if (node.children) { + returnData.connectClusters.push(node.key as number); + } else { + const [id, ...rest] = (node.key as string).split(':'); + returnData.connectors.push({ + connectClusterId: Number(id), + connectorName: rest.join(':'), + }); + } + }); + setCheckedNodes(returnData); + setCheckedKeys( + keys as { + checked: any[]; + halfChecked: any[]; + } + ); + }; + + const onSubmit = () => { + onChange(checkedNodes, `${checkedNodes.connectClusters.length + checkedNodes.connectors.length}项`); + }; + + const onCancel = () => { + visibleChange(false); + setCheckedKeys(defaultChecked.current); + }; + + const onInputChange = (e: React.ChangeEvent) => { + const { value } = e.target; + let newExpandedKeys: React.Key[] = []; + treeData.forEach((item) => { + if (String(item.title).indexOf(value) > -1) { + newExpandedKeys.push(getParentKey(item.key, treeData)); + } + item.children?.forEach((item) => { + if (String(item.title).indexOf(value) > -1) { + newExpandedKeys.push(getParentKey(item.key, treeData)); + } + }); + }); + newExpandedKeys = newExpandedKeys.filter((item, i, self) => item && self.indexOf(item) === i); + setExpandedKeys(newExpandedKeys as React.Key[]); + setSearchValue(value); + setAutoExpandParent(true); + }; + + const filterTreeData = useMemo(() => { + const loop = (data: DataNode[]): DataNode[] => + data.map((item) => { + const strTitle = item.title as string; + const index = strTitle.indexOf(searchValue); + const beforeStr = strTitle.substring(0, index); + const afterStr = strTitle.slice(index + searchValue.length); + const title = + index > -1 ? ( + + {beforeStr} + {searchValue} + {afterStr} + + ) : ( + {strTitle} + ); + if (item.children) { + return { title, key: item.key, children: loop(item.children) }; + } + + return { + title, + key: item.key, + }; + }); + return loop(treeData); + }, [treeData, searchValue]); + + // 获取节点范围列表 + const getScopeList = async () => { + const clustersMap: { [key: string]: DataNode } = {}; + props.scopeList.connectClusters.forEach((connectCluster) => { + clustersMap[connectCluster.id] = { + title: connectCluster.name, + key: connectCluster.id, + children: [], + }; + }); + props.scopeList.connectors.forEach((connector) => { + const targetConnectCluster = clustersMap[connector.connectClusterId]; + if (targetConnectCluster) { + targetConnectCluster.children.push({ + title: connector.connectorName, + key: `${connector.connectClusterId}:${connector.connectorName}`, + }); + } + }); + setTreeData(Object.values(clustersMap)); + }; + + useEffect(() => { + if (isTop) { + setCheckedKeys({ + checked: [], + halfChecked: [], + }); + setCheckedNodes({ + connectClusters: [], + connectors: [], + }); + defaultChecked.current = { + checked: [], + halfChecked: [], + }; + } + }, [isTop]); + + useEffect(() => { + getScopeList(); + }, [props.scopeList]); + + return ( + <> +
{props.title}
+
+
+ +
+ +
+ + +
+
+ + ); +}; + +export default SelectContent; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.less b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.less new file mode 100644 index 00000000..52259ae2 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.less @@ -0,0 +1,13 @@ +.dcloud-tree.connect-dashboard-option-tree { + background: transparent; + height: 198px; + padding: 0 10px; + overflow: auto; + .site-tree-search-value { + color: #f50; + } +} + +.dd-node-scope-module .flx_con .flx_r .custom-scope.dashboard-custom-scope { + height: 292px; +} diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx new file mode 100644 index 00000000..8dd47926 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx @@ -0,0 +1,259 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Utils, AppContainer } from 'knowdesign'; +import api, { MetricType } from '@src/api'; +import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; +import ConnectCard from '@src/components/CardBar/ConnectCard'; +import { useParams } from 'react-router-dom'; +import { FormattedMetricData, formatChartData, MetricInfo } from '@src/constants/chartConfig'; +import ChartOperateBar, { KsHeaderOptions } from '@src/components/ChartOperateBar'; +import ChartDetail from '@src/components/DraggableCharts/Detail'; +import ChartList from '@src/components/DraggableCharts/ChartList'; +import MetricsFilter from './MetricsFilter'; +import SelectContent, { Connector } from './SelectContent'; +import './index.less'; +import { ConnectCluster } from '../Connect/AddConnector'; +import HasConnector from '../Connect/HasConnector'; + +type ChartFilterOptions = Omit; + +const { EventBus } = Utils; +const busInstance = new EventBus(); + +const DraggableCharts = (): JSX.Element => { + const [global] = AppContainer.useGlobalValue(); + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [loading, setLoading] = useState(true); + // const [scopeList, setScopeList] = useState([]); // 节点范围列表 + const [curHeaderOptions, setCurHeaderOptions] = useState(); + const [metricList, setMetricList] = useState<{ [key: string]: (string | number)[] }>({}); + const [metricChartData, setMetricChartData] = useState([]); // 指标图表数据列表 + const [gridNum, setGridNum] = useState(12); // 图表列布局 + const [scopeList, setScopeList] = useState<{ + connectClusters: ConnectCluster[]; + connectors: Connector[]; + }>({ + connectClusters: [], + connectors: [], + }); + const curFetchingTimestamp = useRef(0); + const metricRankList = useRef([]); + const metricFilterRef = useRef(null); + const chartDetailRef = useRef(null); + + // 根据筛选项获取图表信息 + const getMetricChartData = () => { + !curHeaderOptions.isAutoReload && setLoading(true); + const curTimestamp = Date.now(); + curFetchingTimestamp.current = curTimestamp; + + const [startTime, endTime] = curHeaderOptions.rangeTime; + const { connectClusters, connectors } = curHeaderOptions.scopeData.data; + const isTop = curHeaderOptions?.scopeData?.isTop; + const reqBasicBody = { + startTime, + endTime, + topNu: isTop ? curHeaderOptions.scopeData.data : null, + }; + + const getConnectClusterMetrics = + metricList[MetricType.Connect] && (isTop || connectClusters?.length) + ? Utils.post( + api.getConnectClusterMetrics(clusterId), + Object.assign( + { + ...reqBasicBody, + metricsNames: metricList[MetricType.Connect], + }, + isTop + ? {} + : { + connectClusterIdList: connectClusters, + } + ) + ) + : Promise.resolve([]); + const getConnectorMetrics = + metricList[MetricType.Connectors] && (isTop || connectors?.length) + ? Utils.post( + api.getConnectorMetrics(clusterId), + Object.assign( + { + ...reqBasicBody, + metricsNames: metricList[MetricType.Connectors], + }, + isTop ? {} : { connectorNameList: connectors } + ) + ) + : Promise.resolve([]); + + Promise.all([getConnectClusterMetrics, getConnectorMetrics]).then( + (res: any) => { + // 如果当前请求不是最新请求,则不做任何操作 + if (curFetchingTimestamp.current !== curTimestamp) { + return; + } + + // 为保证指标排序结果正确,当指标全部返回后才展示图表 + if (res.length === 1 || (res.length === 2 && res[0] && res[1])) { + const connectClusterData = formatChartData( + res[0], + global.getMetricDefine || {}, + MetricType.Connect, + curHeaderOptions.rangeTime + ) as FormattedMetricData[]; + const connectorData = formatChartData( + res[1], + global.getMetricDefine || {}, + MetricType.Connectors, + curHeaderOptions.rangeTime + ) as FormattedMetricData[]; + // 指标排序 + const formattedMetricData = [...connectClusterData, ...connectorData]; + formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName)); + + setMetricChartData(formattedMetricData); + } else { + setMetricChartData([]); + } + setLoading(false); + }, + () => curFetchingTimestamp.current === curTimestamp && setLoading(false) + ); + }; + + const getScopeList = () => { + const getConnectClusters = Utils.request(api.getConnectClusters(clusterId)); + const getConnectors = Utils.request(api.getConnectors(clusterId)); + Promise.all([getConnectClusters, getConnectors]).then(([connectClusters, connectors]: [ConnectCluster[], Connector[]]) => { + setScopeList({ + connectClusters, + connectors, + }); + }); + }; + + // 筛选项变化或者点击刷新按钮 + const ksHeaderChange = (ksOptions: KsHeaderOptions) => { + const { isAutoReload, gridNum: newGridNum, isRelativeRangeTime, rangeTime, scopeData } = ksOptions; + let newRangeTime = rangeTime; + // 重新渲染图表 + if (newGridNum !== gridNum) { + setGridNum(newGridNum || 12); + busInstance.emit('chartResize'); + } else { + // 如果为相对时间,则当前时间减去 1 分钟,避免最近一分钟的数据还没采集到时前端多补一个点 + if (isRelativeRangeTime) { + newRangeTime = rangeTime.map((timestamp) => timestamp - 60 * 1000) as [number, number]; + } + setCurHeaderOptions({ + isRelativeRangeTime: isRelativeRangeTime, + isAutoReload: isAutoReload, + rangeTime: newRangeTime, + scopeData: scopeData, + }); + } + }; + + // 图表拖拽 + const dragCallback = (oldIndex: number, newIndex: number) => { + const originFrom = metricRankList.current.indexOf(metricChartData[oldIndex].metricName); + const originTarget = metricRankList.current.indexOf(metricChartData[newIndex].metricName); + metricFilterRef.current?.rankChange(originFrom, originTarget); + }; + + // 展开图表详情 + const onExpand = (metricName: string, metricType: MetricType) => { + const linesName = + metricType === MetricType.Connect + ? scopeList.connectClusters.map((cluster) => cluster.id) + : scopeList.connectors.map((connector) => ({ + connectClusterId: connector.connectClusterId, + connectorName: connector.connectorName, + })); + chartDetailRef.current.onOpen(metricType, metricName, linesName); + }; + + // 获取图表指标 + useEffect(() => { + if (Object.values(metricList).some((list) => list.length) && curHeaderOptions) { + getMetricChartData(); + } + }, [curHeaderOptions]); + + useEffect(() => { + if (Object.values(metricList).some((list) => list.length) && curHeaderOptions) { + setLoading(true); + getMetricChartData(); + } + }, [metricList]); + + useEffect(() => { + getScopeList(); + }, []); + + return ( +
+ metricFilterRef.current?.open()} + nodeSelect={{ + name: 'Connect', + customContent: , + }} + /> + { + const res: { [key: string]: (string | number)[] } = {}; + list.forEach(({ type, name, set }) => { + set && (res[type] ? res[type].push(name) : (res[type] = [name])); + }); + setMetricList(res); + }} + onRankChange={(rankList) => { + metricRankList.current = rankList; + }} + /> + + {/* 图表详情 */} + +
+ ); +}; + +const ConnectDashboard = (): JSX.Element => { + const [global] = AppContainer.useGlobalValue(); + return ( + <> +
+ +
+ + <> + + + + + + ); +}; + +export default ConnectDashboard; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx index 55647ae0..8e0bb6fb 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx @@ -79,7 +79,7 @@ const BrokerList: React.FC = (props: any) => {
-
+
{
)} -
+
{/* */} {scene !== 'topicDetail' && (
- +
searchFn()}>
diff --git a/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less b/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less index c13bee61..387d8c49 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.less @@ -92,7 +92,7 @@ .dcloud-table-container::after { box-shadow: none !important; } - .dcloud-pagination{ + .dcloud-pagination { margin-bottom: 0 !important; } } @@ -118,13 +118,14 @@ display: inline-flex; align-items: center; - &-text,&-text-right{ + &-text, + &-text-right { display: inline-block; width: 50px; } - &-text-right{ + &-text-right { text-align: center; - color: #556EE6; + color: #556ee6; } &-img { display: inline-block; @@ -155,15 +156,18 @@ } } -.dcloud-checkbox-table-serch{ +.dcloud-checkbox-table-serch { padding-top: 0; } -.clustom-table-content { - .anticon { - padding: 3px; - border-radius: 50%; - &:hover { - background: rgba(33, 37, 41, 0.04); + +.jobs-self { + .dcloud-table-cell { + .anticon { + padding: 3px; + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); + } } } -} \ No newline at end of file +} diff --git a/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.tsx index a4170af1..4ddcb848 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Jobs/index.tsx @@ -173,7 +173,7 @@ const JobsList: React.FC = (props: any) => {
{/*
*/} -
+
{
-
+
Github: - 5.4K + 5.6K + Star的项目 Know Streaming
diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx index fffbe60f..ec5fab11 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx @@ -1,12 +1,13 @@ -import { Button, Divider, Drawer, Form, Input, InputNumber, Radio, Select, Spin, Space, Utils } from 'knowdesign'; +import { Button, Divider, Drawer, Form, Input, InputNumber, Radio, Select, Spin, Space, Utils, Tabs, Collapse, Empty } from 'knowdesign'; import message from '@src/components/Message'; -import * as React from 'react'; +import React, { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import api from '@src/api'; -import { regClusterName, regUsername } from '@src/constants/reg'; +import { regClusterName, regIpAndPort, regUsername } from '@src/constants/reg'; import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config'; import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem'; - +import { IconFont } from '@knowdesign/icons'; +import notification from '@src/components/Notification'; const LOW_KAFKA_VERSION = '2.8.0'; const CLIENT_PROPERTIES_PLACEHOLDER = `用于创建Kafka客户端进行信息获取的相关配置, 例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置, @@ -20,13 +21,12 @@ word=\\"xxxxxx\\";" } `; -const AccessClusters = (props: any): JSX.Element => { - const { afterSubmitSuccess, clusterInfo, visible } = props; +const { Panel } = Collapse; +const ClusterTabContent = forwardRef((props: any, ref): JSX.Element => { + const { form, clusterInfo, visible } = props; const intl = useIntl(); - const [form] = Form.useForm(); const [loading, setLoading] = React.useState(false); - const [confirmLoading, setConfirmLoading] = React.useState(false); const [curClusterInfo, setCurClusterInfo] = React.useState({}); const [extra, setExtra] = React.useState({ versionExtra: '', @@ -66,6 +66,7 @@ const AccessClusters = (props: any): JSX.Element => { const onCancel = () => { form.resetFields(); setLoading(false); + setCurClusterInfo({}); setExtra({ versionExtra: '', zooKeeperExtra: '', @@ -73,60 +74,6 @@ const AccessClusters = (props: any): JSX.Element => { jmxExtra: '', }); lastFormItemValue.current = { bootstrapServers: '', zookeeper: '', clientProperties: {} }; - props.setVisible && props.setVisible(false); - }; - - const onSubmit = () => { - form.validateFields().then((res) => { - setConfirmLoading(true); - let clientProperties = null; - try { - clientProperties = res.clientProperties && JSON.parse(res.clientProperties); - } catch (err) { - console.error(err); - } - - const params = { - bootstrapServers: res.bootstrapServers, - clientProperties: clientProperties || {}, - description: res.description || '', - jmxProperties: { - jmxPort: res.jmxPort, - maxConn: res.maxConn, - openSSL: res.openSSL || false, - token: res.token, - username: res.username, - }, - kafkaVersion: res.kafkaVersion, - name: res.name, - zookeeper: res.zookeeper || '', - }; - - if (!isNaN(curClusterInfo?.id)) { - Utils.put(api.phyCluster, { - ...params, - id: curClusterInfo?.id, - }) - .then(() => { - message.success('编辑成功'); - afterSubmitSuccess && afterSubmitSuccess(); - onCancel(); - }) - .finally(() => { - setConfirmLoading(false); - }); - } else { - Utils.post(api.phyCluster, params) - .then(() => { - message.success('集群接入成功。注意:新接入集群数据稳定需要1-2分钟'); - afterSubmitSuccess && afterSubmitSuccess(); - onCancel(); - }) - .finally(() => { - setConfirmLoading(false); - }); - } - }); }; const connectTest = () => { @@ -213,39 +160,37 @@ const AccessClusters = (props: any): JSX.Element => { // 获取集群详情数据 React.useEffect(() => { - if (visible) { - if (clusterInfo?.id) { - setLoading(true); + if (clusterInfo?.id && visible) { + setLoading(true); - const resolveJmxProperties = (obj: any) => { - const res = { ...obj }; - try { - const originValue = obj?.jmxProperties; - if (originValue) { - const jmxProperties = JSON.parse(originValue); - typeof jmxProperties === 'object' && jmxProperties !== null && Object.assign(res, jmxProperties); - } - } catch (err) { - console.error('jmxProperties not JSON: ', err); + const resolveJmxProperties = (obj: any) => { + const res = { ...obj }; + try { + const originValue = obj?.jmxProperties; + if (originValue) { + const jmxProperties = JSON.parse(originValue); + typeof jmxProperties === 'object' && jmxProperties !== null && Object.assign(res, jmxProperties); } - return res; - }; + } catch (err) { + console.error('jmxProperties not JSON: ', err); + } + return res; + }; - Utils.request(api.getPhyClusterBasic(clusterInfo.id)) - .then((res: any) => { - setCurClusterInfo(resolveJmxProperties(res)); - }) - .catch((err) => { - setCurClusterInfo(resolveJmxProperties(clusterInfo)); - }) - .finally(() => { - setLoading(false); - }); - } else { - setCurClusterInfo({}); - } + Utils.request(api.getPhyClusterBasic(clusterInfo.id)) + .then((res: any) => { + setCurClusterInfo(resolveJmxProperties(res)); + }) + .catch((err) => { + setCurClusterInfo(resolveJmxProperties(clusterInfo)); + }) + .finally(() => { + setLoading(false); + }); + } else { + setCurClusterInfo({}); } - }, [visible, clusterInfo]); + }, [clusterInfo, visible]); const validators = { name: async (_: any, value: string) => { @@ -358,13 +303,510 @@ const AccessClusters = (props: any): JSX.Element => { }, }; + useImperativeHandle(ref, () => ({ + onCancel, + })); + + return ( + + + + + + {extra.bootstrapExtra}} + validateTrigger={'onBlur'} + rules={[ + { + required: true, + validator: validators.bootstrapServers, + }, + ]} + > + + + {extra.zooKeeperExtra}} + validateTrigger={'onBlur'} + rules={[ + { + validator: validators.zookeeper, + }, + ]} + > + + + +
+
+ + + + + + +
+ + + None + Password Authentication + + + + {({ getFieldValue }) => { + return getFieldValue('openSSL') ? ( +
+ +
+ + + + + + +
+
+ ) : null; + }} +
+
+
+ {extra.versionExtra}} + rules={[ + { + required: true, + validator: validators.kafkaVersion, + }, + ]} + > + + + + +
+ { + form.setFieldsValue({ clientProperties }); + form.validateFields(['clientProperties']); + }} + onBlur={() => { + form.validateFields(['clientProperties']).then(() => { + const bootstrapServers = form.getFieldValue('bootstrapServers'); + const zookeeper = form.getFieldValue('zookeeper'); + const clientProperties = form.getFieldValue('clientProperties'); + + if ( + clientProperties && + clientProperties !== lastFormItemValue.current.clientProperties && + (!!bootstrapServers || !!zookeeper) + ) { + connectTest() + .then(() => { + lastFormItemValue.current.clientProperties = clientProperties; + }) + .catch(() => { + message.error('连接失败'); + }); + } + }); + }} + /> +
+
+ + + + +
+ ); +}); + +const ConnectorForm = (props: { + initFieldsValue: any; + kafkaVersion: string[]; + setSelectedTabKey: React.Dispatch>; + getConnectClustersList: any; + clusterInfo: any; +}) => { + const { initFieldsValue, kafkaVersion, setSelectedTabKey, getConnectClustersList, clusterInfo } = props; + const [form] = Form.useForm(); + + const validators = { + name: async (_: any, value: string) => { + if (!value) { + return Promise.reject('集群名称不能为空'); + } + if (value === initFieldsValue?.name) { + return Promise.resolve(); + } + if (!new RegExp(regClusterName).test(value)) { + return Promise.reject('集群名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~'); + } + return Utils.request(api.getConnectClusterBasicExit(clusterInfo.id, value)) + .then((res: any) => { + const data = res || {}; + return data?.exist ? Promise.reject('集群名称重复') : Promise.resolve(); + }) + .catch(() => Promise.reject('连接超时! 请重试或检查服务')); + }, + address: async (_: any, value: string) => { + if (!value) { + return Promise.reject('请输入集群地址'); + } + if (!new RegExp(regIpAndPort).test(value)) { + return Promise.reject('格式错误,正确示例:http://1.1.1.1, http://1.1.1.1:65535, https://1.1.1.1, https://1.1.1.1:65535'); + } + return Promise.resolve(); + }, + }; + + const onFinish = (values: any) => { + const params = { + ...values, + id: initFieldsValue?.id, + }; + Utils.put(api.batchConnectClusters, [params]) + .then((res) => { + // setSelectedTabKey(undefined); + getConnectClustersList(); + notification.success({ + message: '修改Connect集群成功', + }); + }) + .catch((error) => { + notification.success({ + message: '修改Connect集群失败', + }); + }); + }; + + const onCancel = () => { + setSelectedTabKey(undefined); + try { + const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {}; + form.setFieldsValue({ ...initFieldsValue, jmxPort: jmxPortInfo.jmxPort }); + } catch { + form.setFieldsValue({ ...initFieldsValue }); + } + }; + + useLayoutEffect(() => { + try { + const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {}; + form.setFieldsValue({ ...initFieldsValue, jmxPort: jmxPortInfo.jmxPort }); + } catch { + form.setFieldsValue({ ...initFieldsValue }); + } + }, []); + return ( <> - + + + + + + + + + + {/* + + + + + 应用于所有Broker + 应用于特定Broker + + + + {({ getFieldValue }) => + getFieldValue('priority') === 'allBroker' ? ( + + + + ) : ( + + + + ) + } + */} +
+ + + + + + +
+ + + + + + + + + ); +}; + +const ConnectTabContent = forwardRef((props: any, ref) => { + const { kafkaVersion, clusterInfo, visible } = props; + const [connectors, setConnectors] = useState([]); + const [selectedTabKey, setSelectedTabKey] = useState(undefined); + const [loading, setLoading] = useState(true); + const genExtra = (connector: any) => ( + { + e.stopPropagation(); + Utils.delete(api.deleteConnectClusters, { + params: { + connectClusterId: connector.id, + }, + }).then((res) => { + // setSelectedTabKey(undefined); + getConnectClustersList(); + notification.success({ + message: '删除Connect集群成功', + }); + }); + }} + /> + ); + + const getConnectClustersList = () => { + setLoading(true); + Utils.request(api.getConnectClusters(clusterInfo.id)) + .then((res: any) => { + setConnectors(res || []); + }) + .finally(() => { + setLoading(false); + }); + }; + + useEffect(() => { + visible && getConnectClustersList(); + }, [visible]); + + return ( + + {connectors?.length ? ( + } + onChange={(key: string) => { + setSelectedTabKey(key); + }} + > + {connectors.map((connector, i) => { + return ( + + + + ); + })} + + ) : ( + + )} + + ); +}); + +interface AccessClusterDrawerProps { + visible: boolean; + setVisible: (visible: boolean) => void; + clusterInfo: any; + afterSubmitSuccess: () => void; + kafkaVersion: string[]; + title?: string; +} + +const AccessClusterDrawer = (props: AccessClusterDrawerProps) => { + const { afterSubmitSuccess, clusterInfo, visible, setVisible, kafkaVersion } = props; + const intl = useIntl(); + const [form] = Form.useForm(); + const [confirmLoading, setConfirmLoading] = useState(false); + const clusterRef = useRef(null); + const [positionType, setPositionType] = useState('cluster'); + + const onCancel = () => { + setPositionType('cluster'); + form.resetFields(); + clusterRef.current.onCancel(); + setVisible && setVisible(false); + }; + + const callback = (key: any) => { + setPositionType(key); + }; + + const onSubmit = () => { + form.validateFields().then((res) => { + setConfirmLoading(true); + let clientProperties = null; + try { + clientProperties = res.clientProperties && JSON.parse(res.clientProperties); + } catch (err) { + console.error(err); + } + + const params = { + bootstrapServers: res.bootstrapServers, + clientProperties: clientProperties || {}, + description: res.description || '', + jmxProperties: { + jmxPort: res.jmxPort, + maxConn: res.maxConn, + openSSL: res.openSSL || false, + token: res.token, + username: res.username, + }, + kafkaVersion: res.kafkaVersion, + name: res.name, + zookeeper: res.zookeeper || '', + }; + + if (!isNaN(clusterInfo?.id)) { + Utils.put(api.phyCluster, { + ...params, + id: clusterInfo?.id, + }) + .then(() => { + message.success('编辑成功'); + afterSubmitSuccess && afterSubmitSuccess(); + onCancel(); + }) + .finally(() => { + setConfirmLoading(false); + }); + } else { + Utils.post(api.phyCluster, params) + .then(() => { + message.success('集群接入成功。注意:新接入集群数据稳定需要1-2分钟'); + afterSubmitSuccess && afterSubmitSuccess(); + onCancel(); + }) + .finally(() => { + setConfirmLoading(false); + }); + } + }); + }; + + return ( +
- } - title={intl.formatMessage({ id: props.title || clusterInfo?.id ? 'edit.cluster' : 'access.cluster' })} - visible={props.visible} - placement="right" - width={480} - > - -
- - - - {extra.bootstrapExtra}} - validateTrigger={'onBlur'} - rules={[ - { - required: true, - validator: validators.bootstrapServers, - }, - ]} - > - - - {extra.zooKeeperExtra}} - validateTrigger={'onBlur'} - rules={[ - { - validator: validators.zookeeper, - }, - ]} - > - - - -
-
- - - - - - -
- - - None - Password Authentication - - - - {({ getFieldValue }) => { - return getFieldValue('openSSL') ? ( -
- -
- - - - - - -
-
- ) : null; - }} -
-
-
- {extra.versionExtra}} - rules={[ - { - required: true, - validator: validators.kafkaVersion, - }, - ]} - > - - - - -
- { - form.setFieldsValue({ clientProperties }); - form.validateFields(['clientProperties']); - }} - onBlur={(value: any) => { - form.validateFields(['clientProperties']).then(() => { - const bootstrapServers = form.getFieldValue('bootstrapServers'); - const zookeeper = form.getFieldValue('zookeeper'); - const clientProperties = form.getFieldValue('clientProperties'); - - if ( - clientProperties && - clientProperties !== lastFormItemValue.current.clientProperties && - (!!bootstrapServers || !!zookeeper) - ) { - connectTest() - .then((res: any) => { - lastFormItemValue.current.clientProperties = clientProperties; - }) - .catch((err) => { - message.error('连接失败'); - }); - } - }); - }} - /> -
-
- - - - -
- - + ) : null + } + title={intl.formatMessage({ id: props.title || clusterInfo?.id ? 'edit.cluster' : 'access.cluster' })} + visible={visible} + placement="right" + width={480} + > + + + + + {clusterInfo?.id && ( + + + + )} + + ); }; -export default AccessClusters; +export default AccessClusterDrawer; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less index 83230504..fc5dbfa3 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less @@ -466,6 +466,7 @@ max-width: 180px; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; font-family: @font-family-bold; font-size: 18px; color: #495057; @@ -633,6 +634,12 @@ } } } +.drawer-access-cluster { + .dcloud-drawer-title { + height: 27px; + line-height: 27px; + } +} .drawer-content { .dcloud-form-item-extra { @@ -674,6 +681,41 @@ } } } + .cluster-connect-custom-collapse { + background-color: transparent; + .cluster-connect-custom-panel, + .cluster-connect-custom-panel:last-child { + margin-bottom: 8px; + overflow: hidden; + background: #f8f9fa; + border: 0px; + border-radius: 8px; + .dcloud-collapse-header { + padding: 8px 12px; + font-size: 14px; + color: #495057; + .dcloud-collapse-extra { + opacity: 0; + transition: opacity 0.2s ease; + } + } + &:hover .dcloud-collapse-extra { + opacity: 1; + } + &:not(.dcloud-collapse-item-active) { + .dcloud-collapse-header:hover { + background: #f1f3ff; + } + } + .dcloud-collapse-content-box { + padding: 12px; + } + } + .dcloud-collapse-header .dcloud-collapse-arrow { + margin-right: 8px !important; + font-size: 16px; + } + } } .empty-page { diff --git a/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx index d361f3e7..9919afdd 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx @@ -207,7 +207,7 @@ const SecurityACLs = (): JSX.Element => {
-
+
getACLs()}> diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx index b9337f09..63ab9bd6 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx @@ -1,20 +1,13 @@ -import { Col, Row, SingleChart, Utils, Modal, Spin, Empty, AppContainer, Tooltip } from 'knowdesign'; -import { IconFont } from '@knowdesign/icons'; +import { SingleChart, Utils, Spin, AppContainer, Tooltip } from 'knowdesign'; import React, { useEffect, useRef, useState } from 'react'; import { arrayMoveImmutable } from 'array-move'; import api from '@src/api'; import { useParams } from 'react-router-dom'; -import { - OriginMetricData, - FormattedMetricData, - formatChartData, - supplementaryPoints, - resolveMetricsRank, - MetricInfo, -} from '@src/constants/chartConfig'; +import { OriginMetricData, FormattedMetricData, formatChartData, supplementaryPoints } from '@src/constants/chartConfig'; import { MetricType } from '@src/api'; import { getDataUnit } from '@src/constants/chartConfig'; import ChartOperateBar, { KsHeaderOptions } from '@src/components/ChartOperateBar'; +import MetricsFilter from '@src/components/ChartOperateBar/MetricSelect'; import RenderEmpty from '@src/components/RenderEmpty'; import DragGroup from '@src/components/DragGroup'; import { getChartConfig } from './config'; @@ -56,7 +49,6 @@ const DEFUALT_METRIC_NEED_METRICS = [DEFAULT_METRIC, 'TotalLogSize', 'TotalProdu const DetailChart = (props: { children: JSX.Element }): JSX.Element => { const [global] = AppContainer.useGlobalValue(); const { clusterId } = useParams<{ clusterId: string }>(); - const [metricList, setMetricList] = useState([]); // 指标列表 const [selectedMetricNames, setSelectedMetricNames] = useState<(string | number)[]>([]); // 默认选中的指标的列表 const [metricDataList, setMetricDataList] = useState([]); const [messagesInMetricData, setMessagesInMetricData] = useState({ @@ -72,6 +64,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => { messagesIn: 0, other: 0, }); + const metricFilterRef = useRef(null); // 筛选项变化或者点击刷新按钮 const ksHeaderChange = (ksOptions: KsHeaderOptions) => { @@ -86,65 +79,9 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => { }); }; - // 更新 rank - const updateRank = (metricList: MetricInfo[]) => { - const { list, listInfo, shouldUpdate } = resolveMetricsRank(metricList); - metricRankList.current = list; - if (shouldUpdate) { - updateMetricList(listInfo); - } - }; - - // 获取指标列表 - const getMetricList = () => { - Utils.request(api.getDashboardMetricList(clusterId, MetricType.Cluster)).then((res: MetricInfo[] | null) => { - if (!res) return; - const supportMetrics = res.filter((metric) => metric.support); - const selectedMetrics = supportMetrics.filter((metric) => metric.set).map((metric) => metric.name); - !selectedMetrics.includes(DEFAULT_METRIC) && selectedMetrics.push(DEFAULT_METRIC); - updateRank([...supportMetrics]); - setMetricList(supportMetrics); - setSelectedMetricNames(selectedMetrics); - }); - }; - - // 更新指标 - const updateMetricList = (metricDetailDTOList: { metric: string; rank: number; set: boolean }[]) => { - return Utils.request(api.getDashboardMetricList(clusterId, MetricType.Cluster), { - method: 'POST', - data: { - metricDetailDTOList, - }, - }); - }; - - // 指标选中项更新回调 - const indicatorChangeCallback = (newMetricNames: (string | number)[]) => { - const updateMetrics: { metric: string; set: boolean; rank: number }[] = []; - // 需要选中的指标 - newMetricNames.forEach( - (name) => - !selectedMetricNames.includes(name) && - updateMetrics.push({ metric: name as string, set: true, rank: metricList.find(({ name: metric }) => metric === name)?.rank }) - ); - // 取消选中的指标 - selectedMetricNames.forEach( - (name) => - !newMetricNames.includes(name) && - updateMetrics.push({ metric: name as string, set: false, rank: metricList.find(({ name: metric }) => metric === name)?.rank }) - ); - const requestPromise = Object.keys(updateMetrics).length ? updateMetricList(updateMetrics) : Promise.resolve(); - requestPromise.then( - () => getMetricList(), - () => getMetricList() - ); - - return requestPromise; - }; - // 获取 metric 列表的图表数据 const getMetricData = () => { - if (!selectedMetricNames.length) return; + if (!selectedMetricNames?.length) return; !curHeaderOptions.isAutoReload && setChartLoading(true); const [startTime, endTime] = curHeaderOptions.rangeTime; @@ -286,7 +223,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => { const originTarget = metricRankList.current.indexOf(metricDataList[newIndex].metricName); const newList = arrayMoveImmutable(metricRankList.current, originFrom, originTarget); metricRankList.current = newList; - updateMetricList(newList.map((metric, rank) => ({ metric, rank, set: metricList.find(({ name }) => metric === name)?.set || false }))); + metricFilterRef.current?.rankChange(originFrom, originTarget); setMetricDataList(arrayMoveImmutable(metricDataList, oldIndex, newIndex)); }; @@ -302,29 +239,23 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => { }, [curHeaderOptions]); useEffect(() => { - getMetricList(); setTimeout(() => observeDashboardWidthChange()); }, []); return (
metricFilterRef.current?.open()} onChange={ksHeaderChange} hideNodeScope={true} hideGridSelect={true} - metricSelect={{ - hide: false, - metricType: MetricType.Cluster, - tableData: metricList, - selectedRows: selectedMetricNames, - checkboxProps: (record: MetricInfo) => { - return record.name === DEFAULT_METRIC - ? { - disabled: true, - } - : {}; - }, - submitCallback: indicatorChangeCallback, + /> + { + metricRankList.current = rankList; + setSelectedMetricNames(list); }} /> diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx index deaaab4b..01af934a 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx @@ -32,6 +32,14 @@ export const dimensionMap = { label: 'Zookeeper', href: '/zookeeper', }, + 5: { + label: 'Connect', + href: '/connect', + }, + 6: { + label: 'Connector', + href: '/connect/connectors', + }, } as any; const toLowerCase = (name = '') => { @@ -78,6 +86,15 @@ const CONFIG_ITEM_DETAIL_DESC = { SentRate: (valueGroup: any) => { return `Zookeeper 首发包数小于 ${valueGroup?.ratio * 100}% 总容量`; }, + TaskStartupFailurePercentage: (valueGroup: any) => { + return `任务启动失败概率 小于 ${valueGroup?.value * 100}%`; + }, + ConnectorFailedTaskCount: (valueGroup: any) => { + return `失败状态的任务数量 小于 ${valueGroup?.value}`; + }, + ConnectorUnassignedTaskCount: (valueGroup: any) => { + return `未被分配的任务数量 小于 ${valueGroup?.value}`; + }, }; export const getConfigItemDetailDesc = (item: keyof typeof CONFIG_ITEM_DETAIL_DESC, valueGroup: any) => { @@ -145,9 +162,9 @@ export const getDetailColumn = (clusterId: number) => [ // eslint-disable-next-line react/display-name render: (text: number, record: any) => { return dimensionMap[text] ? ( - {toLowerCase(record?.dimensionName)} + {record?.dimensionDisplayName} ) : ( - toLowerCase(record?.dimensionName) + record?.dimensionDisplayName ); }, }, @@ -219,9 +236,9 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string) // eslint-disable-next-line react/display-name render: (text: number, record: any) => { return dimensionMap[text] ? ( - {toLowerCase(record?.dimensionName)} + {record?.dimensionDisplayName} ) : ( - toLowerCase(record?.dimensionName) + record?.dimensionDisplayName ); }, }, @@ -365,6 +382,33 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string)
); } + case 'TaskStartupFailurePercentage': { + return ( +
+ {'>'} + {getFormItem({ configItem, percent: true })} + 则不通过 +
+ ); + } + case 'ConnectorFailedTaskCount': { + return ( +
+ {'>'} + {getFormItem({ configItem, attrs: { min: 0, max: 99998 } })} + 则不通过 +
+ ); + } + case 'ConnectorUnassignedTaskCount': { + return ( +
+ {'>'} + {getFormItem({ configItem, attrs: { min: 0, max: 99998 } })} + 则不通过 +
+ ); + } default: { return <>; } diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx index b2be16bd..8e35af9a 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx @@ -149,13 +149,13 @@ const AutoPage = (props: any) => { title: 'Partitions', dataIndex: 'partitionNum', key: 'partitionNum', - width: 95, + width: 100, }, { title: 'Replications', dataIndex: 'replicaNum', key: 'replicaNum', - width: 95, + width: 100, }, { title: '健康状态', @@ -163,7 +163,7 @@ const AutoPage = (props: any) => { key: 'HealthState', sorter: true, // 设计图上量出来的是144,但做的时候发现写144 header部分的sort箭头不出来,所以临时调大些 - width: 170, + width: 100, render: (value: any, record: any) => { return calcCurValue(record, 'HealthState'); }, @@ -289,7 +289,7 @@ const AutoPage = (props: any) => {
-
+
{/* 批量扩缩副本 */} diff --git a/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/index.tsx index c7b56f53..a5b29b68 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Zookeeper/index.tsx @@ -78,7 +78,7 @@ const ZookeeperList: React.FC = () => {
-
+
Date: Fri, 16 Dec 2022 13:39:51 +0800 Subject: [PATCH 076/150] =?UTF-8?q?=E8=B0=83=E6=95=B4v3.2=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=AE=B9=E5=99=A8=E5=8C=96=E9=83=A8=E7=BD=B2=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- km-dist/docker/manager/application.yml | 119 +++++++++ km-dist/docker/manager/logback-spring.xml | 236 ++++++++++++++++++ km-dist/docker/mysql/initsql | 118 ++++++++- km-dist/helm/Chart.yaml | 6 +- km-dist/helm/charts/elasticsearch/values.yaml | 1 + .../helm/charts/knowstreaming-web/values.yaml | 2 +- .../charts/ksmysql/templates/statefulset.yaml | 2 +- km-dist/helm/templates/configmap.yaml | 15 ++ km-dist/helm/templates/deployment.yaml | 2 + km-dist/helm/values.yaml | 5 +- 10 files changed, 497 insertions(+), 9 deletions(-) create mode 100644 km-dist/docker/manager/application.yml create mode 100644 km-dist/docker/manager/logback-spring.xml diff --git a/km-dist/docker/manager/application.yml b/km-dist/docker/manager/application.yml new file mode 100644 index 00000000..5e7fec8e --- /dev/null +++ b/km-dist/docker/manager/application.yml @@ -0,0 +1,119 @@ +server: + port: 80 # 服务端口 + tomcat: + accept-count: 1000 + max-connections: 10000 + +spring: + application: + name: know-streaming + profiles: + active: dev + main: + allow-bean-definition-overriding: true + jackson: + time-zone: GMT+8 + datasource: + know-streaming: # know-streaming 自身数据库的配置 + jdbc-url: jdbc:mariadb://SERVER_MYSQL_ADDRESS/SERVER_MYSQL_DB?useUnicode=true&characterEncoding=utf8&jdbcCompliantTruncation=true&allowMultiQueries=true&useSSL=false&alwaysAutoGeneratedKeys=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true + username: SERVER_MYSQL_USER + password: SERVER_MYSQL_PASSWORD + driver-class-name: org.mariadb.jdbc.Driver + maximum-pool-size: 20 + idle-timeout: 30000 + connection-test-query: SELECT 1 + logi-job: # know-streaming 依赖的 logi-job 模块的数据库的配置,默认与 know-streaming 的数据库配置保持一致即可 + enable: true + jdbc-url: jdbc:mariadb://SERVER_MYSQL_ADDRESS/SERVER_MYSQL_DB?useUnicode=true&characterEncoding=utf8&jdbcCompliantTruncation=true&allowMultiQueries=true&useSSL=false&alwaysAutoGeneratedKeys=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true + username: SERVER_MYSQL_USER + password: SERVER_MYSQL_PASSWORD + driver-class-name: org.mariadb.jdbc.Driver + max-lifetime: 60000 + init-sql: true + init-thread-num: 20 + max-thread-num: 50 + log-expire: 3 # 日志保存天数,以天为单位 + app-name: know-streaming + claim-strategy: com.didiglobal.logi.job.core.consensual.RandomConsensual + logi-security: # know-streaming 依赖的 logi-security 模块的数据库的配置,默认与 know-streaming 的数据库配置保持一致即可 + jdbc-url: jdbc:mariadb://SERVER_MYSQL_ADDRESS/SERVER_MYSQL_DB?useUnicode=true&characterEncoding=utf8&jdbcCompliantTruncation=true&allowMultiQueries=true&useSSL=false&alwaysAutoGeneratedKeys=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true + username: SERVER_MYSQL_USER + password: SERVER_MYSQL_PASSWORD + driver-class-name: org.mariadb.jdbc.Driver + app-name: know-streaming + resource-extend-bean-name: myResourceExtendImpl + login-extend-bean-name: logiSecurityDefaultLoginExtendImpl + +logging: + config: classpath:logback-spring.xml + +# 线程池大小相关配置 +thread-pool: + scheduled: + thread-num: 2 # @Scheduled任务的线程池大小,默认是一个 + + collector: # 采集模块的配置 + future-util: # 采集模块线程池配置 + num: 3 # 线程池个数 + thread-num: 64 # 每个线程池核心线程数 + queue-size: 10000 # 每个线程池队列大小 + select-suitable-enable: true # 任务是否自动选择合适的线程池,非主要,可不修改 + suitable-queue-size: 1000 # 线程池理想的队列大小,非主要,可不修改 + + task: # 任务模块的配置 + metrics: # metrics采集任务配置 + thread-num: 18 # metrics采集任务线程池核心线程数 + queue-size: 180 # metrics采集任务线程池队列大小 + metadata: # metadata同步任务配置 + thread-num: 27 # metadata同步任务线程池核心线程数 + queue-size: 270 # metadata同步任务线程池队列大小 + common: # 剩余其他任务配置 + thread-num: 15 # 剩余其他任务线程池核心线程数 + queue-size: 150 # 剩余其他任务线程池队列大小 + es: + search: # es查询线程池 + thread-num: 20 # 线程池大小 + queue-size: 10000 # 队列大小 + + + +# 客户端池大小相关配置 +client-pool: + kafka-consumer: + min-idle-client-num: 2 # 最小空闲客户端数 + max-idle-client-num: 20 # 最大空闲客户端数 + max-total-client-num: 20 # 最大客户端数 + borrow-timeout-unit-ms: 5000 # 租借超时时间,单位秒 + kafka-admin: + client-cnt: 1 # 每个Kafka集群创建的KafkaAdminClient数 + + + +# ES客户端配置 +es: + client: + address: SERVER_ES_ADDRESS + client-cnt: 10 + io-thread-cnt: 2 + max-retry-cnt: 5 + index: + expire: 15 # 索引过期天数,15表示超过15天的索引会被KS过期删除 + + +# 普罗米修斯指标导出相关配置 +management: + endpoints: + web: + base-path: /metrics + exposure: + include: '*' + health: + elasticsearch: + enabled: false + metrics: + export: + prometheus: + descriptions: true + enabled: true + tags: + application: know-streaming diff --git a/km-dist/docker/manager/logback-spring.xml b/km-dist/docker/manager/logback-spring.xml new file mode 100644 index 00000000..91c3af7f --- /dev/null +++ b/km-dist/docker/manager/logback-spring.xml @@ -0,0 +1,236 @@ + + + logback + + + + + + + + + + + + + + info + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + + + + + ${log.path}/log_debug.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + ${log.path}/log_debug_%d{yyyy-MM-dd}.%i.log + + 100MB + + + 2 + 5GB + + + + debug + ACCEPT + DENY + + + + + + + ${log.path}/log_info.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + ${log.path}/log_info_%d{yyyy-MM-dd}.%i.log + + 100MB + + + 7 + + + + info + ACCEPT + DENY + + + + + + + ${log.path}/log_warn.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${log.path}/log_warn_%d{yyyy-MM-dd}.%i.log + + 100MB + + + 7 + + + + warn + ACCEPT + DENY + + + + + + + + ${log.path}/log_error.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${log.path}/log_error_%d{yyyy-MM-dd}.%i.log + + 100MB + + + 7 + + + + ERROR + ACCEPT + DENY + + + + + + ${log.path}/es/es.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${log.path}/es/es_%d{yyyy-MM-dd}.%i.log + + 100MB + + 3 + + + + + + ${log.path}/metric/metrics.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${log.path}/metric/metrics_%d{yyyy-MM-dd}.%i.log + + 100MB + + 3 + + + + + + ${log.path}/task/task.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${log.path}/task/task_%d{yyyy-MM-dd}.%i.log + + 100MB + + 3 + + + + + + ${log.path}/logIJob/logIJob.log + + ${log.path}/logIJob/logIJob.log.%d{yyyy-MM-dd} + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/km-dist/docker/mysql/initsql b/km-dist/docker/mysql/initsql index d5916260..3c8ea159 100644 --- a/km-dist/docker/mysql/initsql +++ b/km-dist/docker/mysql/initsql @@ -1,3 +1,107 @@ +-- KS-KM自身的SQL,KS-KM依赖 Logi-Job 和 Logi-Security,因此另外两个ddl sql文件也需要执行 +DROP TABLE IF EXISTS `ks_kc_connect_cluster`; +CREATE TABLE `ks_kc_connect_cluster` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Connect集群ID', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '集群名称', + `group_name` varchar(128) NOT NULL DEFAULT '' COMMENT '集群Group名称', + `cluster_url` varchar(1024) NOT NULL DEFAULT '' COMMENT '集群地址', + `member_leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL地址', + `version` varchar(64) NOT NULL DEFAULT '' COMMENT 'connect版本', + `jmx_properties` text COMMENT 'JMX配置', + `state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '集群使用的消费组状态,也表示集群状态:-1 Unknown,0 ReBalance,1 Active,2 Dead,3 Empty', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '接入时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_id_group_name` (`id`,`group_name`), + UNIQUE KEY `uniq_name_kafka_cluster` (`name`,`kafka_cluster_phy_id`), + KEY `idx_kafka_cluster_phy_id` (`kafka_cluster_phy_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Connect集群信息表'; + +DROP TABLE IF EXISTS `ks_kc_connector`; +CREATE TABLE `ks_kc_connector` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `connector_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector名称', + `connector_class_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector类', + `connector_type` varchar(32) NOT NULL DEFAULT '' COMMENT 'Connector类型', + `state` varchar(45) NOT NULL DEFAULT '' COMMENT '状态', + `topics` text COMMENT '访问过的Topics', + `task_count` int(11) NOT NULL DEFAULT '0' COMMENT '任务数', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_connect_cluster_id_connector_name` (`connect_cluster_id`,`connector_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Connector信息表'; + +DROP TABLE IF EXISTS `ks_kc_worker`; +CREATE TABLE `ks_kc_worker` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `member_id` varchar(512) NOT NULL DEFAULT '' COMMENT '成员ID', + `host` varchar(128) NOT NULL DEFAULT '' COMMENT '主机名', + `jmx_port` int(16) NOT NULL DEFAULT '-1' COMMENT 'Jmx端口', + `url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'URL信息', + `leader_url` varchar(1024) NOT NULL DEFAULT '' COMMENT 'leaderURL信息', + `leader` int(16) NOT NULL DEFAULT '0' COMMENT '状态: 1是leader,0不是leader', + `worker_id` varchar(128) NOT NULL COMMENT 'worker地址', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_id_member_id` (`connect_cluster_id`,`member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='worker信息表'; + +DROP TABLE IF EXISTS `ks_kc_worker_connector`; +CREATE TABLE `ks_kc_worker_connector` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `kafka_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Kafka集群ID', + `connect_cluster_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT 'Connect集群ID', + `connector_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'Connector名称', + `worker_member_id` varchar(256) NOT NULL DEFAULT '', + `task_id` int(16) NOT NULL DEFAULT '-1' COMMENT 'Task的ID', + `state` varchar(128) DEFAULT NULL COMMENT '任务状态', + `worker_id` varchar(128) DEFAULT NULL COMMENT 'worker信息', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_relation` (`connect_cluster_id`,`connector_name`,`task_id`,`worker_member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Worker和Connector关系表'; + +DROP TABLE IF EXISTS `ks_km_zookeeper`; +CREATE TABLE `ks_km_zookeeper` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT '物理集群ID', + `host` varchar(128) NOT NULL DEFAULT '' COMMENT 'zookeeper主机名', + `port` int(16) NOT NULL DEFAULT '-1' COMMENT 'zookeeper端口', + `role` varchar(16) NOT NULL DEFAULT '' COMMENT '角色, leader follower observer', + `version` varchar(128) NOT NULL DEFAULT '' COMMENT 'zookeeper版本', + `status` int(16) NOT NULL DEFAULT '0' COMMENT '状态: 1存活,0未存活,11存活但是4字命令使用不了', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_phy_id_host_port` (`cluster_phy_id`,`host`, `port`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Zookeeper信息表'; + + +DROP TABLE IF EXISTS `ks_km_group`; +CREATE TABLE `ks_km_group` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT '集群id', + `name` varchar(192) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group名称', + `member_count` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数', + `topic_members` text CHARACTER SET utf8 COMMENT 'group消费的topic列表', + `partition_assignor` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '分配策略', + `coordinator_id` int(11) NOT NULL COMMENT 'group协调器brokerId', + `type` int(11) NOT NULL COMMENT 'group类型 0:consumer 1:connector', + `state` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '状态', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_phy_id_name` (`cluster_phy_id`,`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Group信息表'; + DROP TABLE IF EXISTS `ks_km_broker`; CREATE TABLE `ks_km_broker` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', @@ -255,6 +359,7 @@ CREATE TABLE `ks_km_physical_cluster` ( `kafka_version` varchar(32) NOT NULL DEFAULT '' COMMENT 'kafka版本', `client_properties` text COMMENT 'Kafka客户端配置', `jmx_properties` text COMMENT 'JMX配置', + `zk_properties` text COMMENT 'ZK配置', `description` text COMMENT '备注', `auth_type` int(11) NOT NULL DEFAULT '0' COMMENT '认证类型,-1未知,0:无认证,', `run_state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '运行状态, 0表示未监控, 1监控中,有ZK,2:监控中,无ZK', @@ -352,7 +457,6 @@ CREATE TABLE `ks_km_app_node` ( PRIMARY KEY (`id`), KEY `idx_app_host` (`app_name`,`host_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='km集群部署的node信息'; - -- Logi-Job模块的sql,安装KS-KM需要执行该sql @@ -677,13 +781,21 @@ CREATE TABLE `logi_security_config` KEY `idx_group_name` (`value_group`,`value_name`) ) ENGINE=InnoDB AUTO_INCREMENT=1592 DEFAULT CHARSET=utf8 COMMENT='logi配置项'; - +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_FAILED_TASK_COUNT', '{\"value\" : 1}', 'connector失败状态的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_UNASSIGNED_TASK_COUNT', '{\"value\" : 1}', 'connector未被分配的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE', '{\"value\" : 0.05}', 'Connect集群任务启动失败概率', 'admin'); INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`,`value_group`,`value_name`,`value`,`description`,`operator`) VALUES (-1,'HEALTH','HC_CLUSTER_NO_CONTROLLER','{ \"value\": 1, \"weight\": 30 } ','集群Controller数正常','know-streaming'); INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`,`value_group`,`value_name`,`value`,`description`,`operator`) VALUES (-1,'HEALTH','HC_BROKER_REQUEST_QUEUE_FULL','{ \"value\": 10, \"weight\": 20 } ','Broker-RequestQueueSize指标','know-streaming'); INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`,`value_group`,`value_name`,`value`,`description`,`operator`) VALUES (-1,'HEALTH','HC_BROKER_NETWORK_PROCESSOR_AVG_IDLE_TOO_LOW','{ \"value\": 0.8, \"weight\": 20 } ','Broker-NetworkProcessorAvgIdlePercent指标','know-streaming'); INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`,`value_group`,`value_name`,`value`,`description`,`operator`) VALUES (-1,'HEALTH','HC_GROUP_RE_BALANCE_TOO_FREQUENTLY','{\n \"latestMinutes\": 10,\n \"detectedTimes\": 8,\n \"weight\": 10\n}\n','Group的re-balance频率','know-streaming'); INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`,`value_group`,`value_name`,`value`,`description`,`operator`) VALUES (-1,'HEALTH','HC_TOPIC_NO_LEADER','{ \"value\": 1, \"weight\": 10 } ','Topic 无Leader数','know-stream'); INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`,`value_group`,`value_name`,`value`,`description`,`operator`) VALUES (-1,'HEALTH','HC_TOPIC_UNDER_REPLICA_TOO_LONG','{ \"latestMinutes\": 10, \"detectedTimes\": 8, \"weight\": 10 } ','Topic 未同步持续时间','know-streaming'); +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'); -- 初始化权限 INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('1593', '多集群管理', '0', '0', '1', '多集群管理', '0', 'know-streaming'); INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('1595', '系统管理', '0', '0', '1', '系统管理', '0', 'know-streaming'); @@ -734,6 +846,7 @@ INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `l -- 初始化用户 +-- INSERT INTO `logi_security_user` (`id`, `user_name`, `pw`, `real_name`, `is_delete`, `app_name`) VALUES ('1', 'admin', 'V1ZkU2RHRlhOSGxOUkVsNVdETjBRVlp0Y0V0T1IwWnlaVEZ6YWxGRVJrRkpNVEU1VTJwYVUySkhlRzlSU0RBOWUwQldha28wWVd0N1d5TkFNa0FqWFgxS05sSnNiR2hBZlE9PXtAVmpKNGFre1sjQDNAI119SjZSbGxoQH0=Mv{#cdRgJ45Lqx}3IubEW87!==', '系统管理员', '0', 'know-streaming'); INSERT INTO `logi_security_user` (`id`, `user_name`, `pw`, `real_name`, `is_delete`, `app_name`) VALUES ('1', 'admin', 'V1ZkU2RHRlhOVGRSUmxweFUycFNhR0V6ZEdKSk1FRjRVVU5PWkdaVmJ6SlZiWGh6WVVWQ09YdEFWbXBLTkdGcmUxc2pRREpBSTExOVNqWlNiR3hvUUgwPXtAVmpKNGFre1sjQDNAI119SjZSbGxoQH0=Mv{#cdRgJ45Lqx}3IubEW87!==', '系统管理员', '0', 'know-streaming'); -- 初始化角色 @@ -782,3 +895,4 @@ INSERT INTO `logi_security_config` (`value_group`,`value_name`,`value`,`edit`,`status`,`memo`,`is_delete`,`app_name`,`operator`) VALUES ('SECURITY.LOGIN','SECURITY.TRICK_USERS','[\n \"admin\"\n]',1,1,'允许跳过登录的用户',0,'know-streaming','admin'); + diff --git a/km-dist/helm/Chart.yaml b/km-dist/helm/Chart.yaml index b5f5432c..71155a76 100644 --- a/km-dist/helm/Chart.yaml +++ b/km-dist/helm/Chart.yaml @@ -1,16 +1,16 @@ apiVersion: v2 name: knowstreaming-manager -description: knowstreaming-manager Helm chart +description: knowstreaming-manager Helm chart type: application -version: 0.1.5 +version: 0.1.9 maintainers: - email: didicloud@didiglobal.com name: didicloud -appVersion: "3.0.0-beta.3" +appVersion: "3.2.0" dependencies: - name: knowstreaming-web diff --git a/km-dist/helm/charts/elasticsearch/values.yaml b/km-dist/helm/charts/elasticsearch/values.yaml index e325ec68..c583c543 100644 --- a/km-dist/helm/charts/elasticsearch/values.yaml +++ b/km-dist/helm/charts/elasticsearch/values.yaml @@ -175,6 +175,7 @@ antiAffinityTopologyKey: "kubernetes.io/hostname" # and that they will never end up on the same node. Setting this to soft will do this "best effort" antiAffinity: "" #antiAffinity: "hard" + # This is the node affinity settings as defined in # https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity-beta-feature nodeAffinity: {} diff --git a/km-dist/helm/charts/knowstreaming-web/values.yaml b/km-dist/helm/charts/knowstreaming-web/values.yaml index 4bc6464d..7bcac76a 100644 --- a/km-dist/helm/charts/knowstreaming-web/values.yaml +++ b/km-dist/helm/charts/knowstreaming-web/values.yaml @@ -8,7 +8,7 @@ image: repository: knowstreaming/knowstreaming-ui pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "latest" + tag: "0.1.0" imagePullSecrets: [] nameOverride: "" diff --git a/km-dist/helm/charts/ksmysql/templates/statefulset.yaml b/km-dist/helm/charts/ksmysql/templates/statefulset.yaml index d61c506a..d14e9861 100644 --- a/km-dist/helm/charts/ksmysql/templates/statefulset.yaml +++ b/km-dist/helm/charts/ksmysql/templates/statefulset.yaml @@ -21,7 +21,7 @@ spec: {{- include "ksmysql.selectorLabels" . | nindent 8 }} spec: containers: - - image: knowstreaming/knowstreaming-mysql:latest + - image: knowstreaming/knowstreaming-mysql:0.7.0 name: {{ .Chart.Name }} env: - name: MYSQL_DATABASE diff --git a/km-dist/helm/templates/configmap.yaml b/km-dist/helm/templates/configmap.yaml index 6395aff1..6630f320 100644 --- a/km-dist/helm/templates/configmap.yaml +++ b/km-dist/helm/templates/configmap.yaml @@ -41,6 +41,7 @@ data: idle-timeout: 30000 connection-test-query: SELECT 1 logi-job: + enable: true {{ if .Values.ksmysql.enabled }} jdbc-url: jdbc:mariadb://{{ .Values.ksmysql.service.name }}:{{ .Values.ksmysql.service.port }}/{{ .Values.ksmysql.mysql.dbname }}?useUnicode=true&characterEncoding=utf8&jdbcCompliantTruncation=true&allowMultiQueries=true&useSSL=false&alwaysAutoGeneratedKeys=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true username: {{ .Values.ksmysql.mysql.username }} @@ -77,6 +78,10 @@ data: config: classpath:logback-spring.xml thread-pool: + es: + search: # es查询线程池 + thread-num: 20 # 线程池大小 + queue-size: 10000 # 队列大小 scheduled: thread-num: 2 # @Scheduled任务的线程池大小,默认是一个 collector: # 采集模块的配置 @@ -104,18 +109,25 @@ data: max-idle-client-num: 20 # 最大空闲客户端数 max-total-client-num: 20 # 最大客户端数 borrow-timeout-unit-ms: 5000 # 租借超时时间,单位秒 + kafka-admin: + client-cnt: 1 # 每个Kafka集群创建的KafkaAdminClient数 + es: + index: + expire: 15 # 索引过期天数,15表示超过15天的索引会被KS过期删除 client: {{ if .Values.elasticsearch.enabled }} address: elasticsearch-master:9200 {{- else }} address: {{ .Values.elasticsearch.esClientAddress }}:{{ .Values.elasticsearch.esProt }} + pass: {{ .Values.elasticsearch.userPass }} {{- end }} client-cnt: 10 io-thread-cnt: 2 max-retry-cnt: 5 + # 普罗米修斯指标导出相关配置 management: endpoints: @@ -151,6 +163,7 @@ data: curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_group_metric -d '{"order":10,"index_patterns":["ks_kafka_group_metric*"],"settings":{"index":{"number_of_shards":"10"}},"mappings":{"properties":{"group":{"type":"keyword"},"partitionId":{"type":"long"},"routingValue":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"clusterPhyId":{"type":"long"},"topic":{"type":"keyword"},"metrics":{"properties":{"HealthScore":{"type":"float"},"Lag":{"type":"float"},"OffsetConsumed":{"type":"float"},"HealthCheckTotal":{"type":"float"},"HealthCheckPassed":{"type":"float"}}},"groupMetric":{"type":"keyword"},"key":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"timestamp":{"format":"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis","index":true,"type":"date","doc_values":true}}},"aliases":{}}' && \ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_partition_metric -d '{"order":10,"index_patterns":["ks_kafka_partition_metric*"],"settings":{"index":{"number_of_shards":"10"}},"mappings":{"properties":{"brokerId":{"type":"long"},"partitionId":{"type":"long"},"routingValue":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"clusterPhyId":{"type":"long"},"topic":{"type":"keyword"},"metrics":{"properties":{"LogStartOffset":{"type":"float"},"Messages":{"type":"float"},"LogEndOffset":{"type":"float"}}},"key":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"timestamp":{"format":"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis","index":true,"type":"date","doc_values":true}}},"aliases":{}}' && \ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_replication_metric -d '{"order":10,"index_patterns":["ks_kafka_replication_metric*"],"settings":{"index":{"number_of_shards":"10"}},"mappings":{"properties":{"timestamp":{"format":"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis","index":true,"type":"date","doc_values":true}}},"aliases":{}}' && \ + curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_zookeeper_metric -d '{"order":10,"index_patterns":["ks_kafka_zookeeper_metric*"],"settings":{"index":{"number_of_shards":"10"}},"mappings":{"properties":{"routingValue":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"clusterPhyId":{"type":"long"},"metrics":{"properties":{"AvgRequestLatency":{"type":"double"},"MinRequestLatency":{"type":"double"},"MaxRequestLatency":{"type":"double"},"OutstandingRequests":{"type":"double"},"NodeCount":{"type":"double"},"WatchCount":{"type":"double"},"NumAliveConnections":{"type":"double"},"PacketsReceived":{"type":"double"},"PacketsSent":{"type":"double"},"EphemeralsCount":{"type":"double"},"ApproximateDataSize":{"type":"double"},"OpenFileDescriptorCount":{"type":"double"},"MaxFileDescriptorCount":{"type":"double"}}},"key":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"timestamp":{"format":"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis","type":"date"}}},"aliases":{}}' && \ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_topic_metric -d '{"order":10,"index_patterns":["ks_kafka_topic_metric*"],"settings":{"index":{"number_of_shards":"10"}},"mappings":{"properties":{"brokerId":{"type":"long"},"routingValue":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"topic":{"type":"keyword"},"clusterPhyId":{"type":"long"},"metrics":{"properties":{"BytesIn_min_15":{"type":"float"},"Messages":{"type":"float"},"BytesRejected":{"type":"float"},"PartitionURP":{"type":"float"},"HealthCheckTotal":{"type":"float"},"ReplicationCount":{"type":"float"},"ReplicationBytesOut":{"type":"float"},"ReplicationBytesIn":{"type":"float"},"FailedFetchRequests":{"type":"float"},"BytesIn_min_5":{"type":"float"},"HealthScore":{"type":"float"},"LogSize":{"type":"float"},"BytesOut":{"type":"float"},"BytesOut_min_15":{"type":"float"},"FailedProduceRequests":{"type":"float"},"BytesIn":{"type":"float"},"BytesOut_min_5":{"type":"float"},"MessagesIn":{"type":"float"},"TotalProduceRequests":{"type":"float"},"HealthCheckPassed":{"type":"float"}}},"brokerAgg":{"type":"keyword"},"key":{"type":"text","fields":{"keyword":{"ignore_above":256,"type":"keyword"}}},"timestamp":{"format":"yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis","index":true,"type":"date","doc_values":true}}},"aliases":{}}' || \ exit 1 for i in {0..6};do @@ -160,6 +173,8 @@ data: curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_group_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_partition_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_replication_metric${logdate} && \ + curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_zookeeper_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_topic_metric${logdate} || \ exit 2 done + diff --git a/km-dist/helm/templates/deployment.yaml b/km-dist/helm/templates/deployment.yaml index 139b29e7..8716dd2f 100644 --- a/km-dist/helm/templates/deployment.yaml +++ b/km-dist/helm/templates/deployment.yaml @@ -33,6 +33,8 @@ spec: - name: init-config image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" command: ['/bin/bash', '/conf/init_es_index.sh'] + resources: + {{- toYaml .Values.resources | nindent 10 }} volumeMounts: - name: configmap mountPath: /conf diff --git a/km-dist/helm/values.yaml b/km-dist/helm/values.yaml index 293bd001..678d11d8 100644 --- a/km-dist/helm/values.yaml +++ b/km-dist/helm/values.yaml @@ -3,7 +3,7 @@ replicaCount: 2 image: repository: knowstreaming/knowstreaming-manager pullPolicy: IfNotPresent - tag: "latest" + tag: "0.7.0" imagePullSecrets: [] nameOverride: "" @@ -73,7 +73,7 @@ knowstreaming-web: image: repository: knowstreaming/knowstreaming-ui pullPolicy: IfNotPresent - tag: "latest" + tag: "0.7.0" service: type: NodePort @@ -93,6 +93,7 @@ elasticsearch: #------------------------------------------------------------------ esClientAddress: 10.96.64.13 esProt: 8061 + userPass: "username:password" #ES账号密码,如果有账号密码,按照username:password 的格式填写,没有则不需要填写 #------------------------------------------------------------------ replicas: 3 minimumMasterNodes: 2 From 6ef365e201a1d842779a49c53577eb8847ca56c2 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 16 Dec 2022 13:58:40 +0800 Subject: [PATCH 077/150] bump version to 3.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dfd612aa..4c8d9344 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ - 3.1.0 + 3.2.0 8 8 From 344cec19fe4974dd69c9646efcb50489ca649883 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Mon, 19 Dec 2022 16:54:18 +0800 Subject: [PATCH 078/150] =?UTF-8?q?[Bugfix]connector=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E9=87=87=E9=9B=86=E7=AE=97=E6=9C=80=E5=A4=A7=E5=80=BC=E9=94=99?= =?UTF-8?q?=E8=AF=AF(#836)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connect/connector/impl/ConnectorMetricServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java index c40d1ee1..a92672c5 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java @@ -312,8 +312,8 @@ public class ConnectorMetricServiceImpl extends BaseConnectorMetricService imple return Result.buildFailure(NOT_EXIST); } - Float sum = ret.getData().stream().max((a, b) -> a.getMetric(metric).compareTo(b.getMetric(metric))).get().getMetric(metric); - ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, sum / ret.getData().size()); + Float max = ret.getData().stream().max((a, b) -> a.getMetric(metric).compareTo(b.getMetric(metric))).get().getMetric(metric); + ConnectorMetrics connectorMetrics = ConnectorMetrics.initWithMetric(connectClusterId, connectorName, metric, max); return Result.buildSuc(connectorMetrics); } From 0735b332a80fa63aee0838f7d2fa0c3e83050453 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Tue, 20 Dec 2022 17:36:15 +0800 Subject: [PATCH 079/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=98=A0=E5=B0=84=E9=94=99=E8=AF=AF(#842)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connect/connector/impl/ConnectorMetricServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java index a92672c5..8c9fec4f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java @@ -55,7 +55,7 @@ import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultS public class ConnectorMetricServiceImpl extends BaseConnectorMetricService implements ConnectorMetricService { protected static final ILog LOGGER = LogFactory.getLog(ConnectorMetricServiceImpl.class); - public static final String CONNECTOR_METHOD_DO_NOTHING = "getConnectWorkerMetricSum"; + public static final String CONNECTOR_METHOD_DO_NOTHING = "doNothing"; public static final String CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM = "getConnectWorkerMetricSum"; From 6ed6d5ec8aca98e535b68b2cc5f340250f49fb87 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Thu, 22 Dec 2022 15:49:50 +0800 Subject: [PATCH 080/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9B=B4=E6=96=B0=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98?= =?UTF-8?q?(#840)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install_guide/版本升级手册.md | 5 ++++- km-dist/init/sql/ddl-logi-security.sql | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/install_guide/版本升级手册.md b/docs/install_guide/版本升级手册.md index 2a9951b1..691281f6 100644 --- a/docs/install_guide/版本升级手册.md +++ b/docs/install_guide/版本升级手册.md @@ -6,7 +6,10 @@ ### 升级至 `master` 版本 -暂无 +**SQL 变更** +```sql +alter TABLE logi_security_user MODIFY phone VARCHAR(20); +``` ### 升级至 `3.2.0` 版本 diff --git a/km-dist/init/sql/ddl-logi-security.sql b/km-dist/init/sql/ddl-logi-security.sql index 3e37e5b0..4f37f226 100644 --- a/km-dist/init/sql/ddl-logi-security.sql +++ b/km-dist/init/sql/ddl-logi-security.sql @@ -135,7 +135,7 @@ CREATE TABLE `logi_security_user` pw varchar(2048) not null comment '用户密码', salt char(5) default '' not null comment '密码盐', real_name varchar(128) default '' not null comment '真实姓名', - phone char(11) default '' not null comment 'mobile', + phone char(20) default '' not null comment 'mobile', email varchar(30) default '' not null comment 'email', dept_id int null comment '所属部门id', is_delete tinyint(1) default 0 not null comment '逻辑删除', From c187b5246fbc012324a5a84bb06550de2f8116c0 Mon Sep 17 00:00:00 2001 From: wyb <1164642317@qq.com> Date: Thu, 22 Dec 2022 16:35:15 +0800 Subject: [PATCH 081/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8Dconnector?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E7=AD=9B=E9=80=89=E7=BC=BA=E5=B0=91=E6=8C=87?= =?UTF-8?q?=E6=A0=87=E7=9A=84=E9=97=AE=E9=A2=98(#846)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metric/connect/ConnectConnectorMetricCollector.java | 5 +++++ .../metrics/connect/ConnectorMetricVersionItems.java | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java index 282ce870..4da6d8fd 100644 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/ConnectConnectorMetricCollector.java @@ -82,6 +82,11 @@ public class ConnectConnectorMetricCollector extends AbstractConnectMetricCollec for (VersionControlItem v : items) { try { + //过滤已测得指标 + if (metrics.getMetrics().get(v.getName()) != null) { + continue; + } + Result ret = connectorMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, connectorName, v.getName(), connectorType); if (null == ret || ret.failed() || null == ret.getData()) { continue; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java index cb9ddd07..2d4aeac2 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/ConnectorMetricVersionItems.java @@ -14,7 +14,6 @@ import static com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTyp import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_CONNECTOR; import static com.xiaojukeji.know.streaming.km.common.jmx.JmxAttribute.*; import static com.xiaojukeji.know.streaming.km.common.jmx.JmxName.*; -import static com.xiaojukeji.know.streaming.km.core.service.broker.impl.BrokerMetricServiceImpl.BROKER_METHOD_DO_NOTHING; import static com.xiaojukeji.know.streaming.km.core.service.connect.connector.impl.ConnectorMetricServiceImpl.*; @@ -129,6 +128,13 @@ public class ConnectorMetricVersionItems extends BaseMetricVersionMetric { items.add(buildAllVersionsItem() .name(CONNECTOR_METRIC_HEALTH_STATE).unit("0:好 1:中 2:差 3:宕机").desc("健康状态(0:好 1:中 2:差 3:宕机)").category(CATEGORY_HEALTH) .extendMethod(CONNECTOR_METHOD_GET_METRIC_HEALTH_SCORE)); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_HEALTH_CHECK_PASSED).unit("个").desc("健康项检查通过数").category(CATEGORY_HEALTH) + .extendMethod(CONNECTOR_METHOD_GET_METRIC_HEALTH_SCORE)); + items.add(buildAllVersionsItem() + .name(CONNECTOR_METRIC_HEALTH_CHECK_TOTAL).unit("个").desc("健康项检查总数").category(CATEGORY_HEALTH) + .extendMethod(CONNECTOR_METHOD_GET_METRIC_HEALTH_SCORE)); + items.add(buildAllVersionsItem() .name(CONNECTOR_METRIC_CONNECTOR_TOTAL_TASK_COUNT).unit("个").desc("所有任务数量").category(CATEGORY_PERFORMANCE) .extend(buildConnectJMXMethodExtend(CONNECTOR_METHOD_GET_CONNECT_WORKER_METRIC_SUM) From 4994639111b06295e57e9316fc91894460a6a453 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Tue, 3 Jan 2023 13:16:54 +0800 Subject: [PATCH 082/150] =?UTF-8?q?[Optimize]=E6=97=A0ZK=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=B7=A1=E6=A3=80=E8=AF=A6=E6=83=85=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E5=AF=B9ZK=E7=9A=84=E5=B1=95=E7=A4=BA(#764)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/health/HealthScoreBaseResultVO.java | 4 --- .../checker/AbstractHealthCheckService.java | 2 ++ .../broker/HealthCheckBrokerService.java | 5 +++ .../cluster/HealthCheckClusterService.java | 5 +++ .../HealthCheckConnectClusterService.java | 16 ++++++++- .../connect/HealthCheckConnectorService.java | 18 +++++++++- .../group/HealthCheckGroupService.java | 5 +++ .../topic/HealthCheckTopicService.java | 5 +++ .../HealthCheckZookeeperService.java | 12 ++++++- .../health/state/HealthStateService.java | 4 +-- .../state/impl/HealthStateServiceImpl.java | 35 +++++++++---------- .../api/v3/health/KafkaHealthController.java | 24 ++++++------- 12 files changed, 93 insertions(+), 42 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java index 6bb0b584..5da85481 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/health/HealthScoreBaseResultVO.java @@ -33,10 +33,6 @@ public class HealthScoreBaseResultVO extends BaseTimeVO { @ApiModelProperty(value="检查说明", example = "Group延迟") private String configDesc; - @Deprecated - @ApiModelProperty(value="得分", example = "100") - private Integer score = 100; - @ApiModelProperty(value="结果", example = "true") private Boolean passed; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java index b01b8832..6a630f96 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/AbstractHealthCheckService.java @@ -28,6 +28,8 @@ public abstract class AbstractHealthCheckService { public abstract HealthCheckDimensionEnum getHealthCheckDimensionEnum(); + public abstract Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId); + public HealthCheckResult checkAndGetResult(ClusterParam clusterParam, BaseClusterHealthConfig clusterHealthConfig) { if (ValidateUtils.anyNull(clusterParam, clusterHealthConfig)) { return null; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java index 6da0ed4f..1722cc78 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/broker/HealthCheckBrokerService.java @@ -57,6 +57,11 @@ public class HealthCheckBrokerService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.BROKER; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + return this.getHealthCheckDimensionEnum().getDimension(); + } + /** * Broker网络处理线程平均值过低 */ diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java index a8201f45..39ee6779 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java @@ -44,6 +44,11 @@ public class HealthCheckClusterService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.CLUSTER; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + return this.getHealthCheckDimensionEnum().getDimension(); + } + /** * 检查NoController */ diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java index 566399b6..fc20c21c 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectClusterService.java @@ -14,7 +14,9 @@ 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.utils.Tuple; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; import org.springframework.beans.factory.annotation.Autowired; @@ -32,9 +34,11 @@ import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.conn */ @Service public class HealthCheckConnectClusterService extends AbstractHealthCheckService { - private static final ILog log = LogFactory.getLog(HealthCheckConnectClusterService.class); + @Autowired + private ConnectClusterService connectClusterService; + @Autowired private ConnectClusterMetricService connectClusterMetricService; @@ -57,6 +61,16 @@ public class HealthCheckConnectClusterService extends AbstractHealthCheckService return HealthCheckDimensionEnum.CONNECT_CLUSTER; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + List clusterList = connectClusterService.listByKafkaCluster(kafkaClusterPhyId); + if (ValidateUtils.isEmptyList(clusterList)) { + return null; + } + + return this.getHealthCheckDimensionEnum().getDimension(); + } + private HealthCheckResult checkStartupFailurePercentage(Tuple paramTuple) { ConnectClusterParam param = (ConnectClusterParam) paramTuple.getV1(); HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java index 8f30ade8..e4286423 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java @@ -4,6 +4,7 @@ import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; @@ -13,6 +14,8 @@ 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.utils.Tuple; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorMetricService; import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; @@ -32,11 +35,14 @@ import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.conn */ @Service public class HealthCheckConnectorService extends AbstractHealthCheckService { - private static final ILog log = LogFactory.getLog(HealthCheckConnectorService.class); + @Autowired private ConnectorService connectorService; + @Autowired + private ConnectClusterService connectClusterService; + @Autowired private ConnectorMetricService connectorMetricService; @@ -66,6 +72,16 @@ public class HealthCheckConnectorService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.CONNECTOR; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + List clusterList = connectClusterService.listByKafkaCluster(kafkaClusterPhyId); + if (ValidateUtils.isEmptyList(clusterList)) { + return null; + } + + return this.getHealthCheckDimensionEnum().getDimension(); + } + private HealthCheckResult checkFailedTaskCount(Tuple paramTuple) { ConnectorParam param = (ConnectorParam) paramTuple.getV1(); HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java index 62cf8d13..c80a00ac 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/group/HealthCheckGroupService.java @@ -51,6 +51,11 @@ public class HealthCheckGroupService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.GROUP; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + return this.getHealthCheckDimensionEnum().getDimension(); + } + /** * 检查Group re-balance太频繁 */ diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java index 57537bf4..070a132d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java @@ -62,6 +62,11 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.TOPIC; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + return this.getHealthCheckDimensionEnum().getDimension(); + } + /** * 检查Topic长期未同步 */ diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java index f79af127..b0d70ce6 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/zookeeper/HealthCheckZookeeperService.java @@ -19,6 +19,7 @@ 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.ValidateUtils; import com.xiaojukeji.know.streaming.km.common.utils.zookeeper.ZookeeperUtils; import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems; @@ -56,7 +57,7 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { @Override public List getResList(Long clusterPhyId) { ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(clusterPhyId); - if (clusterPhy == null) { + if (clusterPhy == null || ValidateUtils.isBlank(clusterPhy.getZookeeper())) { return new ArrayList<>(); } @@ -80,6 +81,15 @@ public class HealthCheckZookeeperService extends AbstractHealthCheckService { return HealthCheckDimensionEnum.ZOOKEEPER; } + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + if (ValidateUtils.isEmptyList(this.getResList(kafkaClusterPhyId))) { + return null; + } + + return this.getHealthCheckDimensionEnum().getDimension(); + } + private HealthCheckResult checkBrainSplit(Tuple singleConfigSimpleTuple) { ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1(); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java index bfe9a1bb..f9fc3edb 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java @@ -3,7 +3,6 @@ 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.bean.entity.metrics.connect.ConnectorMetrics; -import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; import java.util.List; @@ -22,8 +21,7 @@ public interface HealthStateService { /** * 获取集群健康检查结果 */ - List getClusterHealthResult(Long clusterPhyId); - List getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum); + List getAllDimensionHealthResult(Long clusterPhyId); List getDimensionHealthResult(Long clusterPhyId, List dimensionCodeList); List getResHealthResult(Long clusterPhyId, Long clusterId, Integer dimension, String resNme); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java index 8193af86..3610cfb7 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java @@ -8,12 +8,14 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreRes import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.*; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; +import com.xiaojukeji.know.streaming.km.common.component.SpringTool; 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.health.HealthStateEnum; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService; @@ -224,17 +226,20 @@ public class HealthStateServiceImpl implements HealthStateService { } @Override - public List getClusterHealthResult(Long clusterPhyId) { - List poList = healthCheckResultService.listCheckResult(clusterPhyId); + public List getAllDimensionHealthResult(Long clusterPhyId) { + List supportedDimensionCodeList = new ArrayList<>(); - return this.convert2HealthScoreResultList(clusterPhyId, poList, null); - } + // 获取支持的code + for (AbstractHealthCheckService service: SpringTool.getBeansOfType(AbstractHealthCheckService.class).values()) { + Integer dimensionCode = service.getDimensionCodeIfSupport(clusterPhyId); + if (dimensionCode == null) { + continue; + } - @Override - public List getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) { - List poList = healthCheckResultService.listCheckResult(clusterPhyId, dimensionEnum.getDimension()); + supportedDimensionCodeList.add(dimensionCode); + } - return this.convert2HealthScoreResultList(clusterPhyId, poList, dimensionEnum.getDimension()); + return this.getDimensionHealthResult(clusterPhyId, supportedDimensionCodeList); } @Override @@ -242,22 +247,14 @@ public class HealthStateServiceImpl implements HealthStateService { //查找健康巡查结果 List poList = new ArrayList<>(); for (Integer dimensionCode : dimensionCodeList) { - HealthCheckDimensionEnum dimensionEnum = HealthCheckDimensionEnum.getByCode(dimensionCode); - - if (dimensionEnum.equals(HealthCheckDimensionEnum.UNKNOWN)) { - continue; - } - - if (dimensionEnum.equals(HealthCheckDimensionEnum.CONNECTOR)) { + if (dimensionCode.equals(HealthCheckDimensionEnum.CONNECTOR.getDimension())) { poList.addAll(healthCheckResultService.getConnectorHealthCheckResult(clusterPhyId)); } else { - poList.addAll(healthCheckResultService.listCheckResult(clusterPhyId, dimensionEnum.getDimension())); + poList.addAll(healthCheckResultService.listCheckResult(clusterPhyId, dimensionCode)); } } - List resultList = this.getResHealthResult(clusterPhyId, dimensionCodeList, poList); - return resultList; - + return this.getResHealthResult(clusterPhyId, dimensionCodeList, poList); } @Override diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java index 1890a1db..48a3bf46 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java @@ -17,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -31,14 +31,6 @@ public class KafkaHealthController { @Autowired private HealthStateService healthStateService; - private List allDimensionCodeList = new ArrayList() { - { - for (HealthCheckDimensionEnum dimensionEnum : HealthCheckDimensionEnum.values()) { - add(dimensionEnum.getDimension()); - } - } - }; - @Autowired private HealthCheckResultService healthCheckResultService; @@ -49,12 +41,18 @@ public class KafkaHealthController { @RequestParam(required = false) Integer dimensionCode) { HealthCheckDimensionEnum dimensionEnum = HealthCheckDimensionEnum.getByCode(dimensionCode); if (!dimensionEnum.equals(HealthCheckDimensionEnum.UNKNOWN)) { - return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList(healthStateService.getDimensionHealthResult(clusterPhyId, Arrays.asList(dimensionCode)))); + return Result.buildSuc( + HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( + healthStateService.getDimensionHealthResult(clusterPhyId, Collections.singletonList(dimensionCode)) + ) + ); } - return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( - healthStateService.getDimensionHealthResult(clusterPhyId, allDimensionCodeList) - )); + return Result.buildSuc( + HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( + healthStateService.getAllDimensionHealthResult(clusterPhyId) + ) + ); } @ApiOperation(value = "集群-健康检查详情") From 17e0c39f8399a96d8f3bab61008a750c4c4594e6 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 6 Jan 2023 14:35:42 +0800 Subject: [PATCH 083/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96Topic?= =?UTF-8?q?=E5=81=A5=E5=BA=B7=E5=B7=A1=E6=A3=80=E7=9A=84=E6=97=A5=E5=BF=97?= =?UTF-8?q?(#855)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/health/checker/topic/HealthCheckTopicService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java index 070a132d..521e1b5b 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/topic/HealthCheckTopicService.java @@ -90,7 +90,7 @@ public class HealthCheckTopicService extends AbstractHealthCheckService { ); if (countResult.failed() || !countResult.hasData()) { - LOGGER.error("method=checkTopicUnderReplicatedPartition||param={}||config={}||result={}||errMsg=get metrics failed", + LOGGER.error("method=checkTopicUnderReplicatedPartition||param={}||config={}||result={}||errMsg=search metrics from es failed", param, singleConfig, countResult); return null; } From 6b3eb05735ab2d4c3e03710b4203bc9c37646037 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 6 Jan 2023 13:43:48 +0800 Subject: [PATCH 084/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E5=AF=B9ZK?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=BF=9B=E8=A1=8C=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=90=8E=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?(#694)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、修复在ks_km_physical_cluster表的zk_properties字段填写ZK 客户端的相关配置后,不生效的问题。 2、删除zk_properties字段中,暂时无需使用的jmxConfig字段。 --- .../common/bean/entity/config/ZKConfig.java | 13 +--- .../persistence/kafka/KafkaAdminZKClient.java | 67 +++++++++++++------ 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/ZKConfig.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/ZKConfig.java index 66a727e5..f0fe41d0 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/ZKConfig.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/config/ZKConfig.java @@ -13,9 +13,6 @@ import java.util.Properties; */ @ApiModel(description = "ZK配置") public class ZKConfig implements Serializable { - @ApiModelProperty(value="ZK的jmx配置") - private JmxConfig jmxConfig; - @ApiModelProperty(value="ZK是否开启secure", example = "false") private Boolean openSecure = false; @@ -28,14 +25,6 @@ public class ZKConfig implements Serializable { @ApiModelProperty(value="ZK的Request超时时间") private Properties otherProps = new Properties(); - public JmxConfig getJmxConfig() { - return jmxConfig == null? new JmxConfig(): jmxConfig; - } - - public void setJmxConfig(JmxConfig jmxConfig) { - this.jmxConfig = jmxConfig; - } - public Boolean getOpenSecure() { return openSecure != null && openSecure; } @@ -53,7 +42,7 @@ public class ZKConfig implements Serializable { } public Integer getRequestTimeoutUnitMs() { - return requestTimeoutUnitMs == null? Constant.DEFAULT_REQUEST_TIMEOUT_UNIT_MS: requestTimeoutUnitMs; + return requestTimeoutUnitMs == null? Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS: requestTimeoutUnitMs; } public void setRequestTimeoutUnitMs(Integer requestTimeoutUnitMs) { diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminZKClient.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminZKClient.java index e6275a60..48b26eb2 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminZKClient.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/kafka/KafkaAdminZKClient.java @@ -3,9 +3,10 @@ package com.xiaojukeji.know.streaming.km.persistence.kafka; 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.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig; import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.persistence.AbstractClusterLoadedChangedHandler; import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; @@ -17,11 +18,12 @@ import org.springframework.stereotype.Component; import scala.Option; import java.util.Map; +import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @Component public class KafkaAdminZKClient extends AbstractClusterLoadedChangedHandler implements KafkaClient { - private static final ILog log = LogFactory.getLog(KafkaAdminZKClient.class); + private static final ILog LOGGER = LogFactory.getLog(KafkaAdminZKClient.class); /** * Kafka提供的KafkaZkClient @@ -92,13 +94,13 @@ public class KafkaAdminZKClient extends AbstractClusterLoadedChangedHandler impl return; } - log.info("close ZK Client starting, clusterPhyId:{}", clusterPhyId); + LOGGER.info("method=closeZKClient||clusterPhyId={}||msg=close ZK Client starting", clusterPhyId); kafkaZkClient.close(); - log.info("close ZK Client success, clusterPhyId:{}", clusterPhyId); + LOGGER.info("method=closeZKClient||clusterPhyId={}||msg=close ZK Client success", clusterPhyId); } catch (Exception e) { - log.error("close ZK Client failed, clusterPhyId:{}", clusterPhyId, e); + LOGGER.error("method=closeZKClient||clusterPhyId={}||msg=close ZK Client failed||errMsg=exception!", clusterPhyId, e); } finally { modifyClientMapLock.unlock(); } @@ -107,19 +109,19 @@ public class KafkaAdminZKClient extends AbstractClusterLoadedChangedHandler impl private KafkaZkClient createZKClient(Long clusterPhyId) throws NotExistException { ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(clusterPhyId); if (clusterPhy == null) { - log.warn("create ZK Client failed, cluster not exist, clusterPhyId:{}", clusterPhyId); + LOGGER.warn("method=createZKClient||clusterPhyId={}||msg=create ZK Client failed, cluster not exist", clusterPhyId); throw new NotExistException(MsgConstant.getClusterPhyNotExist(clusterPhyId)); } if (ValidateUtils.isBlank(clusterPhy.getZookeeper())) { - log.warn("create ZK Client failed, zookeeper not exist, clusterPhyId:{}", clusterPhyId); + LOGGER.warn("method=createZKClient||clusterPhyId={}||msg=create ZK Client failed, zookeeper not exist", clusterPhyId); return null; } - return this.createZKClient(clusterPhyId, clusterPhy.getZookeeper()); + return this.createZKClient(clusterPhyId, clusterPhy); } - private KafkaZkClient createZKClient(Long clusterPhyId, String zookeeperAddress) { + private KafkaZkClient createZKClient(Long clusterPhyId, ClusterPhy clusterPhy) { try { modifyClientMapLock.lock(); @@ -128,33 +130,54 @@ public class KafkaAdminZKClient extends AbstractClusterLoadedChangedHandler impl return kafkaZkClient; } - log.debug("create ZK Client starting, clusterPhyId:{} zookeeperAddress:{}", clusterPhyId, zookeeperAddress); + ZKConfig zkConfig = this.getZKConfig(clusterPhy); + + LOGGER.debug("method=createZKClient||clusterPhyId={}||clusterPhy={}||msg=create ZK Client starting", clusterPhyId, clusterPhy); kafkaZkClient = KafkaZkClient.apply( - zookeeperAddress, - false, -// 添加支持zk的Kerberos认证 -// true, - Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS, - Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS, + clusterPhy.getZookeeper(), + zkConfig.getOpenSecure(), + zkConfig.getSessionTimeoutUnitMs(), + zkConfig.getRequestTimeoutUnitMs(), 5, Time.SYSTEM, - "KnowStreaming-clusterPhyId-" + clusterPhyId, - "SessionExpireListener", - Option.apply("KnowStreaming-clusterPhyId-" + clusterPhyId), - Option.apply(new ZKClientConfig()) + "KS-ZK-ClusterPhyId-" + clusterPhyId, + "KS-ZK-SessionExpireListener-clusterPhyId-" + clusterPhyId, + Option.apply("KS-ZK-ClusterPhyId-" + clusterPhyId), + Option.apply(this.getZKConfig(clusterPhyId, zkConfig.getOtherProps())) ); KAFKA_ZK_CLIENT_MAP.put(clusterPhyId, kafkaZkClient); KAFKA_ZK_CLIENT_CREATE_TIME.put(clusterPhyId, System.currentTimeMillis()); - log.info("create ZK Client success, clusterPhyId:{}", clusterPhyId); + LOGGER.info("method=createZKClient||clusterPhyId={}||msg=create ZK Client success", clusterPhyId); } catch (Exception e) { - log.error("create ZK Client failed, clusterPhyId:{} zookeeperAddress:{}", clusterPhyId, zookeeperAddress, e); + LOGGER.error("method=createZKClient||clusterPhyId={}||clusterPhy={}||msg=create ZK Client failed||errMsg=exception", clusterPhyId, clusterPhy, e); } finally { modifyClientMapLock.unlock(); } return KAFKA_ZK_CLIENT_MAP.get(clusterPhyId); } + + private ZKConfig getZKConfig(ClusterPhy clusterPhy) { + ZKConfig zkConfig = ConvertUtil.str2ObjByJson(clusterPhy.getZkProperties(), ZKConfig.class); + if (zkConfig == null) { + return new ZKConfig(); + } + + return zkConfig; + } + + private ZKClientConfig getZKConfig(Long clusterPhyId, Properties props) { + ZKClientConfig zkClientConfig = new ZKClientConfig(); + + try { + props.entrySet().forEach(elem -> zkClientConfig.setProperty((String) elem.getKey(), (String) elem.getValue())); + } catch (Exception e) { + LOGGER.error("method=getZKConfig||clusterPhyId={}||props={}||errMsg=exception", clusterPhyId, props); + } + + return zkClientConfig; + } } From 2925a20e8e8047b46bdc60a5d779f24e4e843bf9 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 9 Jan 2023 11:18:11 +0800 Subject: [PATCH 085/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E6=B6=88=E6=81=AF=E6=97=B6=EF=BC=8C=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=88=86=E5=8C=BA=E4=B8=8D=E7=94=9F=E6=95=88=E9=97=AE=E9=A2=98?= =?UTF-8?q?(#858)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/core/service/partition/impl/PartitionServiceImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java index 83222090..f4688729 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java @@ -266,9 +266,14 @@ public class PartitionServiceImpl extends BaseKafkaVersionControlService impleme List tpList = this.listPartitionFromCacheFirst(clusterPhyId, topicName).stream() .filter(item -> !item.getLeaderBrokerId().equals(KafkaConstant.NO_LEADER)) + .filter(partition -> partition.getPartitionId().equals(partitionId)) .map(elem -> new TopicPartition(topicName, elem.getPartitionId())) .collect(Collectors.toList()); + if (ValidateUtils.isEmptyList(tpList)) { + return Result.buildSuc(new HashMap<>(0)); + } + try { Result>>> listResult = (Result>>>) doVCHandler(clusterPhyId, PARTITION_OFFSET_GET, new PartitionOffsetParam(clusterPhyId, topicName, offsetSpec, tpList)); From a8b56fb613531b448800eed9eef1fc1708712381 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 9 Jan 2023 14:29:50 +0800 Subject: [PATCH 086/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BF=A1=E6=81=AF=E4=BF=AE=E6=94=B9=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=88=97=E8=A1=A8=E4=BC=9A=E6=8A=9B=E5=87=BA?= =?UTF-8?q?=E7=A9=BA=E6=8C=87=E9=92=88=E5=BC=82=E5=B8=B8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98(#860)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install_guide/版本升级手册.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/install_guide/版本升级手册.md b/docs/install_guide/版本升级手册.md index 691281f6..64a0319d 100644 --- a/docs/install_guide/版本升级手册.md +++ b/docs/install_guide/版本升级手册.md @@ -8,7 +8,9 @@ **SQL 变更** ```sql -alter TABLE logi_security_user MODIFY phone VARCHAR(20); +ALTER TABLE `logi_security_user` + CHANGE COLUMN `phone` `phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'mobile' ; + ``` ### 升级至 `3.2.0` 版本 From f4a219ceefb61501328b66d17482d102b5848781 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 9 Jan 2023 14:47:18 +0800 Subject: [PATCH 087/150] =?UTF-8?q?[Optimize]=E5=8E=BB=E9=99=A4Replica?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E4=BB=8EES=E8=AF=BB=E5=86=99=E7=9A=84?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81(#862)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metric/kafka/ReplicaMetricCollector.java | 114 ------------------ .../sink/kafka/ReplicaMetricESSender.java | 29 ----- .../bean/event/metric/ReplicaMetricEvent.java | 20 --- .../service/replica/ReplicaMetricService.java | 11 -- .../impl/ReplicaMetricServiceImpl.java | 24 ---- .../init/template/ks_kafka_replication_metric | 65 ---------- .../component/AbstractMonitorSinkService.java | 3 - .../es/dao/ReplicationMetricESDAO.java | 95 --------------- .../km/persistence/es/dsls/DslConstant.java | 5 - .../es/template/TemplateConstant.java | 1 - .../getAggSingleReplicationMetrics | 48 -------- .../getReplicationLatestMetrics | 52 -------- .../es/template/ks_kafka_replication_metric | 65 ---------- .../v3/replica/ReplicaMetricsController.java | 2 +- .../es/ReplicationMetricESDAOTest.java | 48 -------- 15 files changed, 1 insertion(+), 581 deletions(-) delete mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java delete mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java delete mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java delete mode 100644 km-dist/init/template/ks_kafka_replication_metric delete mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java delete mode 100644 km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics delete mode 100644 km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics delete mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_replication_metric delete mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ReplicationMetricESDAOTest.java diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java deleted file mode 100644 index e6c5efcd..00000000 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/kafka/ReplicaMetricCollector.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.xiaojukeji.know.streaming.km.collector.metric.kafka; - -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.metrics.ReplicationMetrics; -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.version.VersionControlItem; -import com.xiaojukeji.know.streaming.km.common.bean.event.metric.ReplicaMetricEvent; -import com.xiaojukeji.know.streaming.km.common.constant.Constant; -import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; -import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; -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.version.VersionControlService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; - -import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_REPLICATION; - -/** - * @author didi - */ -@Component -public class ReplicaMetricCollector extends AbstractKafkaMetricCollector { - protected static final ILog LOGGER = LogFactory.getLog(ReplicaMetricCollector.class); - - @Autowired - private VersionControlService versionControlService; - - @Autowired - private ReplicaMetricService replicaMetricService; - - @Autowired - private PartitionService partitionService; - - @Override - public List collectKafkaMetrics(ClusterPhy clusterPhy) { - Long clusterPhyId = clusterPhy.getId(); - List partitions = partitionService.listPartitionFromCacheFirst(clusterPhyId); - List items = versionControlService.listVersionControlItem(this.getClusterVersion(clusterPhy), collectorType().getCode()); - - FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); - - List metricsList = new ArrayList<>(); - for(Partition partition : partitions) { - for (Integer brokerId: partition.getAssignReplicaList()) { - ReplicationMetrics metrics = new ReplicationMetrics(clusterPhyId, partition.getTopicName(), brokerId, partition.getPartitionId()); - metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); - metricsList.add(metrics); - - future.runnableTask( - String.format("class=ReplicaMetricCollector||clusterPhyId=%d||brokerId=%d||topicName=%s||partitionId=%d", - clusterPhyId, brokerId, partition.getTopicName(), partition.getPartitionId()), - 30000, - () -> collectMetrics(clusterPhyId, metrics, items) - ); - } - } - - future.waitExecute(30000); - - publishMetric(new ReplicaMetricEvent(this, metricsList)); - - return metricsList; - } - - @Override - public VersionItemTypeEnum collectorType() { - return METRIC_REPLICATION; - } - - /**************************************************** private method ****************************************************/ - - private ReplicationMetrics collectMetrics(Long clusterPhyId, ReplicationMetrics metrics, List items) { - long startTime = System.currentTimeMillis(); - - for(VersionControlItem v : items) { - try { - if (metrics.getMetrics().containsKey(v.getName())) { - continue; - } - - Result ret = replicaMetricService.collectReplicaMetricsFromKafka( - clusterPhyId, - metrics.getTopic(), - metrics.getBrokerId(), - metrics.getPartitionId(), - v.getName() - ); - - if (null == ret || ret.failed() || null == ret.getData()) { - continue; - } - - metrics.putMetric(ret.getData().getMetrics()); - } catch (Exception e) { - LOGGER.error( - "method=collectMetrics||clusterPhyId={}||topicName={}||partition={}||metricName={}||errMsg=exception!", - clusterPhyId, metrics.getTopic(), metrics.getPartitionId(), v.getName(), e - ); - } - } - - // 记录采集性能 - metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, (System.currentTimeMillis() - startTime) / 1000.0f); - - return metrics; - } -} diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java deleted file mode 100644 index d7b74905..00000000 --- a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/kafka/ReplicaMetricESSender.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xiaojukeji.know.streaming.km.collector.sink.kafka; - -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; -import com.xiaojukeji.know.streaming.km.common.bean.event.metric.ReplicaMetricEvent; -import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ReplicationMetricPO; -import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; - -import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.REPLICATION_INDEX; - -@Component -public class ReplicaMetricESSender extends AbstractMetricESSender implements ApplicationListener { - private static final ILog LOGGER = LogFactory.getLog(ReplicaMetricESSender.class); - - @PostConstruct - public void init(){ - LOGGER.info("method=init||msg=init finished"); - } - - @Override - public void onApplicationEvent(ReplicaMetricEvent event) { - send2es(REPLICATION_INDEX, ConvertUtil.list2List(event.getReplicationMetrics(), ReplicationMetricPO.class)); - } -} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java deleted file mode 100644 index 9b71a69b..00000000 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/ReplicaMetricEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.xiaojukeji.know.streaming.km.common.bean.event.metric; - -import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ReplicationMetrics; -import lombok.Getter; - -import java.util.List; - -/** - * @author didi - */ -@Getter -public class ReplicaMetricEvent extends BaseMetricEvent{ - - private final List replicationMetrics; - - public ReplicaMetricEvent(Object source, List replicationMetrics) { - super( source ); - this.replicationMetrics = replicationMetrics; - } -} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/ReplicaMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/ReplicaMetricService.java index c0a44586..1223df25 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/ReplicaMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/ReplicaMetricService.java @@ -1,9 +1,7 @@ package com.xiaojukeji.know.streaming.km.core.service.replica; -import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ReplicationMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import java.util.List; @@ -14,13 +12,4 @@ public interface ReplicaMetricService { */ Result collectReplicaMetricsFromKafka(Long clusterId, String topic, Integer partitionId, Integer brokerId, String metric); Result collectReplicaMetricsFromKafka(Long clusterId, String topicName, Integer partitionId, Integer brokerId, List metricNameList); - - /** - * 从ES中获取指标 - */ - @Deprecated - Result> getMetricPointsFromES(Long clusterPhyId, Integer brokerId, String topicName, Integer partitionId, MetricDTO dto); - - @Deprecated - Result getLatestMetricsFromES(Long clusterPhyId, Integer brokerId, String topicName, Integer partitionId, List metricNames); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java index c15914a6..92986d4b 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.km.core.service.replica.impl; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ReplicationMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.ReplicationMetricParam; @@ -10,26 +9,21 @@ 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.version.VersionJmxInfo; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ReplicationMetricPO; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil; -import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; 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.version.BaseMetricService; -import com.xiaojukeji.know.streaming.km.persistence.es.dao.ReplicationMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.management.InstanceNotFoundException; import javax.management.ObjectName; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_REPLICATION; @@ -54,9 +48,6 @@ public class ReplicaMetricServiceImpl extends BaseMetricService implements Repli @Autowired private PartitionService partitionService; - @Autowired - private ReplicationMetricESDAO replicationMetricESDAO; - @Override protected List listMetricPOFields(){ return BeanUtil.listBeanFields(ReplicationMetricPO.class); @@ -118,21 +109,6 @@ public class ReplicaMetricServiceImpl extends BaseMetricService implements Repli } } - @Override - public Result> getMetricPointsFromES(Long clusterPhyId, Integer brokerId, String topicName, Integer partitionId, MetricDTO dto) { - Map metricPointMap = replicationMetricESDAO.getReplicationMetricsPoint(clusterPhyId, topicName, brokerId, partitionId, - dto.getMetricsNames(), dto.getAggType(), dto.getStartTime(), dto.getEndTime()); - - List metricPoints = new ArrayList<>(metricPointMap.values()); - return Result.buildSuc(metricPoints); - } - - @Override - public Result getLatestMetricsFromES(Long clusterPhyId, Integer brokerId, String topicName, Integer partitionId, List metricNames) { - ReplicationMetricPO metricPO = replicationMetricESDAO.getReplicationLatestMetrics(clusterPhyId, brokerId, topicName, partitionId, metricNames); - return Result.buildSuc(ConvertUtil.obj2Obj(metricPO, ReplicationMetrics.class)); - } - /**************************************************** private method ****************************************************/ private Result doNothing(VersionItemParam param) { ReplicationMetricParam metricParam = (ReplicationMetricParam)param; diff --git a/km-dist/init/template/ks_kafka_replication_metric b/km-dist/init/template/ks_kafka_replication_metric deleted file mode 100644 index a8ff4b53..00000000 --- a/km-dist/init/template/ks_kafka_replication_metric +++ /dev/null @@ -1,65 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_replication_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "10" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "LogStartOffset" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "LogEndOffset" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java index 1fccaf90..8bac5ac6 100644 --- a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java +++ b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java @@ -56,9 +56,6 @@ public abstract class AbstractMonitorSinkService implements ApplicationListener< GroupMetricEvent groupMetricEvent = (GroupMetricEvent)event; sinkMetrics(groupMetric2SinkPoint(groupMetricEvent.getGroupMetrics())); - } else if(event instanceof ReplicaMetricEvent) { - ReplicaMetricEvent replicaMetricEvent = (ReplicaMetricEvent)event; - sinkMetrics(replicationMetric2SinkPoint(replicaMetricEvent.getReplicationMetrics())); } else if(event instanceof ZookeeperMetricEvent) { ZookeeperMetricEvent zookeeperMetricEvent = (ZookeeperMetricEvent)event; sinkMetrics(zookeeperMetric2SinkPoint(zookeeperMetricEvent.getZookeeperMetrics())); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java deleted file mode 100644 index 6f1c7561..00000000 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/ReplicationMetricESDAO.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.xiaojukeji.know.streaming.km.persistence.es.dao; - -import com.didiglobal.logi.elasticsearch.client.response.query.query.ESQueryResponse; -import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr; -import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ReplicationMetricPO; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.VALUE; -import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.REPLICATION_INDEX; - -/** - * @author didi - */ -@Component -public class ReplicationMetricESDAO extends BaseMetricESDAO { - - @PostConstruct - public void init() { - super.indexName = REPLICATION_INDEX; - checkCurrentDayIndexExist(); - register(this); - } - - /** - * 获取集群 clusterId 中 brokerId 最新的统计指标 - */ - public ReplicationMetricPO getReplicationLatestMetrics(Long clusterPhyId, Integer brokerId, String topic, - Integer partitionId, List metricNames){ - Long endTime = getLatestMetricTime(); - Long startTime = endTime - FIVE_MIN; - - String dsl = dslLoaderUtil.getFormatDslByFileName( - DslConstant.GET_REPLICATION_LATEST_METRICS, clusterPhyId, brokerId, topic, partitionId, startTime, endTime); - - ReplicationMetricPO replicationMetricPO = esOpClient.performRequestAndTakeFirst( - realIndex(startTime, endTime), dsl, ReplicationMetricPO.class); - - return (null == replicationMetricPO) ? new ReplicationMetricPO(clusterPhyId, topic, brokerId, partitionId) - : filterMetrics(replicationMetricPO, metricNames); - } - - /** - * 获取集群 clusterPhyId 中每个 metric 的指定 partitionId 在指定时间[startTime、endTime]区间内聚合计算(avg、max)之后的统计值 - */ - public Map getReplicationMetricsPoint(Long clusterPhyId, String topic, - Integer brokerId, Integer partitionId, List metrics, - String aggType, Long startTime, Long endTime){ - //1、获取需要查下的索引 - String realIndex = realIndex(startTime, endTime); - - //2、构造agg查询条件 - String aggDsl = buildAggsDSL(metrics, aggType); - - String dsl = dslLoaderUtil.getFormatDslByFileName( - DslConstant.GET_REPLICATION_AGG_SINGLE_METRICS, clusterPhyId, brokerId,topic, partitionId, startTime, endTime, aggDsl); - - return esOpClient.performRequestWithRouting(String.valueOf(brokerId), realIndex, dsl, - s -> handleSingleESQueryResponse(s, metrics, aggType), 3); - } - - /**************************************************** private method ****************************************************/ - private Map handleSingleESQueryResponse(ESQueryResponse response, List metrics, String aggType){ - Map metricMap = new HashMap<>(); - - if(null == response || null == response.getAggs()){ - return metricMap; - } - - Map esAggrMap = response.getAggs().getEsAggrMap(); - if (null == esAggrMap) { - return metricMap; - } - - for(String metric : metrics){ - String value = esAggrMap.get(metric).getUnusedMap().get(VALUE).toString(); - - MetricPointVO metricPoint = new MetricPointVO(); - metricPoint.setAggType(aggType); - metricPoint.setValue(value); - metricPoint.setName(metric); - - metricMap.put(metric, metricPoint); - } - - return metricMap; - } -} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java index 9bd8062a..3e01e19f 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java @@ -62,11 +62,6 @@ public class DslConstant { public static final String LIST_PARTITION_LATEST_METRICS_BY_TOPIC = "PartitionMetricESDAO/listPartitionLatestMetricsByTopic"; - /**************************************************** REPLICATION ****************************************************/ - public static final String GET_REPLICATION_AGG_SINGLE_METRICS = "ReplicationMetricESDAO/getAggSingleReplicationMetrics"; - - public static final String GET_REPLICATION_LATEST_METRICS = "ReplicationMetricESDAO/getReplicationLatestMetrics"; - /**************************************************** Group ****************************************************/ public static final String GET_GROUP_TOPIC_PARTITION = "GroupMetricESDAO/getTopicPartitionOfGroup"; diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java index 2fc61f38..f74f79c6 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java @@ -9,7 +9,6 @@ public class TemplateConstant { public static final String BROKER_INDEX = "ks_kafka_broker_metric"; public static final String PARTITION_INDEX = "ks_kafka_partition_metric"; public static final String GROUP_INDEX = "ks_kafka_group_metric"; - public static final String REPLICATION_INDEX = "ks_kafka_replication_metric"; public static final String ZOOKEEPER_INDEX = "ks_kafka_zookeeper_metric"; public static final String CONNECT_CLUSTER_INDEX = "ks_kafka_connect_cluster_metric"; public static final String CONNECT_CONNECTOR_INDEX = "ks_kafka_connect_connector_metric"; diff --git a/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics b/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics deleted file mode 100644 index 59e4abca..00000000 --- a/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getAggSingleReplicationMetrics +++ /dev/null @@ -1,48 +0,0 @@ -{ - "size":0, - "query":{ - "bool":{ - "must":[ - { - "term":{ - "clusterPhyId":{ - "value":%d - } - } - }, - { - "term":{ - "brokerId":{ - "value":%d - } - } - }, - { - "term":{ - "topic":{ - "value":"%s" - } - } - }, - { - "term":{ - "partitionId":{ - "value":%d - } - } - }, - { - "range":{ - "timestamp":{ - "gte":%d, - "lte":%d - } - } - } - ] - } - }, - "aggs":{ - %s - } -} \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics b/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics deleted file mode 100644 index c4650a5c..00000000 --- a/km-persistence/src/main/resources/es/dsl/ReplicationMetricESDAO/getReplicationLatestMetrics +++ /dev/null @@ -1,52 +0,0 @@ -{ - "size": 1, - "query": { - "bool": { - "must": [ - { - "term": { - "clusterPhyId": { - "value": %d - } - } - }, - { - "term": { - "brokerId": { - "value": %d - } - } - }, - { - "term": { - "topic": { - "value": "%s" - } - } - }, - { - "term": { - "partitionId": { - "value": %d - } - } - }, - { - "range": { - "timestamp": { - "gte": %d, - "lte": %d - } - } - } - ] - } - }, - "sort": [ - { - "timestamp": { - "order": "desc" - } - } - ] -} \ No newline at end of file diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_replication_metric b/km-persistence/src/main/resources/es/template/ks_kafka_replication_metric deleted file mode 100644 index a8ff4b53..00000000 --- a/km-persistence/src/main/resources/es/template/ks_kafka_replication_metric +++ /dev/null @@ -1,65 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_replication_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "10" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "LogStartOffset" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "LogEndOffset" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/replica/ReplicaMetricsController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/replica/ReplicaMetricsController.java index 7e276aff..2b1878ea 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/replica/ReplicaMetricsController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/replica/ReplicaMetricsController.java @@ -35,7 +35,7 @@ public class ReplicaMetricsController { @PathVariable String topicName, @PathVariable Integer partitionId, @RequestBody MetricDTO dto) { - return replicationMetricService.getMetricPointsFromES(clusterPhyId, brokerId, topicName, partitionId, dto); + return Result.buildSuc(); } @ApiOperation(value = "Replica指标-单个Replica") diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ReplicationMetricESDAOTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ReplicationMetricESDAOTest.java deleted file mode 100644 index 98224a3d..00000000 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ReplicationMetricESDAOTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xiaojukeji.know.streaming.km.persistence.es; - -import com.xiaojukeji.know.streaming.km.KnowStreamApplicationTest; -import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.ReplicationMetricPO; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import com.xiaojukeji.know.streaming.km.persistence.es.dao.ReplicationMetricESDAO; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Map; - -public class ReplicationMetricESDAOTest extends KnowStreamApplicationTest { - - @Autowired - private ReplicationMetricESDAO replicationMetricESDAO; - - @Test - public void getReplicationLatestMetricsTest(){ - Long clusterPhyId = 2L; - Integer brokerId = 1; - String topic = "know-streaming-test-251"; - Integer partitionId = 1; - ReplicationMetricPO replicationMetricPO = replicationMetricESDAO.getReplicationLatestMetrics( - clusterPhyId, brokerId, topic, partitionId, new ArrayList<>()); - - assert null != replicationMetricPO; - } - - /** - * 测试 - * 获取集群 clusterPhyId 中每个 metric 的指定 partitionId 在指定时间[startTime、endTime]区间内聚合计算(avg、max)之后的统计值 - */ - @Test - public void getReplicationMetricsPointTest(){ - Long clusterPhyId = 2L; - Integer brokerId = 1; - String topic = "know-streaming-test-251"; - Integer partitionId = 1; - Long endTime = System.currentTimeMillis(); - Long startTime = endTime - 4 * 60 * 60 * 1000; - Map metricPointVOMap = replicationMetricESDAO.getReplicationMetricsPoint( - clusterPhyId, topic, brokerId, partitionId, Collections.emptyList(), "avg", startTime, endTime); - - assert null != metricPointVOMap; - } -} From ac7c32acd591c039d47650b8cf0878984d3e366c Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 9 Jan 2023 15:09:15 +0800 Subject: [PATCH 088/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96ES=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E5=8F=8A=E6=A8=A1=E7=89=88=E7=9A=84=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E6=96=87=E6=A1=A3(#832)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、订正不同地方索引模版的shard数存在不一致的问题; 2、删除多余的template.sh,统一使用init_es_template.sh; 3、init_es_template.sh中,增加connect相关索引模版的初始化脚本,删除replica 和 zookeper索引模版的初始化脚本; --- bin/init_es_template.sh | 363 ++++++-- docs/dev_guide/无数据排查文档.md | 7 +- km-dist/init/template/ks_kafka_group_metric | 2 +- .../init/template/ks_kafka_partition_metric | 2 +- km-dist/init/template/ks_kafka_topic_metric | 2 +- .../init/template/ks_kafka_zookeeper_metric | 2 +- km-dist/init/template/template.sh | 803 ------------------ .../es/template/ks_kafka_group_metric | 2 +- .../es/template/ks_kafka_partition_metric | 2 +- .../es/template/ks_kafka_topic_metric | 2 +- .../es/template/ks_kafka_zookeeper_metric | 2 +- 11 files changed, 301 insertions(+), 888 deletions(-) delete mode 100644 km-dist/init/template/template.sh diff --git a/bin/init_es_template.sh b/bin/init_es_template.sh index 86fcfb66..67e21685 100644 --- a/bin/init_es_template.sh +++ b/bin/init_es_template.sh @@ -13,7 +13,7 @@ curl -s --connect-timeout 10 -o /dev/null -X POST -H 'cache-control: no-cache' - ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -115,7 +115,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -302,7 +302,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { @@ -377,7 +377,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { @@ -436,72 +436,6 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl "aliases" : { } }' -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_replication_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_replication_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "10" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "LogStartOffset" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "LogEndOffset" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - }' - curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_topic_metric -d '{ "order" : 10, "index_patterns" : [ @@ -509,7 +443,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { @@ -626,7 +560,7 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { @@ -704,6 +638,288 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl "aliases" : { } }' +curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${SERVER_ES_ADDRESS}/_template/ks_kafka_connect_cluster_metric -d '{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_cluster_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "ConnectorCount" : { + "type" : "float" + }, + "TaskCount" : { + "type" : "float" + }, + "ConnectorStartupAttemptsTotal" : { + "type" : "float" + }, + "ConnectorStartupFailurePercentage" : { + "type" : "float" + }, + "ConnectorStartupFailureTotal" : { + "type" : "float" + }, + "ConnectorStartupSuccessPercentage" : { + "type" : "float" + }, + "ConnectorStartupSuccessTotal" : { + "type" : "float" + }, + "TaskStartupAttemptsTotal" : { + "type" : "float" + }, + "TaskStartupFailurePercentage" : { + "type" : "float" + }, + "TaskStartupFailureTotal" : { + "type" : "float" + }, + "TaskStartupSuccessPercentage" : { + "type" : "float" + }, + "TaskStartupSuccessTotal" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + }' + +curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${SERVER_ES_ADDRESS}/_template/ks_kafka_connect_connector_metric -d '{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_connector_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "connectorName" : { + "type" : "keyword" + }, + "connectorNameAndClusterId" : { + "type" : "keyword" + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "HealthState" : { + "type" : "float" + }, + "ConnectorTotalTaskCount" : { + "type" : "float" + }, + "HealthCheckPassed" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "ConnectorRunningTaskCount" : { + "type" : "float" + }, + "ConnectorPausedTaskCount" : { + "type" : "float" + }, + "ConnectorFailedTaskCount" : { + "type" : "float" + }, + "ConnectorUnassignedTaskCount" : { + "type" : "float" + }, + "BatchSizeAvg" : { + "type" : "float" + }, + "BatchSizeMax" : { + "type" : "float" + }, + "OffsetCommitAvgTimeMs" : { + "type" : "float" + }, + "OffsetCommitMaxTimeMs" : { + "type" : "float" + }, + "OffsetCommitFailurePercentage" : { + "type" : "float" + }, + "OffsetCommitSuccessPercentage" : { + "type" : "float" + }, + "PollBatchAvgTimeMs" : { + "type" : "float" + }, + "PollBatchMaxTimeMs" : { + "type" : "float" + }, + "SourceRecordActiveCount" : { + "type" : "float" + }, + "SourceRecordActiveCountAvg" : { + "type" : "float" + }, + "SourceRecordActiveCountMax" : { + "type" : "float" + }, + "SourceRecordPollRate" : { + "type" : "float" + }, + "SourceRecordPollTotal" : { + "type" : "float" + }, + "SourceRecordWriteRate" : { + "type" : "float" + }, + "SourceRecordWriteTotal" : { + "type" : "float" + }, + "OffsetCommitCompletionRate" : { + "type" : "float" + }, + "OffsetCommitCompletionTotal" : { + "type" : "float" + }, + "OffsetCommitSkipRate" : { + "type" : "float" + }, + "OffsetCommitSkipTotal" : { + "type" : "float" + }, + "PartitionCount" : { + "type" : "float" + }, + "PutBatchAvgTimeMs" : { + "type" : "float" + }, + "PutBatchMaxTimeMs" : { + "type" : "float" + }, + "SinkRecordActiveCount" : { + "type" : "float" + }, + "SinkRecordActiveCountAvg" : { + "type" : "float" + }, + "SinkRecordActiveCountMax" : { + "type" : "float" + }, + "SinkRecordLagMax" : { + "type" : "float" + }, + "SinkRecordReadRate" : { + "type" : "float" + }, + "SinkRecordReadTotal" : { + "type" : "float" + }, + "SinkRecordSendRate" : { + "type" : "float" + }, + "SinkRecordSendTotal" : { + "type" : "float" + }, + "DeadletterqueueProduceFailures" : { + "type" : "float" + }, + "DeadletterqueueProduceRequests" : { + "type" : "float" + }, + "LastErrorTimestamp" : { + "type" : "float" + }, + "TotalErrorsLogged" : { + "type" : "float" + }, + "TotalRecordErrors" : { + "type" : "float" + }, + "TotalRecordFailures" : { + "type" : "float" + }, + "TotalRecordsSkipped" : { + "type" : "float" + }, + "TotalRetries" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + }' + for i in {0..6}; do logdate=_$(date -d "${i} day ago" +%Y-%m-%d) @@ -711,8 +927,9 @@ do curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_cluster_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_group_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_partition_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_replication_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_zookeeper_metric${logdate} && \ + curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_connect_cluster_metric${logdate} && \ + curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_connect_connector_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_topic_metric${logdate} || \ exit 2 done diff --git a/docs/dev_guide/无数据排查文档.md b/docs/dev_guide/无数据排查文档.md index 337c5b13..fd7886bc 100644 --- a/docs/dev_guide/无数据排查文档.md +++ b/docs/dev_guide/无数据排查文档.md @@ -216,7 +216,7 @@ curl http://{ES的IP地址}:{ES的端口号}/_cat/indices/ks_kafka* 查看KS索 #### 3.1.2、解决方案 -执行[/km-dist/init/template/template.sh](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/template/template.sh)脚本创建索引。 +执行 [ES索引及模版初始化](https://github.com/didi/KnowStreaming/blob/master/bin/init_es_template.sh) 脚本,来创建索引及模版。 @@ -245,8 +245,7 @@ curl -XDELETE {ES的IP地址}:{ES的端口号}/ks_kafka* curl -XDELETE {ES的IP地址}:{ES的端口号}/_template/ks_kafka* ``` -执行[/km-dist/init/template/template.sh](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/template/template.sh)脚本初始化索引和模板。 - +执行 [ES索引及模版初始化](https://github.com/didi/KnowStreaming/blob/master/bin/init_es_template.sh) 脚本,来创建索引及模版。 ### 3.3、原因三:集群Shard满 @@ -283,4 +282,4 @@ curl -XPUT -H"content-type:application/json" http://{ES的IP地址}:{ES的端 }' ``` -执行[/km-dist/init/template/template.sh](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/template/template.sh)脚本补全索引。 +执行 [ES索引及模版初始化](https://github.com/didi/KnowStreaming/blob/master/bin/init_es_template.sh) 脚本,来补全索引。 diff --git a/km-dist/init/template/ks_kafka_group_metric b/km-dist/init/template/ks_kafka_group_metric index 3f04f16a..24ff12de 100644 --- a/km-dist/init/template/ks_kafka_group_metric +++ b/km-dist/init/template/ks_kafka_group_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { diff --git a/km-dist/init/template/ks_kafka_partition_metric b/km-dist/init/template/ks_kafka_partition_metric index 68264660..51193c22 100644 --- a/km-dist/init/template/ks_kafka_partition_metric +++ b/km-dist/init/template/ks_kafka_partition_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { diff --git a/km-dist/init/template/ks_kafka_topic_metric b/km-dist/init/template/ks_kafka_topic_metric index b2eed35d..4a1aa70c 100644 --- a/km-dist/init/template/ks_kafka_topic_metric +++ b/km-dist/init/template/ks_kafka_topic_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { diff --git a/km-dist/init/template/ks_kafka_zookeeper_metric b/km-dist/init/template/ks_kafka_zookeeper_metric index e2efb41a..10c9324b 100644 --- a/km-dist/init/template/ks_kafka_zookeeper_metric +++ b/km-dist/init/template/ks_kafka_zookeeper_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { diff --git a/km-dist/init/template/template.sh b/km-dist/init/template/template.sh deleted file mode 100644 index baae166b..00000000 --- a/km-dist/init/template/template.sh +++ /dev/null @@ -1,803 +0,0 @@ -esaddr=127.0.0.1 -port=8060 -curl -s --connect-timeout 10 -o /dev/null http://${esaddr}:${port}/_cat/nodes >/dev/null 2>&1 -if [ "$?" != "0" ];then - echo "Elasticserach 访问失败, 请安装完后检查并重新执行该脚本 " - exit -fi - -curl -s --connect-timeout 10 -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_broker_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_broker_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "NetworkProcessorAvgIdle" : { - "type" : "float" - }, - "UnderReplicatedPartitions" : { - "type" : "float" - }, - "BytesIn_min_15" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "RequestHandlerAvgIdle" : { - "type" : "float" - }, - "connectionsCount" : { - "type" : "float" - }, - "BytesIn_min_5" : { - "type" : "float" - }, - "HealthScore" : { - "type" : "float" - }, - "BytesOut" : { - "type" : "float" - }, - "BytesOut_min_15" : { - "type" : "float" - }, - "BytesIn" : { - "type" : "float" - }, - "BytesOut_min_5" : { - "type" : "float" - }, - "TotalRequestQueueSize" : { - "type" : "float" - }, - "MessagesIn" : { - "type" : "float" - }, - "TotalProduceRequests" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - }, - "TotalResponseQueueSize" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_cluster_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_cluster_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "Connections" : { - "type" : "double" - }, - "BytesIn_min_15" : { - "type" : "double" - }, - "PartitionURP" : { - "type" : "double" - }, - "HealthScore_Topics" : { - "type" : "double" - }, - "EventQueueSize" : { - "type" : "double" - }, - "ActiveControllerCount" : { - "type" : "double" - }, - "GroupDeads" : { - "type" : "double" - }, - "BytesIn_min_5" : { - "type" : "double" - }, - "HealthCheckTotal_Topics" : { - "type" : "double" - }, - "Partitions" : { - "type" : "double" - }, - "BytesOut" : { - "type" : "double" - }, - "Groups" : { - "type" : "double" - }, - "BytesOut_min_15" : { - "type" : "double" - }, - "TotalRequestQueueSize" : { - "type" : "double" - }, - "HealthCheckPassed_Groups" : { - "type" : "double" - }, - "TotalProduceRequests" : { - "type" : "double" - }, - "HealthCheckPassed" : { - "type" : "double" - }, - "TotalLogSize" : { - "type" : "double" - }, - "GroupEmptys" : { - "type" : "double" - }, - "PartitionNoLeader" : { - "type" : "double" - }, - "HealthScore_Brokers" : { - "type" : "double" - }, - "Messages" : { - "type" : "double" - }, - "Topics" : { - "type" : "double" - }, - "PartitionMinISR_E" : { - "type" : "double" - }, - "HealthCheckTotal" : { - "type" : "double" - }, - "Brokers" : { - "type" : "double" - }, - "Replicas" : { - "type" : "double" - }, - "HealthCheckTotal_Groups" : { - "type" : "double" - }, - "GroupRebalances" : { - "type" : "double" - }, - "MessageIn" : { - "type" : "double" - }, - "HealthScore" : { - "type" : "double" - }, - "HealthCheckPassed_Topics" : { - "type" : "double" - }, - "HealthCheckTotal_Brokers" : { - "type" : "double" - }, - "PartitionMinISR_S" : { - "type" : "double" - }, - "BytesIn" : { - "type" : "double" - }, - "BytesOut_min_5" : { - "type" : "double" - }, - "GroupActives" : { - "type" : "double" - }, - "MessagesIn" : { - "type" : "double" - }, - "GroupReBalances" : { - "type" : "double" - }, - "HealthCheckPassed_Brokers" : { - "type" : "double" - }, - "HealthScore_Groups" : { - "type" : "double" - }, - "TotalResponseQueueSize" : { - "type" : "double" - }, - "Zookeepers" : { - "type" : "double" - }, - "LeaderMessages" : { - "type" : "double" - }, - "HealthScore_Cluster" : { - "type" : "double" - }, - "HealthCheckPassed_Cluster" : { - "type" : "double" - }, - "HealthCheckTotal_Cluster" : { - "type" : "double" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "type" : "date" - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_group_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_group_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "group" : { - "type" : "keyword" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "HealthScore" : { - "type" : "float" - }, - "Lag" : { - "type" : "float" - }, - "OffsetConsumed" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - } - } - }, - "groupMetric" : { - "type" : "keyword" - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_partition_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_partition_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "LogStartOffset" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "LogEndOffset" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_replication_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_replication_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "LogStartOffset" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "LogEndOffset" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${esaddr}:${port}/_template/ks_kafka_topic_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_topic_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "topic" : { - "type" : "keyword" - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "BytesIn_min_15" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "BytesRejected" : { - "type" : "float" - }, - "PartitionURP" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "ReplicationCount" : { - "type" : "float" - }, - "ReplicationBytesOut" : { - "type" : "float" - }, - "ReplicationBytesIn" : { - "type" : "float" - }, - "FailedFetchRequests" : { - "type" : "float" - }, - "BytesIn_min_5" : { - "type" : "float" - }, - "HealthScore" : { - "type" : "float" - }, - "LogSize" : { - "type" : "float" - }, - "BytesOut" : { - "type" : "float" - }, - "BytesOut_min_15" : { - "type" : "float" - }, - "FailedProduceRequests" : { - "type" : "float" - }, - "BytesIn" : { - "type" : "float" - }, - "BytesOut_min_5" : { - "type" : "float" - }, - "MessagesIn" : { - "type" : "float" - }, - "TotalProduceRequests" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - } - } - }, - "brokerAgg" : { - "type" : "keyword" - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${SERVER_ES_ADDRESS}/_template/ks_kafka_zookeeper_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_zookeeper_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "AvgRequestLatency" : { - "type" : "double" - }, - "MinRequestLatency" : { - "type" : "double" - }, - "MaxRequestLatency" : { - "type" : "double" - }, - "OutstandingRequests" : { - "type" : "double" - }, - "NodeCount" : { - "type" : "double" - }, - "WatchCount" : { - "type" : "double" - }, - "NumAliveConnections" : { - "type" : "double" - }, - "PacketsReceived" : { - "type" : "double" - }, - "PacketsSent" : { - "type" : "double" - }, - "EphemeralsCount" : { - "type" : "double" - }, - "ApproximateDataSize" : { - "type" : "double" - }, - "OpenFileDescriptorCount" : { - "type" : "double" - }, - "MaxFileDescriptorCount" : { - "type" : "double" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "type" : "date" - } - } - }, - "aliases" : { } - }' - -curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${SERVER_ES_ADDRESS}/_template/ks_kafka_zookeeper_metric -d '{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_zookeeper_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "AvgRequestLatency" : { - "type" : "double" - }, - "MinRequestLatency" : { - "type" : "double" - }, - "MaxRequestLatency" : { - "type" : "double" - }, - "OutstandingRequests" : { - "type" : "double" - }, - "NodeCount" : { - "type" : "double" - }, - "WatchCount" : { - "type" : "double" - }, - "NumAliveConnections" : { - "type" : "double" - }, - "PacketsReceived" : { - "type" : "double" - }, - "PacketsSent" : { - "type" : "double" - }, - "EphemeralsCount" : { - "type" : "double" - }, - "ApproximateDataSize" : { - "type" : "double" - }, - "OpenFileDescriptorCount" : { - "type" : "double" - }, - "MaxFileDescriptorCount" : { - "type" : "double" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "type" : "date" - } - } - }, - "aliases" : { } - }' - -for i in {0..6}; -do - logdate=_$(date -d "${i} day ago" +%Y-%m-%d) - curl -s --connect-timeout 10 -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_broker_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_cluster_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_group_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_partition_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_replication_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_zookeeper_metric${logdate} && \ - curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_topic_metric${logdate} || \ - exit 2 -done diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_group_metric b/km-persistence/src/main/resources/es/template/ks_kafka_group_metric index 3f04f16a..24ff12de 100644 --- a/km-persistence/src/main/resources/es/template/ks_kafka_group_metric +++ b/km-persistence/src/main/resources/es/template/ks_kafka_group_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric b/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric index 68264660..51193c22 100644 --- a/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric +++ b/km-persistence/src/main/resources/es/template/ks_kafka_partition_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric b/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric index b2eed35d..4a1aa70c 100644 --- a/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric +++ b/km-persistence/src/main/resources/es/template/ks_kafka_topic_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "6" } }, "mappings" : { diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric b/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric index e2efb41a..10c9324b 100644 --- a/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric +++ b/km-persistence/src/main/resources/es/template/ks_kafka_zookeeper_metric @@ -5,7 +5,7 @@ ], "settings" : { "index" : { - "number_of_shards" : "10" + "number_of_shards" : "2" } }, "mappings" : { From dd6004b9d4eda9823305fdcf26159193b2d79b59 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 11 Jan 2023 11:21:34 +0800 Subject: [PATCH 089/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E9=87=87?= =?UTF-8?q?=E9=9B=86=E5=89=AF=E6=9C=AC=E6=8C=87=E6=A0=87=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E4=BC=A0=E9=80=92=E9=94=99=E8=AF=AF=E9=97=AE?= =?UTF-8?q?=E9=A2=98(#867)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/service/broker/impl/BrokerMetricServiceImpl.java | 2 +- .../service/replica/impl/ReplicaMetricServiceImpl.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java index a1320b90..20d89362 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java @@ -373,8 +373,8 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker Result metricsResult = replicaMetricService.collectReplicaMetricsFromKafka( clusterId, p.getTopicName(), - brokerId, p.getPartitionId(), + brokerId, ReplicaMetricVersionItems.REPLICATION_METRIC_LOG_SIZE ); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java index 92986d4b..6b0c28d2 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/replica/impl/ReplicaMetricServiceImpl.java @@ -78,8 +78,8 @@ public class ReplicaMetricServiceImpl extends BaseMetricService implements Repli Result ret = this.collectReplicaMetricsFromKafka( clusterId, metrics.getTopic(), - metrics.getBrokerId(), metrics.getPartitionId(), + metrics.getBrokerId(), metricName ); @@ -146,8 +146,8 @@ public class ReplicaMetricServiceImpl extends BaseMetricService implements Repli Integer brokerId = metricParam.getBrokerId(); Integer partitionId = metricParam.getPartitionId(); - Result endRet = this.collectReplicaMetricsFromKafka(clusterId, topic, brokerId, partitionId, REPLICATION_METRIC_LOG_END_OFFSET); - Result startRet = this.collectReplicaMetricsFromKafka(clusterId, topic, brokerId, partitionId, REPLICATION_METRIC_LOG_START_OFFSET); + Result endRet = this.collectReplicaMetricsFromKafka(clusterId, topic, partitionId, brokerId, REPLICATION_METRIC_LOG_END_OFFSET); + Result startRet = this.collectReplicaMetricsFromKafka(clusterId, topic, partitionId, brokerId, REPLICATION_METRIC_LOG_START_OFFSET); ReplicationMetrics replicationMetrics = new ReplicationMetrics(clusterId, topic, brokerId, partitionId); if(null != endRet && endRet.successful() && null != startRet && startRet.successful()){ @@ -155,6 +155,8 @@ public class ReplicaMetricServiceImpl extends BaseMetricService implements Repli Float startOffset = startRet.getData().getMetrics().get(REPLICATION_METRIC_LOG_START_OFFSET); replicationMetrics.putMetric(metric, endOffset - startOffset); + replicationMetrics.putMetric(REPLICATION_METRIC_LOG_END_OFFSET, endOffset); + replicationMetrics.putMetric(REPLICATION_METRIC_LOG_START_OFFSET, startOffset); } return Result.buildSuc(replicationMetrics); From eab988e18f4708c4cd5c86a467d128198e9c3218 Mon Sep 17 00:00:00 2001 From: wuyouwuyoulian Date: Sat, 7 Jan 2023 09:07:45 +0800 Subject: [PATCH 090/150] For #781, Fix "The partition display is incomplete" bug --- .../src/pages/ConsumerGroup/ExpandedRow.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx index fac1e99a..3e951943 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx @@ -167,8 +167,11 @@ export const ExpandedRow: any = ({ record, groupName }: any) => { ) .then((data: any) => { if (!data) return; - setPagination((origin: any) => { - return { ...origin, current: data.pagination?.pageNo, pageSize: data.pagination?.pageSize }; + + setPagination({ + current: data.pagination?.pageNo, + pageSize: data.pagination?.pageSize, + total: data.pagination?.total, }); setConsumerList(data?.bizData || []); }) From 0e50bfc5d4967d08f9f1a78594e75718d4a5d510 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 13 Jan 2023 16:04:25 +0800 Subject: [PATCH 091/150] =?UTF-8?q?=E4=BC=98=E5=8C=96PR=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/PULL_REQUEST_TEMPLATE.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9ca3226e..6f868666 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,9 +14,10 @@ XXXX 请遵循此清单,以帮助我们快速轻松地整合您的贡献: -* [ ] 确保有针对更改提交的 Github issue(通常在您开始处理之前)。诸如拼写错误之类的琐碎更改不需要 Github issue。您的Pull Request应该只解决这个问题,而不需要进行其他更改—— 一个 PR 解决一个问题。 -* [ ] 格式化 Pull Request 标题,如[ISSUE #123] support Confluent Schema Registry。 Pull Request 中的每个提交都应该有一个有意义的主题行和正文。 -* [ ] 编写足够详细的Pull Request描述,以了解Pull Request的作用、方式和原因。 -* [ ] 编写必要的单元测试来验证您的逻辑更正。如果提交了新功能或重大更改,请记住在test 模块中添加 integration-test -* [ ] 确保编译通过,集成测试通过 +* [ ] 一个 PR(Pull Request的简写)只解决一个问题,禁止一个 PR 解决多个问题; +* [ ] 确保 PR 有对应的 Issue(通常在您开始处理之前创建),除非是书写错误之类的琐碎更改不需要 Issue ; +* [ ] 格式化 PR 及 Commit-Log 的标题及内容,例如 #861 。PS:Commit-Log 需要在 Git Commit 代码时进行填写,在 GitHub 上修改不了; +* [ ] 编写足够详细的 PR 描述,以了解 PR 的作用、方式和原因; +* [ ] 编写必要的单元测试来验证您的逻辑更正。如果提交了新功能或重大更改,请记住在 test 模块中添加 integration-test; +* [ ] 确保编译通过,集成测试通过; From a02760417b51f9a87a420b3eb4cca3dbf5545a9b Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 30 Jan 2023 13:16:43 +0800 Subject: [PATCH 092/150] =?UTF-8?q?[Optimize]ZK-Overview=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=BB=98=E8=AE=A4=E5=B1=95=E7=A4=BA=E7=9A=84?= =?UTF-8?q?=E6=8C=87=E6=A0=87(#874)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/VersionControlManagerImpl.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java index 6abfebba..d87e1bcc 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java @@ -34,6 +34,7 @@ import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafk import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems.*; @Service public class VersionControlManagerImpl implements VersionControlManager { @@ -48,6 +49,7 @@ public class VersionControlManagerImpl implements VersionControlManager { @PostConstruct public void init(){ + // topic defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_STATE, 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)); @@ -58,6 +60,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)); + // cluster defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_STATE, 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)); @@ -73,11 +76,13 @@ public class VersionControlManagerImpl implements VersionControlManager { defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_GROUP_REBALANCES, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_JOB_RUNNING, true)); + // group 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)); + // broker defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_STATE, 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)); @@ -91,6 +96,24 @@ public class VersionControlManagerImpl implements VersionControlManager { defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_PARTITIONS_SKEW, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_BYTES_IN, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_BYTES_OUT, true)); + + // zookeeper + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_HEALTH_STATE, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_MAX_REQUEST_LATENCY, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_NODE_COUNT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_WATCH_COUNT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_NUM_ALIVE_CONNECTIONS, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_PACKETS_RECEIVED, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_PACKETS_SENT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_EPHEMERALS_COUNT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_APPROXIMATE_DATA_SIZE, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_OPEN_FILE_DESCRIPTOR_COUNT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_KAFKA_ZK_DISCONNECTS_PER_SEC, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_KAFKA_ZK_SYNC_CONNECTS_PER_SEC, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_KAFKA_ZK_REQUEST_LATENCY_99TH, true)); } @Autowired From c275b426327bc7f834be6e4249da0ff1578a6607 Mon Sep 17 00:00:00 2001 From: congchen0321 <118714181+congchen0321@users.noreply.github.com> Date: Mon, 12 Dec 2022 15:56:55 +0800 Subject: [PATCH 093/150] Update faq.md --- docs/user_guide/faq.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/user_guide/faq.md b/docs/user_guide/faq.md index a91cdf79..553c809a 100644 --- a/docs/user_guide/faq.md +++ b/docs/user_guide/faq.md @@ -182,3 +182,16 @@ Node 版本: v12.22.12 + 原因:由于数据库编码和我们提供的脚本不一致,数据库里的数据发生了乱码,因此出现权限识别失败问题。 + 解决方案:清空数据库数据,将数据库字符集调整为utf8,最后重新执行[dml-logi.sql](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/sql/dml-logi.sql)脚本导入数据即可。 + +## 8.13、接入开启kerberos认证的kafka集群 +1.部署KnowStreaming的机器上安装krb客户端 +2.替换/etc/krb5.conf配置文件 +3.把kafka对应的keytab复制到改机器目录下 +4.接入集群时认证配置,配置信息根据实际情况填写 +{ + "security.protocol": "SASL_PLAINTEXT", + "sasl.mechanism": "GSSAPI", + "sasl.jaas.config": "com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true keyTab=\"/etc/keytab/kafka.keytab\" storeKey=true useTicketCache=false principal=\"kafka/kafka@TEST.COM\";", + "sasl.kerberos.service.name": "kafka" +} + From 67743b859a1dfda8569d842a14d45f4583916525 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 8 Feb 2023 13:49:30 +0800 Subject: [PATCH 094/150] =?UTF-8?q?[Optimize]=E8=A1=A5=E5=85=85Ldap?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=9A=84=E9=85=8D=E7=BD=AE=E8=AF=B4=E6=98=8E?= =?UTF-8?q?(#888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/user_guide/faq.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/user_guide/faq.md b/docs/user_guide/faq.md index 553c809a..06f3ee09 100644 --- a/docs/user_guide/faq.md +++ b/docs/user_guide/faq.md @@ -183,15 +183,42 @@ Node 版本: v12.22.12 + 原因:由于数据库编码和我们提供的脚本不一致,数据库里的数据发生了乱码,因此出现权限识别失败问题。 + 解决方案:清空数据库数据,将数据库字符集调整为utf8,最后重新执行[dml-logi.sql](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/sql/dml-logi.sql)脚本导入数据即可。 + ## 8.13、接入开启kerberos认证的kafka集群 -1.部署KnowStreaming的机器上安装krb客户端 -2.替换/etc/krb5.conf配置文件 -3.把kafka对应的keytab复制到改机器目录下 -4.接入集群时认证配置,配置信息根据实际情况填写 + +1. 部署KnowStreaming的机器上安装krb客户端; +2. 替换/etc/krb5.conf配置文件; +3. 把kafka对应的keytab复制到改机器目录下; +4. 接入集群时认证配置,配置信息根据实际情况填写; +```json { "security.protocol": "SASL_PLAINTEXT", "sasl.mechanism": "GSSAPI", "sasl.jaas.config": "com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true keyTab=\"/etc/keytab/kafka.keytab\" storeKey=true useTicketCache=false principal=\"kafka/kafka@TEST.COM\";", "sasl.kerberos.service.name": "kafka" } +``` + +## 8.14、对接Ldap的配置 + +```yaml +# 需要在application.yml中增加如下配置。相关配置的信息,按实际情况进行调整 +account: + ldap: + url: ldap://127.0.0.1:8080/ + basedn: DC=senz,DC=local + factory: com.sun.jndi.ldap.LdapCtxFactory + filter: sAMAccountName + security: + authentication: simple + principal: CN=search,DC=senz,DC=local + credentials: xxxxxxx + auth-user-registration: false # 是否注册到mysql,默认false + auth-user-registration-role: 1677 # 1677是超级管理员角色的id,如果赋予想默认赋予普通角色,可以到ks新建一个。 + +# 需要在application.yml中修改如下配置 +spring: + logi-security: + login-extend-bean-name: ksLdapLoginService # 表示使用ldap的service +``` From 16e251cbe83161ace0d46f0489a91fb50e025ccb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 8 Feb 2023 14:10:37 +0800 Subject: [PATCH 095/150] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=BC=80=E6=BA=90?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 1094 ++++++++++++++++++++++--------------------------------- 1 file changed, 433 insertions(+), 661 deletions(-) diff --git a/LICENSE b/LICENSE index 2def0e88..3a42fbf6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,661 +1,433 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. \ No newline at end of file + Apache License + + Version 2.0, January 2004 + + http://www.apache.org/licenses/ + + + + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + + + +1. Definitions. + + + + + "License" shall mean the terms and conditions for use, reproduction, + + and distribution as defined by Sections 1 through 9 of this document. + + + + + "Licensor" shall mean the copyright owner or entity authorized by + + the copyright owner that is granting the License. + + + + + "Legal Entity" shall mean the union of the acting entity and all + + other entities that control, are controlled by, or are under common + + control with that entity. For the purposes of this definition, + + "control" means (i) the power, direct or indirect, to cause the + + direction or management of such entity, whether by contract or + + otherwise, or (ii) ownership of fifty percent (50%) or more of the + + outstanding shares, or (iii) beneficial ownership of such entity. + + + + + "You" (or "Your") shall mean an individual or Legal Entity + + exercising permissions granted by this License. + + + + + "Source" form shall mean the preferred form for making modifications, + + including but not limited to software source code, documentation + + source, and configuration files. + + + + + "Object" form shall mean any form resulting from mechanical + + transformation or translation of a Source form, including but + + not limited to compiled object code, generated documentation, + + and conversions to other media types. + + + + + "Work" shall mean the work of authorship, whether in Source or + + Object form, made available under the License, as indicated by a + + copyright notice that is included in or attached to the work + + (an example is provided in the Appendix below). + + + + + "Derivative Works" shall mean any work, whether in Source or Object + + form, that is based on (or derived from) the Work and for which the + + editorial revisions, annotations, elaborations, or other modifications + + represent, as a whole, an original work of authorship. For the purposes + + of this License, Derivative Works shall not include works that remain + + separable from, or merely link (or bind by name) to the interfaces of, + + the Work and Derivative Works thereof. + + + + + "Contribution" shall mean any work of authorship, including + + the original version of the Work and any modifications or additions + + to that Work or Derivative Works thereof, that is intentionally + + submitted to Licensor for inclusion in the Work by the copyright owner + + or by an individual or Legal Entity authorized to submit on behalf of + + the copyright owner. For the purposes of this definition, "submitted" + + means any form of electronic, verbal, or written communication sent + + to the Licensor or its representatives, including but not limited to + + communication on electronic mailing lists, source code control systems, + + and issue tracking systems that are managed by, or on behalf of, the + + Licensor for the purpose of discussing and improving the Work, but + + excluding communication that is conspicuously marked or otherwise + + designated in writing by the copyright owner as "Not a Contribution." + + + + + "Contributor" shall mean Licensor and any individual or Legal Entity + + on behalf of whom a Contribution has been received by Licensor and + + subsequently incorporated within the Work. + + + + +2. Grant of Copyright License. Subject to the terms and conditions of + + this License, each Contributor hereby grants to You a perpetual, + + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + + copyright license to reproduce, prepare Derivative Works of, + + publicly display, publicly perform, sublicense, and distribute the + + Work and such Derivative Works in Source or Object form. + + + + +3. Grant of Patent License. Subject to the terms and conditions of + + this License, each Contributor hereby grants to You a perpetual, + + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + + (except as stated in this section) patent license to make, have made, + + use, offer to sell, sell, import, and otherwise transfer the Work, + + where such license applies only to those patent claims licensable + + by such Contributor that are necessarily infringed by their + + Contribution(s) alone or by combination of their Contribution(s) + + with the Work to which such Contribution(s) was submitted. If You + + institute patent litigation against any entity (including a + + cross-claim or counterclaim in a lawsuit) alleging that the Work + + or a Contribution incorporated within the Work constitutes direct + + or contributory patent infringement, then any patent licenses + + granted to You under this License for that Work shall terminate + + as of the date such litigation is filed. + + + + +4. Redistribution. You may reproduce and distribute copies of the + + Work or Derivative Works thereof in any medium, with or without + + modifications, and in Source or Object form, provided that You + + meet the following conditions: + + + + + (a) You must give any other recipients of the Work or + + Derivative Works a copy of this License; and + + + + + (b) You must cause any modified files to carry prominent notices + + stating that You changed the files; and + + + + + (c) You must retain, in the Source form of any Derivative Works + + that You distribute, all copyright, patent, trademark, and + + attribution notices from the Source form of the Work, + + excluding those notices that do not pertain to any part of + + the Derivative Works; and + + + + + (d) If the Work includes a "NOTICE" text file as part of its + + distribution, then any Derivative Works that You distribute must + + include a readable copy of the attribution notices contained + + within such NOTICE file, excluding those notices that do not + + pertain to any part of the Derivative Works, in at least one + + of the following places: within a NOTICE text file distributed + + as part of the Derivative Works; within the Source form or + + documentation, if provided along with the Derivative Works; or, + + within a display generated by the Derivative Works, if and + + wherever such third-party notices normally appear. The contents + + of the NOTICE file are for informational purposes only and + + do not modify the License. You may add Your own attribution + + notices within Derivative Works that You distribute, alongside + + or as an addendum to the NOTICE text from the Work, provided + + that such additional attribution notices cannot be construed + + as modifying the License. + + + + + You may add Your own copyright statement to Your modifications and + + may provide additional or different license terms and conditions + + for use, reproduction, or distribution of Your modifications, or + + for any such Derivative Works as a whole, provided Your use, + + reproduction, and distribution of the Work otherwise complies with + + the conditions stated in this License. + + + + +5. Submission of Contributions. Unless You explicitly state otherwise, + + any Contribution intentionally submitted for inclusion in the Work + + by You to the Licensor shall be under the terms and conditions of + + this License, without any additional terms or conditions. + + Notwithstanding the above, nothing herein shall supersede or modify + + the terms of any separate license agreement you may have executed + + with Licensor regarding such Contributions. + + + + +6. Trademarks. This License does not grant permission to use the trade + + names, trademarks, service marks, or product names of the Licensor, + + except as required for reasonable and customary use in describing the + + origin of the Work and reproducing the content of the NOTICE file. + + + + +7. Disclaimer of Warranty. Unless required by applicable law or + + agreed to in writing, Licensor provides the Work (and each + + Contributor provides its Contributions) on an "AS IS" BASIS, + + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + + implied, including, without limitation, any warranties or conditions + + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + + PARTICULAR PURPOSE. You are solely responsible for determining the + + appropriateness of using or redistributing the Work and assume any + + risks associated with Your exercise of permissions under this License. + + + + +8. Limitation of Liability. In no event and under no legal theory, + + whether in tort (including negligence), contract, or otherwise, + + unless required by applicable law (such as deliberate and grossly + + negligent acts) or agreed to in writing, shall any Contributor be + + liable to You for damages, including any direct, indirect, special, + + incidental, or consequential damages of any character arising as a + + result of this License or out of the use or inability to use the + + Work (including but not limited to damages for loss of goodwill, + + work stoppage, computer failure or malfunction, or any and all + + other commercial damages or losses), even if such Contributor + + has been advised of the possibility of such damages. + + + + +9. Accepting Warranty or Additional Liability. While redistributing + + the Work or Derivative Works thereof, You may choose to offer, + + and charge a fee for, acceptance of support, warranty, indemnity, + + or other liability obligations and/or rights consistent with this + + License. However, in accepting such obligations, You may act only + + on Your own behalf and on Your sole responsibility, not on behalf + + of any other Contributor, and only if You agree to indemnify, + + defend, and hold each Contributor harmless for any liability + + incurred by, or claims asserted against, such Contributor by reason + + of your accepting any such warranty or additional liability. + + + + +END OF TERMS AND CONDITIONS + + + + +APPENDIX: How to apply the Apache License to your work. + + + + + To apply the Apache License to your work, attach the following + + boilerplate notice, with the fields enclosed by brackets "{}" + + replaced with your own identifying information. (Don't include + + the brackets!) The text should be enclosed in the appropriate + + comment syntax for the file format. We also recommend that a + + file or class name and description of purpose be included on the + + same "printed page" as the copyright notice for easier + + identification within third-party archives. + + + + +Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. + + + + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + + + + + http://www.apache.org/licenses/LICENSE-2.0 + + + + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. \ No newline at end of file From 256f7709710f4a5aa6cfb636f63c3171222a7978 Mon Sep 17 00:00:00 2001 From: _haoqi <1148648445@qq.com> Date: Wed, 1 Feb 2023 14:24:55 +0800 Subject: [PATCH 096/150] [Feature]Support running tests with testcontainers(#870) --- km-biz/pom.xml | 4 - km-common/pom.xml | 4 - km-core/pom.xml | 10 -- km-persistence/pom.xml | 6 ++ km-rest/pom.xml | 34 ++++++- .../km/KnowStreamApplicationTest.java | 30 ++---- .../km/core/TopicMetricServiceTest.java | 4 +- .../cluster/ClusterPhyServiceTest.java | 54 +++++++++++ .../persistence/es/BrokerMetricESDAOTest.java | 14 +-- .../es/ClusterMetricESDAOTest.java | 4 +- .../persistence/es/GroupMetricESDAOTest.java | 11 +-- .../es/PartitionMetricESDAOTest.java | 5 +- .../persistence/es/TopicMetricESDAOTest.java | 22 ++--- .../test/kafka/KafkaContainerTest.java | 47 ++++++++++ .../streaming/test/kafka/env/KafkaEnv.java | 13 +++ .../know/streaming/test/km/KMBase.java | 69 ++++++++++++++ .../test/km/contrainer/KMContainer.java | 92 +++++++++++++++++++ .../know/streaming/test/km/env/KMEnv.java | 27 ++++++ pom.xml | 15 ++- 19 files changed, 383 insertions(+), 82 deletions(-) create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java diff --git a/km-biz/pom.xml b/km-biz/pom.xml index 54399210..b8c3457b 100644 --- a/km-biz/pom.xml +++ b/km-biz/pom.xml @@ -62,10 +62,6 @@ commons-lang commons-lang - - junit - junit - commons-codec diff --git a/km-common/pom.xml b/km-common/pom.xml index 08b90dd7..63c94a48 100644 --- a/km-common/pom.xml +++ b/km-common/pom.xml @@ -81,10 +81,6 @@ 3.0.2 - - junit - junit - org.projectlombok lombok diff --git a/km-core/pom.xml b/km-core/pom.xml index 031b591a..896d54d6 100644 --- a/km-core/pom.xml +++ b/km-core/pom.xml @@ -46,12 +46,6 @@ org.springframework.boot spring-boot-starter-aop - - org.springframework.boot - spring-boot-starter-test - ${springboot.version} - test - @@ -67,10 +61,6 @@ commons-lang commons-lang - - junit - junit - commons-codec diff --git a/km-persistence/pom.xml b/km-persistence/pom.xml index 2be4b11c..e1b0ff5a 100644 --- a/km-persistence/pom.xml +++ b/km-persistence/pom.xml @@ -37,6 +37,12 @@ io.github.zqrferrari logi-elasticsearch-client + + + junit + junit + + diff --git a/km-rest/pom.xml b/km-rest/pom.xml index 543d4483..5c8ea2b8 100644 --- a/km-rest/pom.xml +++ b/km-rest/pom.xml @@ -19,6 +19,8 @@ 2.3.7.RELEASE 5.3.19 + + false @@ -101,10 +103,6 @@ test - - junit - junit - io.dropwizard.metrics metrics-core @@ -133,6 +131,34 @@ spring-boot-actuator-autoconfigure ${springboot.version} + + + + org.testcontainers + kafka + test + + + + org.testcontainers + mysql + test + + + + com.mysql + mysql-connector-j + 8.0.32 + test + + + + + org.testcontainers + elasticsearch + test + + diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java index e8ce0d27..97556191 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java @@ -1,13 +1,10 @@ package com.xiaojukeji.know.streaming.km; import com.xiaojukeji.know.streaming.km.rest.KnowStreaming; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import com.xiaojukeji.know.streaming.test.km.KMBase; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.http.HttpHeaders; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -15,32 +12,19 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; /** * @author d06679 * @date 2019/4/11 - * + *

* 得使用随机端口号,这样行执行单元测试的时候,不会出现端口号占用的情况 */ @ActiveProfiles("test") @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = KnowStreaming.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class KnowStreamApplicationTest { - - protected HttpHeaders headers; - +public class KnowStreamApplicationTest extends KMBase { @LocalServerPort private Integer port; - @BeforeEach - public void setUp() { - // 获取 springboot server 监听的端口号 - // port = applicationContext.getWebServer().getPort(); - System.out.println( String.format("port is : [%d]", port)); -// -// headers = new HttpHeaders(); -// headers.add("X-SSO-USER", "zengqiao"); - } - - @Test - public void test() { - Assertions.assertNotNull(port); - } +// @Test +// public void test() { +// Assertions.assertNotNull(port); +// } } \ No newline at end of file diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/TopicMetricServiceTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/TopicMetricServiceTest.java index 36ad96c3..2a2c34a9 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/TopicMetricServiceTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/TopicMetricServiceTest.java @@ -18,12 +18,13 @@ import java.util.List; public class TopicMetricServiceTest extends KnowStreamApplicationTest { + Long clusterId = 1L; + @Autowired private TopicMetricService topicMetricService; @Test public void listTopicMetricsFromESTest(){ - Long clusterId = 1l; Long endTime = System.currentTimeMillis(); Long startTime = endTime - 3600 * 1000; @@ -47,7 +48,6 @@ public class TopicMetricServiceTest extends KnowStreamApplicationTest { @Test public void pagingTopicWithLatestMetricsFromESTest(){ - Long clusterId = 2l; List metricNameList = new ArrayList<>(); SearchSort sort = new SearchSort(); sort.setQueryName("LogSize"); diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java new file mode 100644 index 00000000..14f07072 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java @@ -0,0 +1,54 @@ +package com.xiaojukeji.know.streaming.km.core.service.cluster; + +import com.xiaojukeji.know.streaming.km.KnowStreamApplicationTest; +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterPhyAddDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig; +import com.xiaojukeji.know.streaming.km.common.converter.ClusterConverter; +import com.xiaojukeji.know.streaming.km.common.exception.AdminOperateException; +import com.xiaojukeji.know.streaming.km.common.exception.DuplicateException; +import com.xiaojukeji.know.streaming.km.common.exception.ParamErrorException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Properties; + +@Slf4j +public class ClusterPhyServiceTest extends KnowStreamApplicationTest { + @Autowired + private ClusterPhyService clusterPhyService; + + @Test + @Order(Integer.MIN_VALUE) + void addClusterPhyTest() { + try { + Properties properties = new Properties(); + JmxConfig jmxConfig = new JmxConfig(); + jmxConfig.setOpenSSL(false); + + ClusterPhyAddDTO dto = new ClusterPhyAddDTO(); + dto.setName("test"); + dto.setDescription(""); + dto.setKafkaVersion(kafkaVersion()); + dto.setJmxProperties(jmxConfig); + dto.setClientProperties(properties); + dto.setZookeeper(zookeeperUrl()); + dto.setBootstrapServers(bootstrapServers()); + Assertions.assertEquals(1, + clusterPhyService.addClusterPhy(ClusterConverter.convert2ClusterPhyPO(dto), "root")); + } catch (ParamErrorException | DuplicateException | AdminOperateException e) { + throw new RuntimeException(e); + } + } + + @Test + void listAllClustersTest() { + List clusterPhies = clusterPhyService.listAllClusters(); + Assertions.assertNotNull(clusterPhies); + log.info("集群列表:{}", clusterPhies); + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/BrokerMetricESDAOTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/BrokerMetricESDAOTest.java index f08a229a..fdb574ab 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/BrokerMetricESDAOTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/BrokerMetricESDAOTest.java @@ -6,6 +6,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchRange; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchSort; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.persistence.es.dao.BrokerMetricESDAO; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -14,8 +15,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +@Slf4j public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { + Long clusterId = 1L; + @Autowired private BrokerMetricESDAO brokerMetriceESDAO; @@ -25,7 +29,7 @@ public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { SearchSort def = new SearchSort("timestamp", true); String sortDsl = brokerMetriceESDAO.buildSortDsl(sort, def); - System.out.println(sortDsl); + log.info(sortDsl); } @Test @@ -33,7 +37,7 @@ public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { SearchRange sort = new SearchRange("age", 1232321f, 45345345345f); String sortDsl = brokerMetriceESDAO.buildRangeDsl(sort); - System.out.println(sortDsl); + log.info(sortDsl); } @Test @@ -44,12 +48,11 @@ public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { String matchDsl = brokerMetriceESDAO.buildMatchDsl(matches); - System.out.println(matchDsl); + log.info(matchDsl); } @Test public void getBrokerMetricsPointTest(){ - Long clusterId = 2L; Integer brokerId = 1; List metrics = Arrays.asList("BytesIn", "BytesIn_min_5"); Long endTime = System.currentTimeMillis(); @@ -63,7 +66,6 @@ public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listBrokerMetricesByBrokerIdsTest(){ - Long clusterId = 123L; List metrics = Arrays.asList("BytesInPerSec_min_1", "BytesInPerSec_min_15"); List brokerIds = Arrays.asList(1L); Long endTime = System.currentTimeMillis(); @@ -74,7 +76,6 @@ public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listBrokerMetricsByTopTest(){ - Long clusterId = 123L; List metrics = Arrays.asList("BytesInPerSec_min_1", "BytesInPerSec_min_15"); Long endTime = System.currentTimeMillis(); Long startTime = endTime - 4 * 60 * 60 * 1000; @@ -84,7 +85,6 @@ public class BrokerMetricESDAOTest extends KnowStreamApplicationTest { @Test public void getTopBrokerIdsTest(){ - Long clusterId = 123L; List metrics = Arrays.asList("BytesInPerSec_min_1", "BytesInPerSec_min_15"); Long endTime = System.currentTimeMillis(); Long startTime = endTime - 4 * 60 * 60 * 1000; diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ClusterMetricESDAOTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ClusterMetricESDAOTest.java index d0f96bff..46e3d31a 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ClusterMetricESDAOTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/ClusterMetricESDAOTest.java @@ -15,6 +15,8 @@ import java.util.*; public class ClusterMetricESDAOTest extends KnowStreamApplicationTest { + Long clusterId = 1L; + @Autowired private ClusterMetricESDAO clusterMetricESDAO; @@ -34,7 +36,6 @@ public class ClusterMetricESDAOTest extends KnowStreamApplicationTest { */ @Test public void getClusterMetricsPointTest(){ - Long clusterId = 1L; List metrics = Arrays.asList( "Connections", "BytesIn_min_15", "PartitionURP", "HealthScore_Topics", "EventQueueSize", "ActiveControllerCount", @@ -67,7 +68,6 @@ public class ClusterMetricESDAOTest extends KnowStreamApplicationTest { */ @Test public void getClusterLatestMetricsTest(){ - Long clusterId = 1L; List metrics = Collections.emptyList(); ClusterMetricPO clusterLatestMetrics = clusterMetricESDAO.getClusterLatestMetrics(clusterId, metrics); diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/GroupMetricESDAOTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/GroupMetricESDAOTest.java index bc6d5821..4d0c5654 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/GroupMetricESDAOTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/GroupMetricESDAOTest.java @@ -20,12 +20,13 @@ import java.util.Set; public class GroupMetricESDAOTest extends KnowStreamApplicationTest { + Long clusterId = 1L; + @Autowired private GroupMetricESDAO groupMetricESDAO; @Test public void listLatestMetricsAggByGroupTopicTest(){ - Long clusterPhyId = 2L; List groupTopicList = new ArrayList<>(); groupTopicList.add(new GroupTopic("g-know-streaming-123456", "know-streaming-test-251")); groupTopicList.add(new GroupTopic("test_group", "know-streaming-test-251")); @@ -33,14 +34,13 @@ public class GroupMetricESDAOTest extends KnowStreamApplicationTest { List metrics = Arrays.asList("OffsetConsumed", "Lag"); AggTypeEnum aggType = AggTypeEnum.AVG; - List groupMetricPOS = groupMetricESDAO.listLatestMetricsAggByGroupTopic(clusterPhyId, groupTopicList, metrics, aggType); + List groupMetricPOS = groupMetricESDAO.listLatestMetricsAggByGroupTopic(clusterId, groupTopicList, metrics, aggType); assert !CollectionUtils.isEmpty(groupMetricPOS); } @Test public void listGroupTopicPartitionsTest(){ - Long clusterId = 2L; String groupName = "g-know-streaming-123456"; Long endTime = System.currentTimeMillis(); Long startTime = endTime - 24 * 3600 * 1000; @@ -51,17 +51,15 @@ public class GroupMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listPartitionLatestMetricsTest(){ - Long clusterId = 2L; String groupName = "test_group_20220421"; String topicName = "know-streaming-test-251"; List groupMetricPOS = groupMetricESDAO.listPartitionLatestMetrics(clusterId, groupName, topicName, null); - assert !CollectionUtils.isEmpty(groupMetricPOS); + assert CollectionUtils.isEmpty(groupMetricPOS); } @Test public void countMetricValueTest(){ - Long clusterId = 3L; String groupName = "test_group"; SearchTerm searchTerm = new SearchTerm("HealthCheckTotal", "1", false); @@ -75,7 +73,6 @@ public class GroupMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listGroupMetricsTest(){ - Long clusterId = 2L; String groupName = "g-know-streaming-123456"; Long endTime = System.currentTimeMillis(); Long startTime = endTime - 24 * 3600 * 1000; diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/PartitionMetricESDAOTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/PartitionMetricESDAOTest.java index 5721cbf0..9c8ac21b 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/PartitionMetricESDAOTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/PartitionMetricESDAOTest.java @@ -11,16 +11,17 @@ import java.util.List; public class PartitionMetricESDAOTest extends KnowStreamApplicationTest { + Long clusterId = 1L; + @Autowired private PartitionMetricESDAO partitionMetricESDAO; @Test public void listPartitionLatestMetricsByTopicTest(){ - Long clusterPhyId = 2L; String topic = "__consumer_offsets"; List partitionMetricPOS = partitionMetricESDAO.listPartitionLatestMetricsByTopic( - clusterPhyId, topic, new ArrayList<>()); + clusterId, topic, new ArrayList<>()); assert null != partitionMetricPOS; } diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/TopicMetricESDAOTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/TopicMetricESDAOTest.java index 09db0971..90ef291a 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/TopicMetricESDAOTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/persistence/es/TopicMetricESDAOTest.java @@ -8,22 +8,25 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchSort; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.TopicMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; import com.xiaojukeji.know.streaming.km.persistence.es.dao.TopicMetricESDAO; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +@Slf4j public class TopicMetricESDAOTest extends KnowStreamApplicationTest { + Long clusterId = 1L; + @Autowired private TopicMetricESDAO topicMetricESDAO; @Test public void listTopicMaxMinMetricsTest(){ - Long clusterId = 2L; String topic = "know-streaming-test-251"; String topic1 = "topic_test01"; Long endTime = System.currentTimeMillis(); @@ -36,7 +39,6 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { @Test public void getTopicsAggsMetricsValueTest(){ - Long clusterId = 2L; List topicList = Arrays.asList("know-streaming-test-251", "topic_test01"); List metrics = Arrays.asList( "Messages", "BytesIn_min_15", "BytesRejected", @@ -56,7 +58,6 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listTopicWithLatestMetricsTest(){ - Long clusterId = 2L; SearchSort sort = new SearchSort("LogSize", true); sort.setMetric(true); @@ -65,12 +66,11 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { List topicMetricPOS = topicMetricESDAO.listTopicWithLatestMetrics(clusterId, sort, fuzzy, null, terms); - assert !CollectionUtils.isEmpty(topicMetricPOS); + log.info("{}", topicMetricPOS); } @Test public void getTopicLatestMetricByBrokerIdTest(){ - Long clusterId = 2L; String topic = "know-streaming-test-251"; Integer brokerId = 1; @@ -81,7 +81,6 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { @Test public void getTopicLatestMetricTest(){ - Long clusterId = 2L; String topic = "know-streaming-test-251"; TopicMetricPO topicMetricPO = topicMetricESDAO.getTopicLatestMetric(clusterId, topic, new ArrayList<>()); @@ -91,7 +90,6 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listTopicLatestMetricTest(){ - Long clusterId = 2L; String topic = "know-streaming-test-251"; String topic1 = "know-streaming-123"; String topic2 = "1209test"; @@ -112,7 +110,6 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { @Test public void listBrokerMetricsByTopicsTest(){ - Long clusterId = 2L; List metrics = Arrays.asList( "Messages", "BytesIn_min_15", "BytesRejected", "PartitionURP", "HealthCheckTotal", "ReplicationCount", @@ -125,12 +122,13 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { Long endTime = System.currentTimeMillis(); Long startTime = endTime - 4 * 60 * 60 * 1000; - topicMetricESDAO.listTopicMetricsByTopics(clusterId, metrics, "avg", topics, startTime, endTime); + Table> list = + topicMetricESDAO.listTopicMetricsByTopics(clusterId, metrics, "avg", topics, startTime, endTime); + Assertions.assertNotNull(list); } @Test public void countMetricValueOccurrencesTest(){ - Long clusterPhyId = 2L; String topic = "__consumer_offsets"; String metricName = "HealthCheckPassed"; Float metricValue = 2f; @@ -142,7 +140,7 @@ public class TopicMetricESDAOTest extends KnowStreamApplicationTest { Long endTime = System.currentTimeMillis(); Long startTime = endTime - 4 * 60 * 60 * 1000; - Integer i = topicMetricESDAO.countMetricValue(clusterPhyId, topic, searchMatch, startTime, endTime); + Integer i = topicMetricESDAO.countMetricValue(clusterId, topic, searchMatch, startTime, endTime); assert null != i; } diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java new file mode 100644 index 00000000..49291ec9 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java @@ -0,0 +1,47 @@ +package com.xiaojukeji.know.streaming.test.kafka; + +import com.xiaojukeji.know.streaming.test.kafka.env.KafkaEnv; +import com.xiaojukeji.know.streaming.test.km.env.KMEnv; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.lifecycle.Startables; +import org.testcontainers.utility.DockerImageName; + +public class KafkaContainerTest implements KafkaEnv { + private static final String KAFKA_VERSION = "7.3.1"; + private static final DockerImageName KAFKA_IMAGE = DockerImageName.parse( + "confluentinc/cp-kafka" + KMEnv.SEPARATOR + KAFKA_VERSION); + static KafkaContainer KAFKA_CONTAINER = new KafkaContainer(KAFKA_IMAGE) + .withEnv("TZ", "Asia/Shanghai"); + + @Override + public void init() { + Startables.deepStart(KAFKA_CONTAINER).join(); + } + + @Override + public void cleanup() { + /* + * 不需要手动调用清理容器 + * 1. test执行结束后testcontainer会清理容器 + * 2. junit5的@AfterAll方法会在SpringBoot生命周期结束前执行,导致数据库连接无法关闭 + **/ +// if (KAFKA_CONTAINER != null) { +// KAFKA_CONTAINER.close(); +// } + } + + @Override + public String getBootstrapServers() { + return KAFKA_CONTAINER.getBootstrapServers(); + } + + @Override + public String getZKUrl() { + return String.format("%s:%d", KAFKA_CONTAINER.getHost(), KAFKA_CONTAINER.getMappedPort(2181)); + } + + @Override + public String getVersion() { + return KAFKA_VERSION; + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java new file mode 100644 index 00000000..e35c4d5b --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java @@ -0,0 +1,13 @@ +package com.xiaojukeji.know.streaming.test.kafka.env; + +public interface KafkaEnv { + void init(); + + void cleanup(); + + String getBootstrapServers(); + + String getZKUrl(); + + String getVersion(); +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java new file mode 100644 index 00000000..8383a009 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java @@ -0,0 +1,69 @@ +package com.xiaojukeji.know.streaming.test.km; + +import com.xiaojukeji.know.streaming.test.kafka.KafkaContainerTest; +import com.xiaojukeji.know.streaming.test.kafka.env.KafkaEnv; +import com.xiaojukeji.know.streaming.test.km.contrainer.KMContainer; +import com.xiaojukeji.know.streaming.test.km.env.KMEnv; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +public abstract class KMBase { + private static KMEnv kmEnv; + private static KafkaEnv kafkaEnv; + + @BeforeAll + static void init() { + if (container()) { + kmEnv = new KMContainer(); + kmEnv.init(); + + if (kmEnv.kafka()) { + kafkaEnv = new KafkaContainerTest(); + kafkaEnv.init(); + } + } + } + + + @DynamicPropertySource + static void setUp(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.know-streaming.jdbc-url", KMBase.kmEnv.jdbcUrl()); + + registry.add("spring.logi-job.jdbc-url", KMBase.kmEnv.jdbcUrl()); + + registry.add("spring.logi-security.jdbc-url", KMBase.kmEnv.jdbcUrl()); + + registry.add("spring.logi-security.jdbc-url", KMBase.kmEnv.jdbcUrl()); + + registry.add("es.client.address", KMBase.kmEnv.esUrl()); + } + + + @AfterAll + static void destroy() { + if (kmEnv != null) { + kmEnv.cleanup(); + } + if (kafkaEnv != null) { + kafkaEnv.cleanup(); + } + } + + static boolean container() { + return true; + } + + protected String kafkaVersion() { + return kafkaEnv.getVersion(); + } + + protected String bootstrapServers() { + return kafkaEnv.getBootstrapServers(); + } + + protected String zookeeperUrl() { + return kafkaEnv.getZKUrl(); + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java new file mode 100644 index 00000000..78bb52b5 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java @@ -0,0 +1,92 @@ +package com.xiaojukeji.know.streaming.test.km.contrainer; + +import com.xiaojukeji.know.streaming.test.km.env.KMEnv; +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.lifecycle.Startables; +import org.testcontainers.utility.DockerImageName; + +import java.util.function.Supplier; + +public class KMContainer implements KMEnv { + private static final String ES_VERSION = "7.6.2"; + private static final String LATEST_VERSION = "latest"; + + private static final String DB_PROPERTY = "?useUnicode=true" + + "&characterEncoding=utf8" + + "&jdbcCompliantTruncation=true" + + "&allowMultiQueries=true" + + "&useSSL=false" + + "&alwaysAutoGeneratedKeys=true" + + "&serverTimezone=GMT%2B8" + + "&allowPublicKeyRetrieval=true"; + private static final DockerImageName ES_IMAGE = DockerImageName.parse( + "docker.io/library/elasticsearch" + KMEnv.SEPARATOR + ES_VERSION) + .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"); + private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse( + "knowstreaming/knowstreaming-mysql" + KMEnv.SEPARATOR + LATEST_VERSION) + .asCompatibleSubstituteFor("mysql"); + + private static final DockerImageName INIT_IMAGE = DockerImageName.parse( + "knowstreaming/knowstreaming-manager" + KMEnv.SEPARATOR + LATEST_VERSION); + + private static final ElasticsearchContainer ES_CONTAINER = new ElasticsearchContainer(ES_IMAGE) +// .withImagePullPolicy(PullPolicy.alwaysPull()) + .withEnv("TZ", "Asia/Shanghai") + .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") + .withEnv("discovery.type", "single-node"); + private static final GenericContainer INIT_CONTAINER = new GenericContainer<>(INIT_IMAGE) + .withEnv("TZ", "Asia/Shanghai") + .withCommand("/bin/bash", "/es_template_create.sh") + .dependsOn(ES_CONTAINER); + private static final MySQLContainer MYSQL_CONTAINER = new MySQLContainer<>(MYSQL_IMAGE) + .withEnv("MYSQL_ROOT_HOST", "%") + .withEnv("TZ", "Asia/Shanghai") + .withDatabaseName("know_streaming") + .withUsername("root") + .withPassword("mysql_pass"); + + + @NotNull + public Supplier jdbcUrl() { + return () -> "jdbc:mariadb://" + + MYSQL_CONTAINER.getHost() + ":" + MYSQL_CONTAINER.getMappedPort(3306) + + "/know_streaming" + DB_PROPERTY; + } + + @NotNull + public Supplier esUrl() { + return () -> ES_CONTAINER.getHost() + ":" + ES_CONTAINER.getMappedPort(9200); + } + + @Override + public void init() { + if (es()) { + Startables.deepStart(ES_CONTAINER, INIT_CONTAINER).join(); + } + + if (mysql()) { + Startables.deepStart(MYSQL_CONTAINER).join(); + } + } + + @Override + public void cleanup() { + /* + * 不需要手动调用清理容器 + * 1. test执行结束后testcontainer会清理容器 + * 2. junit5的@AfterAll方法会在SpringBoot生命周期结束前执行,导致数据库连接无法关闭 + **/ +// if (ES_CONTAINER != null) { +// ES_CONTAINER.close(); +// } +// if (INIT_CONTAINER != null) { +// INIT_CONTAINER.close(); +// } +// if (MYSQL_CONTAINER != null) { +// MYSQL_CONTAINER.close(); +// } + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java new file mode 100644 index 00000000..8964d5e1 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java @@ -0,0 +1,27 @@ +package com.xiaojukeji.know.streaming.test.km.env; + +import java.util.function.Supplier; + +public interface KMEnv { + String SEPARATOR = ":"; + + void init(); + + void cleanup(); + + default boolean es() { + return true; + } + + default boolean mysql() { + return true; + } + + default boolean kafka() { + return true; + } + + Supplier jdbcUrl(); + + Supplier esUrl(); +} diff --git a/pom.xml b/pom.xml index 4c8d9344..e288d0ce 100644 --- a/pom.xml +++ b/pom.xml @@ -140,11 +140,6 @@ - - junit - junit - 4.12 - commons-lang commons-lang @@ -322,6 +317,16 @@ 1.5.4 + + + + org.testcontainers + testcontainers-bom + 1.17.6 + pom + import + + \ No newline at end of file From 915e48de220c1651c266022164b136e103568d96 Mon Sep 17 00:00:00 2001 From: haoqi <1148648445@qq.com> Date: Wed, 8 Feb 2023 19:12:56 +0800 Subject: [PATCH 097/150] =?UTF-8?q?[Optimize]=E8=A1=A5=E5=85=85Testcontain?= =?UTF-8?q?ers=E7=9A=84=E4=BD=BF=E7=94=A8=E8=AF=B4=E6=98=8E(#890)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/user_guide/faq.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/user_guide/faq.md b/docs/user_guide/faq.md index 06f3ee09..1656ec37 100644 --- a/docs/user_guide/faq.md +++ b/docs/user_guide/faq.md @@ -222,3 +222,7 @@ spring: logi-security: login-extend-bean-name: ksLdapLoginService # 表示使用ldap的service ``` + +## 8.15、测试时使用Testcontainers的说明 +1. 需要docker运行环境 [Testcontainers运行环境说明](https://www.testcontainers.org/supported_docker_environment/) +2. 如果本机没有docker,可以使用[远程访问docker](https://docs.docker.com/config/daemon/remote-access/) [Testcontainers配置说明](https://www.testcontainers.org/features/configuration/#customizing-docker-host-detection) \ No newline at end of file From da95c635039bb67c284d36dab5b19b00b7efc78b Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 8 Feb 2023 20:40:18 +0800 Subject: [PATCH 098/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96TestContain?= =?UTF-8?q?ers=E7=9B=B8=E5=85=B3=E4=BE=9D=E8=B5=96(#892)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、去除对mysql-connector-j的依赖; 2、整理代码; --- km-rest/pom.xml | 8 - .../km/KnowStreamApplicationTest.java | 4 +- .../cluster/ClusterPhyServiceTest.java | 3 +- .../know/streaming/test/KMTestEnvService.java | 74 ++++++++ .../test/container/BaseTestContainer.java | 7 + .../test/container/es/ESTestContainer.java | 43 +++++ .../container/kafka/KafkaTestContainer.java | 31 ++++ .../container/mysql/KSMySQLContainer.java | 172 ++++++++++++++++++ .../container/mysql/MySQLTestContainer.java | 44 +++++ .../test/kafka/KafkaContainerTest.java | 47 ----- .../streaming/test/kafka/env/KafkaEnv.java | 13 -- .../know/streaming/test/km/KMBase.java | 69 ------- .../test/km/contrainer/KMContainer.java | 92 ---------- .../know/streaming/test/km/env/KMEnv.java | 27 --- 14 files changed, 375 insertions(+), 259 deletions(-) create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/BaseTestContainer.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/kafka/KafkaTestContainer.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java create mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java delete mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java delete mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java delete mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java delete mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java delete mode 100644 km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java diff --git a/km-rest/pom.xml b/km-rest/pom.xml index 5c8ea2b8..091e2696 100644 --- a/km-rest/pom.xml +++ b/km-rest/pom.xml @@ -145,14 +145,6 @@ test - - com.mysql - mysql-connector-j - 8.0.32 - test - - - org.testcontainers elasticsearch diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java index 97556191..a023e21a 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/KnowStreamApplicationTest.java @@ -1,7 +1,7 @@ package com.xiaojukeji.know.streaming.km; import com.xiaojukeji.know.streaming.km.rest.KnowStreaming; -import com.xiaojukeji.know.streaming.test.km.KMBase; +import com.xiaojukeji.know.streaming.test.KMTestEnvService; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; @@ -19,7 +19,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = KnowStreaming.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class KnowStreamApplicationTest extends KMBase { +public class KnowStreamApplicationTest extends KMTestEnvService { @LocalServerPort private Integer port; diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java index 14f07072..c2a0b209 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/km/core/service/cluster/ClusterPhyServiceTest.java @@ -5,6 +5,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterPhyAddDTO import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig; import com.xiaojukeji.know.streaming.km.common.converter.ClusterConverter; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import com.xiaojukeji.know.streaming.km.common.exception.AdminOperateException; import com.xiaojukeji.know.streaming.km.common.exception.DuplicateException; import com.xiaojukeji.know.streaming.km.common.exception.ParamErrorException; @@ -33,7 +34,7 @@ public class ClusterPhyServiceTest extends KnowStreamApplicationTest { ClusterPhyAddDTO dto = new ClusterPhyAddDTO(); dto.setName("test"); dto.setDescription(""); - dto.setKafkaVersion(kafkaVersion()); + dto.setKafkaVersion(VersionEnum.V_2_5_1.getVersion()); dto.setJmxProperties(jmxConfig); dto.setClientProperties(properties); dto.setZookeeper(zookeeperUrl()); diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java new file mode 100644 index 00000000..f74d456f --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java @@ -0,0 +1,74 @@ +package com.xiaojukeji.know.streaming.test; + +import com.xiaojukeji.know.streaming.test.container.es.ESTestContainer; +import com.xiaojukeji.know.streaming.test.container.kafka.KafkaTestContainer; +import com.xiaojukeji.know.streaming.test.container.mysql.MySQLTestContainer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +public abstract class KMTestEnvService { + private static final boolean useES = true; + private static final boolean useMysql = true; + private static final boolean useKafka = true; + + + private static MySQLTestContainer mySQLTestContainer; + + private static ESTestContainer esTestContainer; + + private static KafkaTestContainer kafkaTestContainer; + + @BeforeAll + static void init() { + if (useES) { + mySQLTestContainer = new MySQLTestContainer(); + mySQLTestContainer.init(); + } + + if (useMysql) { + esTestContainer = new ESTestContainer(); + esTestContainer.init(); + } + + if (useKafka) { + kafkaTestContainer = new KafkaTestContainer(); + kafkaTestContainer.init(); + } + } + + + @DynamicPropertySource + static void setUp(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.know-streaming.jdbc-url", mySQLTestContainer.jdbcUrl()); + registry.add("spring.logi-job.jdbc-url", mySQLTestContainer.jdbcUrl()); + registry.add("spring.logi-security.jdbc-url", mySQLTestContainer.jdbcUrl()); + + registry.add("es.client.address", esTestContainer.esUrl()); + } + + + @AfterAll + static void destroy() { + if (mySQLTestContainer != null) { + mySQLTestContainer.cleanup(); + } + + if (esTestContainer != null) { + esTestContainer.cleanup(); + } + + if (kafkaTestContainer != null) { + kafkaTestContainer.cleanup(); + } + } + + protected String bootstrapServers() { + return kafkaTestContainer.getBootstrapServers(); + } + + protected String zookeeperUrl() { + return kafkaTestContainer.getZKUrl(); + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/BaseTestContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/BaseTestContainer.java new file mode 100644 index 00000000..4dba9fd0 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/BaseTestContainer.java @@ -0,0 +1,7 @@ +package com.xiaojukeji.know.streaming.test.container; + +public abstract class BaseTestContainer { + public abstract void init(); + + public abstract void cleanup(); +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java new file mode 100644 index 00000000..2a297252 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java @@ -0,0 +1,43 @@ +package com.xiaojukeji.know.streaming.test.container.es; + +import com.xiaojukeji.know.streaming.test.container.BaseTestContainer; +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.lifecycle.Startables; +import org.testcontainers.utility.DockerImageName; + +import java.util.function.Supplier; + +public class ESTestContainer extends BaseTestContainer { + + // es容器 + private static final ElasticsearchContainer ES_CONTAINER = new ElasticsearchContainer( + DockerImageName.parse("docker.io/library/elasticsearch:7.6.2").asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch") + ) + .withEnv("TZ", "Asia/Shanghai") + .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") + .withEnv("discovery.type", "single-node"); + + // km容器,需要初始化es索引模版 + private static final GenericContainer INIT_CONTAINER = new GenericContainer<>( + "knowstreaming/knowstreaming-manager:latest" + ) + .withEnv("TZ", "Asia/Shanghai") + .withCommand("/bin/bash", "/es_template_create.sh") + .dependsOn(ES_CONTAINER); + + @NotNull + public Supplier esUrl() { + return () -> ES_CONTAINER.getHost() + ":" + ES_CONTAINER.getMappedPort(9200); + } + + @Override + public void init() { + Startables.deepStart(ES_CONTAINER, INIT_CONTAINER).join(); + } + + @Override + public void cleanup() { + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/kafka/KafkaTestContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/kafka/KafkaTestContainer.java new file mode 100644 index 00000000..a87a4c9c --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/kafka/KafkaTestContainer.java @@ -0,0 +1,31 @@ +package com.xiaojukeji.know.streaming.test.container.kafka; + +import com.xiaojukeji.know.streaming.test.container.BaseTestContainer; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.lifecycle.Startables; +import org.testcontainers.utility.DockerImageName; + +public class KafkaTestContainer extends BaseTestContainer { + + // kafka容器 + private static final KafkaContainer KAFKA_CONTAINER = new KafkaContainer( + DockerImageName.parse("confluentinc/cp-kafka:7.3.1") + ).withEnv("TZ", "Asia/Shanghai"); + + @Override + public void init() { + Startables.deepStart(KAFKA_CONTAINER).join(); + } + + @Override + public void cleanup() { + } + + public String getBootstrapServers() { + return KAFKA_CONTAINER.getBootstrapServers(); + } + + public String getZKUrl() { + return String.format("%s:%d", KAFKA_CONTAINER.getHost(), KAFKA_CONTAINER.getMappedPort(2181)); + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java new file mode 100644 index 00000000..a656e6d9 --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java @@ -0,0 +1,172 @@ +package com.xiaojukeji.know.streaming.test.container.mysql; + +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +import java.util.Set; + +/** + * @author richardnorth + * @see org.testcontainers.containers.MySQLContainer + */ +public class KSMySQLContainer> extends JdbcDatabaseContainer { + public static final String NAME = "mysql"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mysql"); + + @Deprecated + public static final String DEFAULT_TAG = "5.7.34"; + + @Deprecated + public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart(); + + static final String DEFAULT_USER = "test"; + + static final String DEFAULT_PASSWORD = "test"; + + private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = "TC_MY_CNF"; + + public static final Integer MYSQL_PORT = 3306; + + private String databaseName = "test"; + + private String username = DEFAULT_USER; + + private String password = DEFAULT_PASSWORD; + + private static final String MYSQL_ROOT_USER = "root"; + + /** + * @deprecated use {@link MySQLContainer(DockerImageName)} instead + */ + @Deprecated + public KSMySQLContainer() { + this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); + } + + public KSMySQLContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public KSMySQLContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + addExposedPort(MYSQL_PORT); + } + + /** + * @return the ports on which to check if the container is ready + * @deprecated use {@link #getLivenessCheckPortNumbers()} instead + */ + @NotNull + @Override + @Deprecated + protected Set getLivenessCheckPorts() { + return super.getLivenessCheckPorts(); + } + + @Override + protected void configure() { + optionallyMapResourceParameterAsVolume( + MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, + "/etc/mysql/conf.d", + "mysql-default-conf" + ); + + addEnv("MYSQL_DATABASE", databaseName); + if (!MYSQL_ROOT_USER.equalsIgnoreCase(username)) { + addEnv("MYSQL_USER", username); + } + if (password != null && !password.isEmpty()) { + addEnv("MYSQL_PASSWORD", password); + addEnv("MYSQL_ROOT_PASSWORD", password); + } else if (MYSQL_ROOT_USER.equalsIgnoreCase(username)) { + addEnv("MYSQL_ALLOW_EMPTY_PASSWORD", "yes"); + } else { + throw new ContainerLaunchException("Empty password can be used only with the root user"); + } + setStartupAttempts(3); + } + + @Override + public String getDriverClassName() { + // KS改动的地方 + try { + Class.forName("org.mariadb.jdbc.Driver"); + return "org.mariadb.jdbc.Driver"; + } catch (ClassNotFoundException e) { + return "org.mariadb.jdbc.Driver"; + } + } + + @Override + public String getJdbcUrl() { + String additionalUrlParams = constructUrlParameters("?", "&"); + + // KS改动的地方 + return "jdbc:mariadb://" + getHost() + ":" + getMappedPort(MYSQL_PORT) + "/" + databaseName + additionalUrlParams; + } + + @Override + protected String constructUrlForConnection(String queryString) { + String url = super.constructUrlForConnection(queryString); + + if (!url.contains("useSSL=")) { + String separator = url.contains("?") ? "&" : "?"; + url = url + separator + "useSSL=false"; + } + + if (!url.contains("allowPublicKeyRetrieval=")) { + url = url + "&allowPublicKeyRetrieval=true"; + } + + return url; + } + + @Override + public String getDatabaseName() { + return databaseName; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getTestQueryString() { + return "SELECT 1"; + } + + public SELF withConfigurationOverride(String s) { + parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s); + return self(); + } + + @Override + public SELF withDatabaseName(final String databaseName) { + this.databaseName = databaseName; + return self(); + } + + @Override + public SELF withUsername(final String username) { + this.username = username; + return self(); + } + + @Override + public SELF withPassword(final String password) { + this.password = password; + return self(); + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java new file mode 100644 index 00000000..d232940a --- /dev/null +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java @@ -0,0 +1,44 @@ +package com.xiaojukeji.know.streaming.test.container.mysql; + +import com.xiaojukeji.know.streaming.test.container.BaseTestContainer; +import org.jetbrains.annotations.NotNull; +import org.testcontainers.lifecycle.Startables; +import org.testcontainers.utility.DockerImageName; + +import java.util.function.Supplier; + +public class MySQLTestContainer extends BaseTestContainer { + private static final String DB_PROPERTY = "?useUnicode=true" + + "&characterEncoding=utf8" + + "&jdbcCompliantTruncation=true" + + "&allowMultiQueries=true" + + "&useSSL=false" + + "&alwaysAutoGeneratedKeys=true" + + "&serverTimezone=GMT%2B8" + + "&allowPublicKeyRetrieval=true"; + + private static final KSMySQLContainer MYSQL_CONTAINER = new KSMySQLContainer<>( + DockerImageName.parse("knowstreaming/knowstreaming-mysql:latest").asCompatibleSubstituteFor("mysql") + ) + .withEnv("MYSQL_ROOT_HOST", "%") + .withEnv("TZ", "Asia/Shanghai") + .withDatabaseName("know_streaming") + .withUsername("root") + .withPassword("mysql_pass"); + + @NotNull + public Supplier jdbcUrl() { + return () -> "jdbc:mariadb://" + + MYSQL_CONTAINER.getHost() + ":" + MYSQL_CONTAINER.getMappedPort(3306) + + "/know_streaming" + DB_PROPERTY; + } + + @Override + public void init() { + Startables.deepStart(MYSQL_CONTAINER).join(); + } + + @Override + public void cleanup() { + } +} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java deleted file mode 100644 index 49291ec9..00000000 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/KafkaContainerTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.xiaojukeji.know.streaming.test.kafka; - -import com.xiaojukeji.know.streaming.test.kafka.env.KafkaEnv; -import com.xiaojukeji.know.streaming.test.km.env.KMEnv; -import org.testcontainers.containers.KafkaContainer; -import org.testcontainers.lifecycle.Startables; -import org.testcontainers.utility.DockerImageName; - -public class KafkaContainerTest implements KafkaEnv { - private static final String KAFKA_VERSION = "7.3.1"; - private static final DockerImageName KAFKA_IMAGE = DockerImageName.parse( - "confluentinc/cp-kafka" + KMEnv.SEPARATOR + KAFKA_VERSION); - static KafkaContainer KAFKA_CONTAINER = new KafkaContainer(KAFKA_IMAGE) - .withEnv("TZ", "Asia/Shanghai"); - - @Override - public void init() { - Startables.deepStart(KAFKA_CONTAINER).join(); - } - - @Override - public void cleanup() { - /* - * 不需要手动调用清理容器 - * 1. test执行结束后testcontainer会清理容器 - * 2. junit5的@AfterAll方法会在SpringBoot生命周期结束前执行,导致数据库连接无法关闭 - **/ -// if (KAFKA_CONTAINER != null) { -// KAFKA_CONTAINER.close(); -// } - } - - @Override - public String getBootstrapServers() { - return KAFKA_CONTAINER.getBootstrapServers(); - } - - @Override - public String getZKUrl() { - return String.format("%s:%d", KAFKA_CONTAINER.getHost(), KAFKA_CONTAINER.getMappedPort(2181)); - } - - @Override - public String getVersion() { - return KAFKA_VERSION; - } -} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java deleted file mode 100644 index e35c4d5b..00000000 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/kafka/env/KafkaEnv.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.xiaojukeji.know.streaming.test.kafka.env; - -public interface KafkaEnv { - void init(); - - void cleanup(); - - String getBootstrapServers(); - - String getZKUrl(); - - String getVersion(); -} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java deleted file mode 100644 index 8383a009..00000000 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/KMBase.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.xiaojukeji.know.streaming.test.km; - -import com.xiaojukeji.know.streaming.test.kafka.KafkaContainerTest; -import com.xiaojukeji.know.streaming.test.kafka.env.KafkaEnv; -import com.xiaojukeji.know.streaming.test.km.contrainer.KMContainer; -import com.xiaojukeji.know.streaming.test.km.env.KMEnv; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; - -public abstract class KMBase { - private static KMEnv kmEnv; - private static KafkaEnv kafkaEnv; - - @BeforeAll - static void init() { - if (container()) { - kmEnv = new KMContainer(); - kmEnv.init(); - - if (kmEnv.kafka()) { - kafkaEnv = new KafkaContainerTest(); - kafkaEnv.init(); - } - } - } - - - @DynamicPropertySource - static void setUp(DynamicPropertyRegistry registry) { - registry.add("spring.datasource.know-streaming.jdbc-url", KMBase.kmEnv.jdbcUrl()); - - registry.add("spring.logi-job.jdbc-url", KMBase.kmEnv.jdbcUrl()); - - registry.add("spring.logi-security.jdbc-url", KMBase.kmEnv.jdbcUrl()); - - registry.add("spring.logi-security.jdbc-url", KMBase.kmEnv.jdbcUrl()); - - registry.add("es.client.address", KMBase.kmEnv.esUrl()); - } - - - @AfterAll - static void destroy() { - if (kmEnv != null) { - kmEnv.cleanup(); - } - if (kafkaEnv != null) { - kafkaEnv.cleanup(); - } - } - - static boolean container() { - return true; - } - - protected String kafkaVersion() { - return kafkaEnv.getVersion(); - } - - protected String bootstrapServers() { - return kafkaEnv.getBootstrapServers(); - } - - protected String zookeeperUrl() { - return kafkaEnv.getZKUrl(); - } -} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java deleted file mode 100644 index 78bb52b5..00000000 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/contrainer/KMContainer.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.xiaojukeji.know.streaming.test.km.contrainer; - -import com.xiaojukeji.know.streaming.test.km.env.KMEnv; -import org.jetbrains.annotations.NotNull; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.elasticsearch.ElasticsearchContainer; -import org.testcontainers.lifecycle.Startables; -import org.testcontainers.utility.DockerImageName; - -import java.util.function.Supplier; - -public class KMContainer implements KMEnv { - private static final String ES_VERSION = "7.6.2"; - private static final String LATEST_VERSION = "latest"; - - private static final String DB_PROPERTY = "?useUnicode=true" + - "&characterEncoding=utf8" + - "&jdbcCompliantTruncation=true" + - "&allowMultiQueries=true" + - "&useSSL=false" + - "&alwaysAutoGeneratedKeys=true" + - "&serverTimezone=GMT%2B8" + - "&allowPublicKeyRetrieval=true"; - private static final DockerImageName ES_IMAGE = DockerImageName.parse( - "docker.io/library/elasticsearch" + KMEnv.SEPARATOR + ES_VERSION) - .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"); - private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse( - "knowstreaming/knowstreaming-mysql" + KMEnv.SEPARATOR + LATEST_VERSION) - .asCompatibleSubstituteFor("mysql"); - - private static final DockerImageName INIT_IMAGE = DockerImageName.parse( - "knowstreaming/knowstreaming-manager" + KMEnv.SEPARATOR + LATEST_VERSION); - - private static final ElasticsearchContainer ES_CONTAINER = new ElasticsearchContainer(ES_IMAGE) -// .withImagePullPolicy(PullPolicy.alwaysPull()) - .withEnv("TZ", "Asia/Shanghai") - .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") - .withEnv("discovery.type", "single-node"); - private static final GenericContainer INIT_CONTAINER = new GenericContainer<>(INIT_IMAGE) - .withEnv("TZ", "Asia/Shanghai") - .withCommand("/bin/bash", "/es_template_create.sh") - .dependsOn(ES_CONTAINER); - private static final MySQLContainer MYSQL_CONTAINER = new MySQLContainer<>(MYSQL_IMAGE) - .withEnv("MYSQL_ROOT_HOST", "%") - .withEnv("TZ", "Asia/Shanghai") - .withDatabaseName("know_streaming") - .withUsername("root") - .withPassword("mysql_pass"); - - - @NotNull - public Supplier jdbcUrl() { - return () -> "jdbc:mariadb://" - + MYSQL_CONTAINER.getHost() + ":" + MYSQL_CONTAINER.getMappedPort(3306) - + "/know_streaming" + DB_PROPERTY; - } - - @NotNull - public Supplier esUrl() { - return () -> ES_CONTAINER.getHost() + ":" + ES_CONTAINER.getMappedPort(9200); - } - - @Override - public void init() { - if (es()) { - Startables.deepStart(ES_CONTAINER, INIT_CONTAINER).join(); - } - - if (mysql()) { - Startables.deepStart(MYSQL_CONTAINER).join(); - } - } - - @Override - public void cleanup() { - /* - * 不需要手动调用清理容器 - * 1. test执行结束后testcontainer会清理容器 - * 2. junit5的@AfterAll方法会在SpringBoot生命周期结束前执行,导致数据库连接无法关闭 - **/ -// if (ES_CONTAINER != null) { -// ES_CONTAINER.close(); -// } -// if (INIT_CONTAINER != null) { -// INIT_CONTAINER.close(); -// } -// if (MYSQL_CONTAINER != null) { -// MYSQL_CONTAINER.close(); -// } - } -} diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java deleted file mode 100644 index 8964d5e1..00000000 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/km/env/KMEnv.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xiaojukeji.know.streaming.test.km.env; - -import java.util.function.Supplier; - -public interface KMEnv { - String SEPARATOR = ":"; - - void init(); - - void cleanup(); - - default boolean es() { - return true; - } - - default boolean mysql() { - return true; - } - - default boolean kafka() { - return true; - } - - Supplier jdbcUrl(); - - Supplier esUrl(); -} From d046cb8bf46e325554dd26e884761d6bea0a99c3 Mon Sep 17 00:00:00 2001 From: EricZeng Date: Thu, 9 Feb 2023 11:24:06 +0800 Subject: [PATCH 099/150] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: haoqi123 <49672871+haoqi123@users.noreply.github.com> --- .../com/xiaojukeji/know/streaming/test/KMTestEnvService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java index f74d456f..a3a7f200 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java @@ -22,7 +22,7 @@ public abstract class KMTestEnvService { @BeforeAll static void init() { - if (useES) { + if (useMysql) { mySQLTestContainer = new MySQLTestContainer(); mySQLTestContainer.init(); } From 3ce4bf231a1d1de1818115a871177a49ae50184b Mon Sep 17 00:00:00 2001 From: EricZeng Date: Thu, 9 Feb 2023 11:24:36 +0800 Subject: [PATCH 100/150] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: haoqi123 <49672871+haoqi123@users.noreply.github.com> --- .../com/xiaojukeji/know/streaming/test/KMTestEnvService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java index a3a7f200..acae3123 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java @@ -27,7 +27,7 @@ public abstract class KMTestEnvService { mySQLTestContainer.init(); } - if (useMysql) { + if (useES) { esTestContainer = new ESTestContainer(); esTestContainer.init(); } From 353d781bcacbb73934bdfa1cf503728142850194 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 13:42:43 +0800 Subject: [PATCH 101/150] =?UTF-8?q?[Feature]=E8=A1=A5=E5=85=85MM2=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E7=B4=A2=E5=BC=95=E5=8F=8A=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E8=A1=A8=E4=BF=A1=E6=81=AF(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/init_es_template.sh | 101 +++++++++++++++++++++++++++++ docs/install_guide/版本升级手册.md | 45 +++++++++++++ km-dist/init/sql/ddl-ks-km.sql | 20 +++++- km-dist/init/sql/dml-ks-km.sql | 11 +++- km-dist/init/sql/dml-logi.sql | 22 +++++++ 5 files changed, 195 insertions(+), 4 deletions(-) diff --git a/bin/init_es_template.sh b/bin/init_es_template.sh index 67e21685..e570d285 100644 --- a/bin/init_es_template.sh +++ b/bin/init_es_template.sh @@ -920,6 +920,106 @@ curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: appl "aliases" : { } }' +curl -s -o /dev/null -X POST -H 'cache-control: no-cache' -H 'content-type: application/json' http://${SERVER_ES_ADDRESS}/_template/ks_kafka_connect_mirror_maker_metric -d '{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_mirror_maker_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "connectorName" : { + "type" : "keyword" + }, + "connectorNameAndClusterId" : { + "type" : "keyword" + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "HealthState" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "ByteCount" : { + "type" : "float" + }, + "ByteRate" : { + "type" : "float" + }, + "RecordAgeMs" : { + "type" : "float" + }, + "RecordAgeMsAvg" : { + "type" : "float" + }, + "RecordAgeMsMax" : { + "type" : "float" + }, + "RecordAgeMsMin" : { + "type" : "float" + }, + "RecordCount" : { + "type" : "float" + }, + "RecordRate" : { + "type" : "float" + }, + "ReplicationLatencyMs" : { + "type" : "float" + }, + "ReplicationLatencyMsAvg" : { + "type" : "float" + }, + "ReplicationLatencyMsMax" : { + "type" : "float" + }, + "ReplicationLatencyMsMin" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + }' + + for i in {0..6}; do logdate=_$(date -d "${i} day ago" +%Y-%m-%d) @@ -930,6 +1030,7 @@ do curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_zookeeper_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_connect_cluster_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_connect_connector_metric${logdate} && \ + curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_connect_mirror_maker_metric${logdate} && \ curl -s -o /dev/null -X PUT http://${esaddr}:${port}/ks_kafka_topic_metric${logdate} || \ exit 2 done diff --git a/docs/install_guide/版本升级手册.md b/docs/install_guide/版本升级手册.md index 64a0319d..847f5876 100644 --- a/docs/install_guide/版本升级手册.md +++ b/docs/install_guide/版本升级手册.md @@ -11,6 +11,51 @@ ALTER TABLE `logi_security_user` CHANGE COLUMN `phone` `phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'mobile' ; +ALTER TABLE ks_kc_connector ADD `heartbeat_connector_name` varchar(512) DEFAULT '' COMMENT '心跳检测connector名称'; +ALTER TABLE ks_kc_connector ADD `checkpoint_connector_name` varchar(512) DEFAULT '' COMMENT '进度确认connector名称'; + +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_TOTAL_RECORD_ERRORS', '{\"value\" : 1}', 'MirrorMaker消息处理错误的次数', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_REPLICATION_LATENCY_MS_MAX', '{\"value\" : 6000}', 'MirrorMaker消息复制最大延迟时间', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_UNASSIGNED_TASK_COUNT', '{\"value\" : 20}', 'MirrorMaker未被分配的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_FAILED_TASK_COUNT', '{\"value\" : 10}', 'MirrorMaker失败状态的任务数量', 'admin'); + + +-- 多集群管理权限2023-01-05新增 +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2012', 'Topic-新增Topic复制', '1593', '1', '2', 'Topic-新增Topic复制', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2014', 'Topic-详情-取消Topic复制', '1593', '1', '2', 'Topic-详情-取消Topic复制', '0', 'know-streaming'); + +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2012', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2014', '0', 'know-streaming'); + + +-- 多集群管理权限2023-01-18新增 +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2016', 'MM2-新增', '1593', '1', '2', 'MM2-新增', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2018', 'MM2-编辑', '1593', '1', '2', 'MM2-编辑', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2020', 'MM2-删除', '1593', '1', '2', 'MM2-删除', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2022', 'MM2-重启', '1593', '1', '2', 'MM2-重启', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2024', 'MM2-暂停&恢复', '1593', '1', '2', 'MM2-暂停&恢复', '0', 'know-streaming'); + +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2016', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2018', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2020', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2022', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2024', '0', 'know-streaming'); + + +DROP TABLE IF EXISTS `ks_ha_active_standby_relation`; +CREATE TABLE `ks_ha_active_standby_relation` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `active_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT '主集群ID', + `standby_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT '备集群ID', + `res_name` varchar(192) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '资源名称', + `res_type` int(11) NOT NULL DEFAULT '-1' COMMENT '资源类型,0:集群,1:镜像Topic,2:主备Topic', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_res` (`res_type`,`active_cluster_phy_id`,`standby_cluster_phy_id`,`res_name`), + UNIQUE KEY `uniq_res_type_standby_cluster_res_name` (`res_type`,`standby_cluster_phy_id`,`res_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='HA主备关系表'; ``` ### 升级至 `3.2.0` 版本 diff --git a/km-dist/init/sql/ddl-ks-km.sql b/km-dist/init/sql/ddl-ks-km.sql index 30b51c20..c19de0a3 100644 --- a/km-dist/init/sql/ddl-ks-km.sql +++ b/km-dist/init/sql/ddl-ks-km.sql @@ -422,6 +422,8 @@ CREATE TABLE `ks_kc_connector` ( `state` varchar(45) NOT NULL DEFAULT '' COMMENT '状态', `topics` text COMMENT '访问过的Topics', `task_count` int(11) NOT NULL DEFAULT '0' COMMENT '任务数', + `heartbeat_connector_name` varchar(512) DEFAULT '' COMMENT '心跳检测connector名称', + `checkpoint_connector_name` varchar(512) DEFAULT '' COMMENT '进度确认connector名称', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), @@ -462,4 +464,20 @@ CREATE TABLE `ks_kc_worker_connector` ( `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uniq_relation` (`connect_cluster_id`,`connector_name`,`task_id`,`worker_member_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Worker和Connector关系表'; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Worker和Connector关系表'; + + +DROP TABLE IF EXISTS `ks_ha_active_standby_relation`; +CREATE TABLE `ks_ha_active_standby_relation` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `active_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT '主集群ID', + `standby_cluster_phy_id` bigint(20) NOT NULL DEFAULT '-1' COMMENT '备集群ID', + `res_name` varchar(192) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '资源名称', + `res_type` int(11) NOT NULL DEFAULT '-1' COMMENT '资源类型,0:集群,1:镜像Topic,2:主备Topic', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_cluster_res` (`res_type`,`active_cluster_phy_id`,`standby_cluster_phy_id`,`res_name`), + UNIQUE KEY `uniq_res_type_standby_cluster_res_name` (`res_type`,`standby_cluster_phy_id`,`res_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='HA主备关系表'; diff --git a/km-dist/init/sql/dml-ks-km.sql b/km-dist/init/sql/dml-ks-km.sql index 17600fa1..4ba81dc2 100644 --- a/km-dist/init/sql/dml-ks-km.sql +++ b/km-dist/init/sql/dml-ks-km.sql @@ -13,6 +13,11 @@ INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value 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'); -INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_FAILED_TASK_COUNT', '{\"value\" : 1}', 'connector失败状态的任务数量', 'admin'); -INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_UNASSIGNED_TASK_COUNT', '{\"value\" : 1}', 'connector未被分配的任务数量', 'admin'); -INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE', '{\"value\" : 0.05}', 'connecct集群任务启动失败概率', 'admin'); \ No newline at end of file +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_FAILED_TASK_COUNT', '{\"value\" : 1}', 'Connector失败状态的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECTOR_UNASSIGNED_TASK_COUNT', '{\"value\" : 1}', 'Connector未被分配的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE', '{\"value\" : 0.05}', 'Connect集群任务启动失败概率', 'admin'); + +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_TOTAL_RECORD_ERRORS', '{\"value\" : 1}', 'MirrorMaker消息处理错误的次数', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_REPLICATION_LATENCY_MS_MAX', '{\"value\" : 6000}', 'MirrorMaker消息复制最大延迟时间', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_UNASSIGNED_TASK_COUNT', '{\"value\" : 20}', 'MirrorMaker未被分配的任务数量', 'admin'); +INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_MIRROR_MAKER_FAILED_TASK_COUNT', '{\"value\" : 10}', 'MirrorMaker失败状态的任务数量', 'admin'); \ No newline at end of file diff --git a/km-dist/init/sql/dml-logi.sql b/km-dist/init/sql/dml-logi.sql index 6d6e8159..0ad09b4b 100644 --- a/km-dist/init/sql/dml-logi.sql +++ b/km-dist/init/sql/dml-logi.sql @@ -97,3 +97,25 @@ INSERT INTO `logi_security_config` (`value_group`,`value_name`,`value`,`edit`,`status`,`memo`,`is_delete`,`app_name`,`operator`) VALUES ('SECURITY.LOGIN','SECURITY.TRICK_USERS','[\n \"admin\"\n]',1,1,'允许跳过登录的用户',0,'know-streaming','admin'); + + +-- 多集群管理权限2023-01-05新增 +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2012', 'Topic-新增Topic复制', '1593', '1', '2', 'Topic-新增Topic复制', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2014', 'Topic-详情-取消Topic复制', '1593', '1', '2', 'Topic-详情-取消Topic复制', '0', 'know-streaming'); + +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2012', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2014', '0', 'know-streaming'); + + +-- 多集群管理权限2023-01-18新增 +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2016', 'MM2-新增', '1593', '1', '2', 'MM2-新增', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2018', 'MM2-编辑', '1593', '1', '2', 'MM2-编辑', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2020', 'MM2-删除', '1593', '1', '2', 'MM2-删除', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2022', 'MM2-重启', '1593', '1', '2', 'MM2-重启', '0', 'know-streaming'); +INSERT INTO `logi_security_permission` (`id`, `permission_name`, `parent_id`, `leaf`, `level`, `description`, `is_delete`, `app_name`) VALUES ('2024', 'MM2-暂停&恢复', '1593', '1', '2', 'MM2-暂停&恢复', '0', 'know-streaming'); + +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2016', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2018', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2020', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2022', '0', 'know-streaming'); +INSERT INTO `logi_security_role_permission` (`role_id`, `permission_id`, `is_delete`, `app_name`) VALUES ('1677', '2024', '0', 'know-streaming'); From 346aee8fe7f4963d99df72703eb4650013176bb1 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 14:14:41 +0800 Subject: [PATCH 102/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DTopic?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E5=A4=A7=E7=9B=98=E8=8E=B7=E5=8F=96TopN?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E5=AD=98=E5=9C=A8=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98(#896)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、将ES排序调整为基于本地cache的排序; 2、将database的本地cache从core模块移动到persistence模块; --- .../km/core/flusher/DatabaseDataFlusher.java | 2 +- .../impl/ClusterMetricServiceImpl.java | 2 +- .../impl/HealthCheckResultServiceImpl.java | 2 +- .../partition/impl/PartitionServiceImpl.java | 2 +- .../topic/impl/TopicMetricServiceImpl.java | 2 +- .../cache/DataBaseDataLocalCache.java | 61 +++++++++++++------ .../persistence/es/dao/TopicMetricESDAO.java | 42 ++++++++----- 7 files changed, 76 insertions(+), 37 deletions(-) rename {km-core/src/main/java/com/xiaojukeji/know/streaming/km/core => km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence}/cache/DataBaseDataLocalCache.java (59%) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java index 8f71a5ea..b91e27c1 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java @@ -10,7 +10,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.Topic; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil; -import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; 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.health.checkresult.HealthCheckResultService; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java index 9d2a7f70..d8c1387f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/cluster/impl/ClusterMetricServiceImpl.java @@ -33,7 +33,7 @@ import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; import com.xiaojukeji.know.streaming.km.common.utils.*; -import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.core.service.acl.KafkaAclService; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerMetricService; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java index 4f0640c2..0d15561f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java @@ -13,7 +13,7 @@ import com.xiaojukeji.know.streaming.km.common.enums.config.ConfigGroupEnum; 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.utils.ConvertUtil; -import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.config.PlatformClusterConfigService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java index f4688729..1c5ad2e5 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/partition/impl/PartitionServiceImpl.java @@ -22,7 +22,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; import com.xiaojukeji.know.streaming.km.common.utils.Triple; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; -import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.znode.brokers.PartitionMap; import com.xiaojukeji.know.streaming.km.persistence.kafka.zookeeper.znode.brokers.PartitionState; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java index d4a12b41..bc275821 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java @@ -28,7 +28,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.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.cache.CollectedMetricsLocalCache; -import com.xiaojukeji.know.streaming.km.core.cache.DataBaseDataLocalCache; +import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; 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.partition.PartitionMetricService; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java similarity index 59% rename from km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java rename to km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java index 5412988c..5c6a1fbf 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/DataBaseDataLocalCache.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java @@ -1,4 +1,4 @@ -package com.xiaojukeji.know.streaming.km.core.cache; +package com.xiaojukeji.know.streaming.km.persistence.cache; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -7,31 +7,58 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +@Component public class DataBaseDataLocalCache { - private static final Cache> topicLatestMetricsCache = Caffeine.newBuilder() - .expireAfterWrite(360, TimeUnit.SECONDS) - .maximumSize(500) - .build(); + @Value(value = "${cache.metric.topic-size:2000}") + private Long topicLatestMetricsCacheSize; - private static final Cache clusterLatestMetricsCache = Caffeine.newBuilder() - .expireAfterWrite(180, TimeUnit.SECONDS) - .maximumSize(500) - .build(); + @Value(value = "${cache.metric.cluster-size:2000}") + private Long clusterLatestMetricsCacheSize; - private static final Cache>> partitionsCache = Caffeine.newBuilder() - .expireAfterWrite(60, TimeUnit.SECONDS) - .maximumSize(500) - .build(); + @Value(value = "${cache.metadata.partition-size:2000}") + private Long partitionsCacheSize; - private static final Cache>> healthCheckResultCache = Caffeine.newBuilder() - .expireAfterWrite(90, TimeUnit.SECONDS) - .maximumSize(1000) - .build(); + @Value(value = "${cache.metadata.health-check-result-size:10000}") + private Long healthCheckResultCacheSize; + + private static Cache> topicLatestMetricsCache; + + private static Cache clusterLatestMetricsCache; + + private static Cache>> partitionsCache; + + private static Cache>> healthCheckResultCache; + + @PostConstruct + private void init() { + topicLatestMetricsCache = Caffeine.newBuilder() + .expireAfterWrite(360, TimeUnit.SECONDS) + .maximumSize(topicLatestMetricsCacheSize) + .build(); + + clusterLatestMetricsCache = Caffeine.newBuilder() + .expireAfterWrite(180, TimeUnit.SECONDS) + .maximumSize(clusterLatestMetricsCacheSize) + .build(); + + partitionsCache = Caffeine.newBuilder() + .expireAfterWrite(60, TimeUnit.SECONDS) + .maximumSize(partitionsCacheSize) + .build(); + + healthCheckResultCache = Caffeine.newBuilder() + .expireAfterWrite(90, TimeUnit.SECONDS) + .maximumSize(healthCheckResultCacheSize) + .build(); + } public static Map getTopicMetrics(Long clusterPhyId) { return topicLatestMetricsCache.getIfPresent(clusterPhyId); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java index 88c0a82f..5c35f221 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/TopicMetricESDAO.java @@ -4,14 +4,19 @@ import com.didiglobal.logi.elasticsearch.client.response.query.query.ESQueryResp import com.didiglobal.logi.elasticsearch.client.response.query.query.aggs.ESAggr; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchFuzzy; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchShould; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchTerm; import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchSort; import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.TopicMetricPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.enums.SortTypeEnum; import com.xiaojukeji.know.streaming.km.common.utils.MetricsUtils; +import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; +import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -308,25 +313,32 @@ public class TopicMetricESDAO extends BaseMetricESDAO { return table; } - //public for test - public Map> getTopNTopics(Long clusterPhyId, List metrics, - String aggType, int topN, - Long startTime, Long endTime){ - //1、获取需要查下的索引 - String realIndex = realIndex(startTime, endTime); + public Map> getTopNTopics(Long clusterPhyId, + List metricNameList, + String aggType, + int topN, + Long startTime, + Long endTime) { + Map metricsMap = DataBaseDataLocalCache.getTopicMetrics(clusterPhyId); + if (metricsMap == null) { + return new HashMap<>(); + } - //2、根据查询的时间区间大小来确定指标点的聚合区间大小 - String interval = MetricsUtils.getInterval(endTime - startTime); + List metricsList = new ArrayList<>(metricsMap.values()); - //3、构造agg查询条件 - String aggDsl = buildAggsDSL(metrics, aggType); + Map> resultMap = new HashMap<>(); + for (String metricName: metricNameList) { + metricsList = PaginationMetricsUtil.sortMetrics( + metricsList.stream().map(elem -> (BaseMetrics)elem).collect(Collectors.toList()), + metricName, + "topic", + SortTypeEnum.DESC.getSortType() + ).stream().map(elem -> (TopicMetrics)elem).collect(Collectors.toList()); - //4、查询es - String dsl = dslLoaderUtil.getFormatDslByFileName( - DslConstant.GET_TOPIC_AGG_TOP_METRICS, clusterPhyId, startTime, endTime, interval, aggDsl); + resultMap.put(metricName, metricsList.subList(0, Math.min(topN, metricsList.size())).stream().map(elem -> elem.getTopic()).collect(Collectors.toList())); + } - return esOpClient.performRequest(realIndex, dsl, - s -> handleTopTopicESQueryResponse(s, metrics, topN), 3); + return resultMap; } /**************************************************** private method ****************************************************/ From 9b7c41e804c2fd0e9e1e127259d0042859ed6c25 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 14:35:47 +0800 Subject: [PATCH 103/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-?= =?UTF-8?q?=E8=AF=BB=E5=86=99ES=E4=B8=AD=E7=9A=84MM2=E6=8C=87=E6=A0=87(#89?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metrics/mm2/MirrorMakerMetrics.java | 46 +++++++++ .../po/metrice/mm2/MirrorMakerMetricPO.java | 39 ++++++++ ...DAO.java => BaseConnectorMetricESDAO.java} | 21 ++-- .../connector/ConnectorMetricESDAO.java | 19 ++++ .../connect/mm2/MirrorMakerMetricESDAO.java | 18 ++++ .../km/persistence/es/dsls/DslConstant.java | 2 - .../es/template/TemplateConstant.java | 1 + .../ks_kafka_connect_mirror_maker_metric | 98 +++++++++++++++++++ 8 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerMetrics.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/mm2/MirrorMakerMetricPO.java rename km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/{ConnectorMetricESDAO.java => BaseConnectorMetricESDAO.java} (95%) create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/connector/ConnectorMetricESDAO.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/mm2/MirrorMakerMetricESDAO.java create mode 100644 km-persistence/src/main/resources/es/template/ks_kafka_connect_mirror_maker_metric diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerMetrics.java new file mode 100644 index 00000000..b44324ea --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerMetrics.java @@ -0,0 +1,46 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @author zengqiao + * @date 20/6/17 + */ +@Data +@NoArgsConstructor +@ToString +public class MirrorMakerMetrics extends BaseMetrics { + private Long connectClusterId; + + private String connectorName; + + private String connectorNameAndClusterId; + + public MirrorMakerMetrics(Long connectClusterId, String connectorName) { + super(null); + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.connectorNameAndClusterId = connectorName + "#" + connectClusterId; + } + + public MirrorMakerMetrics(Long clusterPhyId, Long connectClusterId, String connectorName) { + super(clusterPhyId); + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.connectorNameAndClusterId = connectorName + "#" + connectClusterId; + } + + public static MirrorMakerMetrics initWithMetric(Long connectClusterId, String connectorName, String metricName, Float value) { + MirrorMakerMetrics metrics = new MirrorMakerMetrics(connectClusterId, connectorName); + metrics.putMetric(metricName, value); + return metrics; + } + + @Override + public String unique() { + return "KCOR@" + connectClusterId + "@" + connectorName; + } +} \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/mm2/MirrorMakerMetricPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/mm2/MirrorMakerMetricPO.java new file mode 100644 index 00000000..5a823024 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/metrice/mm2/MirrorMakerMetricPO.java @@ -0,0 +1,39 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.metrice.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.BaseMetricESPO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import static com.xiaojukeji.know.streaming.km.common.utils.CommonUtils.monitorTimestamp2min; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MirrorMakerMetricPO extends BaseMetricESPO { + private Long connectClusterId; + + private String connectorName; + + /** + * 用于es内部排序 + */ + private String connectorNameAndClusterId; + + public MirrorMakerMetricPO(Long kafkaClusterPhyId, Long connectClusterId, String connectorName){ + super(kafkaClusterPhyId); + this.connectClusterId = connectClusterId; + this.connectorName = connectorName; + this.connectorNameAndClusterId = connectorName + "#" + connectClusterId; + } + + @Override + public String getKey() { + return "KCOR@" + clusterPhyId + "@" + connectClusterId + "@" + connectorName + "@" + monitorTimestamp2min(timestamp); + } + + @Override + public String getRoutingValue() { + return String.valueOf(connectClusterId); + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/BaseConnectorMetricESDAO.java similarity index 95% rename from km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java rename to km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/BaseConnectorMetricESDAO.java index 1c47e862..cd80b62c 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/ConnectorMetricESDAO.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/BaseConnectorMetricESDAO.java @@ -13,27 +13,16 @@ import com.xiaojukeji.know.streaming.km.common.utils.Triple; import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import com.xiaojukeji.know.streaming.km.persistence.es.dao.BaseMetricESDAO; import com.xiaojukeji.know.streaming.km.persistence.es.dsls.DslConstant; -import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import static com.xiaojukeji.know.streaming.km.common.constant.ESConstant.*; -import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_CONNECTOR_INDEX; -@Component -public class ConnectorMetricESDAO extends BaseMetricESDAO { - - @PostConstruct - public void init() { - super.indexName = CONNECT_CONNECTOR_INDEX; - checkCurrentDayIndexExist(); - register( this); - } +public class BaseConnectorMetricESDAO extends BaseMetricESDAO { /** - * 获取每个 metric 的 topN 个 connector 的指标,如果获取不到 topN 的 connectors, 则默认返回 defaultTopics 的指标 + * 获取每个 metric 的 topN 个 connector 的指标,如果获取不到 topN 的 connectors, 则默认返回 defaultConnectorList 的指标 */ public Table, List> listMetricsByTopN(Long clusterPhyId, List> defaultConnectorList, @@ -143,7 +132,7 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { for(Tuple connector : connectorList) { try { esTPService.submitSearchTask( - String.format("class=ConnectorMetricESDAO||method=listMetricsByConnectors||ClusterPhyId=%d||connectorName=%s", clusterPhyId, connector.getV2()), + String.format("class=BaseConnectorMetricESDAO||method=listMetricsByConnectors||ClusterPhyId=%d||connectorName=%s", clusterPhyId, connector.getV2()), 3000, () -> { String dsl = dslLoaderUtil.getFormatDslByFileName( @@ -318,7 +307,9 @@ public class ConnectorMetricESDAO extends BaseMetricESDAO { private Tuple splitConnectorNameAndClusterId(String connectorNameAndClusterId){ String[] ss = connectorNameAndClusterId.split("#"); - if(null == ss || ss.length != 2){return null;} + if(null == ss || ss.length != 2) { + return null; + } return new Tuple<>(ss[0], Long.valueOf(ss[1])); } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/connector/ConnectorMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/connector/ConnectorMetricESDAO.java new file mode 100644 index 00000000..167d5bac --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/connector/ConnectorMetricESDAO.java @@ -0,0 +1,19 @@ +package com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.connector; + +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.BaseConnectorMetricESDAO; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_CONNECTOR_INDEX; + +@Component +public class ConnectorMetricESDAO extends BaseConnectorMetricESDAO { + + @PostConstruct + public void init() { + super.indexName = CONNECT_CONNECTOR_INDEX; + checkCurrentDayIndexExist(); + register( this); + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/mm2/MirrorMakerMetricESDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/mm2/MirrorMakerMetricESDAO.java new file mode 100644 index 00000000..c6879d38 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dao/connect/mm2/MirrorMakerMetricESDAO.java @@ -0,0 +1,18 @@ +package com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.mm2; + +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.BaseConnectorMetricESDAO; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_MM2_INDEX; + +@Component +public class MirrorMakerMetricESDAO extends BaseConnectorMetricESDAO { + + @PostConstruct + public void init() { + super.indexName = CONNECT_MM2_INDEX; + checkCurrentDayIndexExist(); + register( this); + } +} diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java index 3e01e19f..83c0279c 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/dsls/DslConstant.java @@ -89,6 +89,4 @@ public class DslConstant { public static final String GET_CONNECTOR_AGG_LIST_METRICS = "ConnectorMetricESDAO/getConnectorAggListMetric"; public static final String GET_CONNECTOR_AGG_TOP_METRICS = "ConnectorMetricESDAO/getConnectorAggTopMetric"; - - } diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java index f74f79c6..52f7b6bf 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/es/template/TemplateConstant.java @@ -12,6 +12,7 @@ public class TemplateConstant { public static final String ZOOKEEPER_INDEX = "ks_kafka_zookeeper_metric"; public static final String CONNECT_CLUSTER_INDEX = "ks_kafka_connect_cluster_metric"; public static final String CONNECT_CONNECTOR_INDEX = "ks_kafka_connect_connector_metric"; + public static final String CONNECT_MM2_INDEX = "ks_kafka_connect_mirror_maker_metric"; private TemplateConstant() { } diff --git a/km-persistence/src/main/resources/es/template/ks_kafka_connect_mirror_maker_metric b/km-persistence/src/main/resources/es/template/ks_kafka_connect_mirror_maker_metric new file mode 100644 index 00000000..c95a36ed --- /dev/null +++ b/km-persistence/src/main/resources/es/template/ks_kafka_connect_mirror_maker_metric @@ -0,0 +1,98 @@ +{ + "order" : 10, + "index_patterns" : [ + "ks_kafka_connect_mirror_maker_metric*" + ], + "settings" : { + "index" : { + "number_of_shards" : "2" + } + }, + "mappings" : { + "properties" : { + "connectClusterId" : { + "type" : "long" + }, + "routingValue" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "connectorName" : { + "type" : "keyword" + }, + "connectorNameAndClusterId" : { + "type" : "keyword" + }, + "clusterPhyId" : { + "type" : "long" + }, + "metrics" : { + "properties" : { + "HealthState" : { + "type" : "float" + }, + "HealthCheckTotal" : { + "type" : "float" + }, + "ByteCount" : { + "type" : "float" + }, + "ByteRate" : { + "type" : "float" + }, + "RecordAgeMs" : { + "type" : "float" + }, + "RecordAgeMsAvg" : { + "type" : "float" + }, + "RecordAgeMsMax" : { + "type" : "float" + }, + "RecordAgeMsMin" : { + "type" : "float" + }, + "RecordCount" : { + "type" : "float" + }, + "RecordRate" : { + "type" : "float" + }, + "ReplicationLatencyMs" : { + "type" : "float" + }, + "ReplicationLatencyMsAvg" : { + "type" : "float" + }, + "ReplicationLatencyMsMax" : { + "type" : "float" + }, + "ReplicationLatencyMsMin" : { + "type" : "float" + } + } + }, + "key" : { + "type" : "text", + "fields" : { + "keyword" : { + "ignore_above" : 256, + "type" : "keyword" + } + } + }, + "timestamp" : { + "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", + "index" : true, + "type" : "date", + "doc_values" : true + } + } + }, + "aliases" : { } + } \ No newline at end of file From 6ba3dceb84de27d4f52bab4ffe141decd568f47b Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 14:47:08 +0800 Subject: [PATCH 104/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-?= =?UTF-8?q?=E9=87=87=E9=9B=86MM2=E6=8C=87=E6=A0=87(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sink/mm2/MirrorMakerMetricESSender.java | 33 ++ .../entity/connect/mm2/MirrorMakerTopic.java | 33 ++ .../mm2/MirrorMakerTopicPartitionMetrics.java | 38 ++ .../connect/mm2/MirrorMakerMetricParam.java | 26 ++ .../metric/mm2/MirrorMakerMetricEvent.java | 21 ++ .../streaming/km/common/jmx/JmxAttribute.java | 26 ++ .../know/streaming/km/common/jmx/JmxName.java | 6 + .../impl/ConnectorMetricServiceImpl.java | 2 +- .../connect/mm2/MirrorMakerMetricService.java | 28 ++ .../impl/MirrorMakerMetricServiceImpl.java | 324 ++++++++++++++++++ .../MirrorMakerMetricVersionItems.java | 100 +++++- .../mm2/metrics/MirrorMakerCollectorTask.java | 31 ++ 12 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/mm2/MirrorMakerMetricESSender.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/mm2/MirrorMakerTopic.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerTopicPartitionMetrics.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/mm2/MirrorMakerMetricParam.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/mm2/MirrorMakerMetricEvent.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerMetricService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerMetricServiceImpl.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/metrics/MirrorMakerCollectorTask.java diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/mm2/MirrorMakerMetricESSender.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/mm2/MirrorMakerMetricESSender.java new file mode 100644 index 00000000..3089a995 --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/sink/mm2/MirrorMakerMetricESSender.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.collector.sink.mm2; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.sink.AbstractMetricESSender; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.mm2.MirrorMakerMetricEvent; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.mm2.MirrorMakerMetricPO; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +import static com.xiaojukeji.know.streaming.km.persistence.es.template.TemplateConstant.CONNECT_MM2_INDEX; + +/** + * @author zengqiao + * @date 2022/12/20 + */ +@Component +public class MirrorMakerMetricESSender extends AbstractMetricESSender implements ApplicationListener { + protected static final ILog LOGGER = LogFactory.getLog(MirrorMakerMetricESSender.class); + + @PostConstruct + public void init(){ + LOGGER.info("method=init||msg=init finished"); + } + + @Override + public void onApplicationEvent(MirrorMakerMetricEvent event) { + send2es(CONNECT_MM2_INDEX, ConvertUtil.list2List(event.getMetricsList(), MirrorMakerMetricPO.class)); + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/mm2/MirrorMakerTopic.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/mm2/MirrorMakerTopic.java new file mode 100644 index 00000000..aea8a33c --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/mm2/MirrorMakerTopic.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * @author wyb + * @date 2022/12/14 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MirrorMakerTopic { + + /** + * mm2集群别名 + */ + private String clusterAlias; + + /** + * topic名称 + */ + private String topicName; + + /** + * partition在connect上的分布 Map + */ + private Map partitionMap; + +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerTopicPartitionMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerTopicPartitionMetrics.java new file mode 100644 index 00000000..ef17adc9 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/mm2/MirrorMakerTopicPartitionMetrics.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author wyb + * @date 2022/12/16 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MirrorMakerTopicPartitionMetrics extends BaseMetrics { + private Long connectClusterId; + + private String mirrorMakerName; + + private String clusterAlias; + + private String topicName; + + private Integer partitionId; + + private String workerId; + + @Override + public String unique() { + return "KCOR@" + connectClusterId + "@" + mirrorMakerName + "@" + clusterAlias + "@" + workerId + "@" + topicName + "@" + partitionId; + } + + public static MirrorMakerTopicPartitionMetrics initWithMetric(Long connectClusterId, String mirrorMakerName, String clusterAlias, String topicName, Integer partitionId, String workerId, String metricName, Float value) { + MirrorMakerTopicPartitionMetrics metrics = new MirrorMakerTopicPartitionMetrics(connectClusterId, mirrorMakerName, clusterAlias, topicName, partitionId, workerId); + metrics.putMetric(metricName, value); + return metrics; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/mm2/MirrorMakerMetricParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/mm2/MirrorMakerMetricParam.java new file mode 100644 index 00000000..c67f3128 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/metric/connect/mm2/MirrorMakerMetricParam.java @@ -0,0 +1,26 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.MetricParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author wyb + * @date 2022/12/15 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MirrorMakerMetricParam extends MetricParam { + private Long connectClusterId; + + private String mirrorMakerName; + + private List mirrorMakerTopicList; + + private String metric; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/mm2/MirrorMakerMetricEvent.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/mm2/MirrorMakerMetricEvent.java new file mode 100644 index 00000000..42e98ce7 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/event/metric/mm2/MirrorMakerMetricEvent.java @@ -0,0 +1,21 @@ +package com.xiaojukeji.know.streaming.km.common.bean.event.metric.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.BaseMetricEvent; +import lombok.Getter; + +import java.util.List; + +/** + * @author zengqiao + * @date 2022/12/20 + */ +@Getter +public class MirrorMakerMetricEvent extends BaseMetricEvent { + private final List metricsList; + + public MirrorMakerMetricEvent(Object source, List metricsList) { + super(source); + this.metricsList = metricsList; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java index 2a89a08c..3043b90f 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxAttribute.java @@ -144,6 +144,32 @@ public class JmxAttribute { public static final String TOTAL_RETRIES = "total-retries"; + /*********************************************************** mm2 ***********************************************************/ + + public static final String BYTE_COUNT = "byte-count"; + + public static final String BYTE_RATE = "byte-rate"; + + public static final String RECORD_AGE_MS = "record-age-ms"; + + public static final String RECORD_AGE_MS_AVG = "record-age-ms-avg"; + + public static final String RECORD_AGE_MS_MAX = "record-age-ms-max"; + + public static final String RECORD_AGE_MS_MIN = "record-age-ms-min"; + + public static final String RECORD_COUNT = "record-count"; + + public static final String RECORD_RATE = "record-rate"; + + public static final String REPLICATION_LATENCY_MS = "replication-latency-ms"; + + public static final String REPLICATION_LATENCY_MS_AVG = "replication-latency-ms-avg"; + + public static final String REPLICATION_LATENCY_MS_MAX = "replication-latency-ms-max"; + + public static final String REPLICATION_LATENCY_MS_MIN = "replication-latency-ms-min"; + private JmxAttribute() { } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java index 5e11e271..39217986 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/jmx/JmxName.java @@ -41,6 +41,8 @@ public class JmxName { public static final String JMX_SERVER_APP_INFO ="kafka.server:type=app-info"; + public static final String JMX_SERVER_TOPIC_MIRROR ="kafka.server:type=FetcherLagMetrics,name=ConsumerLag,clientId=*,topic=%s,partition=*"; + /*********************************************************** controller ***********************************************************/ public static final String JMX_CONTROLLER_ACTIVE_COUNT = "kafka.controller:type=KafkaController,name=ActiveControllerCount"; @@ -82,6 +84,10 @@ public class JmxName { public static final String JMX_CONNECTOR_TASK_ERROR_METRICS = "kafka.connect:type=task-error-metrics,connector=%s,task=%s"; + /*********************************************************** mm2 ***********************************************************/ + + public static final String JMX_MIRROR_MAKER_SOURCE = "kafka.connect.mirror:type=MirrorSourceConnector,target=%s,topic=%s,partition=%s"; + private JmxName() { } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java index 8c9fec4f..8792875d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorMetricServiceImpl.java @@ -34,7 +34,7 @@ import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerServic import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseConnectorMetricService; import com.xiaojukeji.know.streaming.km.persistence.connect.ConnectJMXClient; -import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.ConnectorMetricESDAO; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.connector.ConnectorMetricESDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerMetricService.java new file mode 100644 index 00000000..f4c833d8 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerMetricService.java @@ -0,0 +1,28 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.mm2.MetricsMirrorMakersDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; + +import java.util.List; + +/** + * @author wyb + * @date 2022/12/15 + */ +public interface MirrorMakerMetricService { + + Result collectMirrorMakerMetricsFromKafka(Long connectClusterPhyId, String mirrorMakerName, List mirrorMakerTopicList, String metricName); + + /** + * 从ES中获取一段时间内聚合计算之后的指标线 + */ + Result> listMirrorMakerClusterMetricsFromES(Long clusterPhyId, MetricsMirrorMakersDTO dto); + + Result> getLatestMetricsFromES(Long clusterPhyId, List> mirrorMakerList, List metricNameList); + + Result getLatestMetricsFromES(Long connectClusterId, String connectorName, List metricsNames); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerMetricServiceImpl.java new file mode 100644 index 00000000..83242841 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerMetricServiceImpl.java @@ -0,0 +1,324 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.mm2.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.mm2.MetricsMirrorMakersDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerTopicPartitionMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.connect.mm2.MirrorMakerMetricParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.connect.ConnectorMetricPO; +import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.mm2.MirrorMakerMetricPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionConnectJmxInfo; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; +import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerMetricService; +import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; +import com.xiaojukeji.know.streaming.km.core.service.version.BaseConnectorMetricService; +import com.xiaojukeji.know.streaming.km.persistence.connect.ConnectJMXClient; +import org.springframework.beans.factory.annotation.Autowired; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.mm2.MirrorMakerMetricESDAO; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*; +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_MIRROR_MAKER; + +/** + * @author wyb + * @date 2022/12/15 + */ +@Service +public class MirrorMakerMetricServiceImpl extends BaseConnectorMetricService implements MirrorMakerMetricService { + protected static final ILog LOGGER = LogFactory.getLog(MirrorMakerMetricServiceImpl.class); + + public static final String MIRROR_MAKER_METHOD_DO_NOTHING = "doNothing"; + + public static final String MIRROR_MAKER_METHOD_GET_HEALTH_SCORE = "getMetricHealthScore"; + + public static final String MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_SUM = "getTopicPartitionMetricListSum"; + + public static final String MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_AVG = "getTopicPartitionMetricListAvg"; + + public static final String MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MIN = "getTopicPartitionMetricListMin"; + + public static final String MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MAX = "getTopicPartitionMetricListMax"; + + @Autowired + private ConnectJMXClient connectJMXClient; + + @Autowired + private MirrorMakerMetricESDAO mirrorMakerMetricESDAO; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private HealthStateService healthStateService; + + @Override + protected List listMetricPOFields() { + return BeanUtil.listBeanFields(MirrorMakerMetricPO.class); + } + + @Override + protected void initRegisterVCHandler() { + registerVCHandler(MIRROR_MAKER_METHOD_DO_NOTHING, this::doNothing); + registerVCHandler(MIRROR_MAKER_METHOD_GET_HEALTH_SCORE, this::getMetricHealthScore); + registerVCHandler(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_SUM, this::getTopicPartitionMetricListSum); + registerVCHandler(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_AVG, this::getTopicPartitionMetricListAvg); + registerVCHandler(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MAX, this::getTopicPartitionMetricListMax); + registerVCHandler(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MIN, this::getTopicPartitionMetricListMin); + } + + @Override + protected VersionItemTypeEnum getVersionItemType() { + return METRIC_CONNECT_MIRROR_MAKER; + } + + @Override + public Result collectMirrorMakerMetricsFromKafka(Long connectClusterPhyId, String mirrorMakerName, List mirrorMakerTopicList, String metricName) { + try { + MirrorMakerMetricParam metricParam = new MirrorMakerMetricParam(connectClusterPhyId, mirrorMakerName, mirrorMakerTopicList, metricName); + return (Result) doVCHandler(connectClusterPhyId, metricName, metricParam); + } catch (Exception e) { + return Result.buildFailure(VC_HANDLE_NOT_EXIST); + } + } + + @Override + public Result> listMirrorMakerClusterMetricsFromES(Long clusterPhyId, MetricsMirrorMakersDTO dto) { + Long startTime = dto.getStartTime(); + Long endTime = dto.getEndTime(); + Integer topN = dto.getTopNu(); + String aggType = dto.getAggType(); + List metricNameList = dto.getMetricsNames(); + + List> connectorList = new ArrayList<>(); + if(!CollectionUtils.isEmpty(dto.getConnectorNameList())){ + connectorList = dto.getConnectorNameList().stream() + .map(c -> new Tuple<>(c.getConnectClusterId(), c.getConnectorName())) + .collect(Collectors.toList()); + } + + Table, List> retTable; + if(ValidateUtils.isEmptyList(connectorList)) { + // 按照TopN的方式去获取 + List> defaultConnectorList = this.listTopNMirrorMakerList(clusterPhyId, topN); + + retTable = mirrorMakerMetricESDAO.listMetricsByTopN(clusterPhyId, defaultConnectorList, metricNameList, aggType, topN, startTime, endTime); + } else { + // 制定集群ID去获取 + retTable = mirrorMakerMetricESDAO.listMetricsByConnectors(clusterPhyId, metricNameList, aggType, connectorList, startTime, endTime); + } + + return Result.buildSuc(this.metricMap2VO(clusterPhyId, retTable.rowMap())); + } + + @Override + public Result> getLatestMetricsFromES(Long clusterPhyId, List> mirrorMakerList, List metricNameList) { + List connectorLatestMetricList = mirrorMakerMetricESDAO.getConnectorLatestMetric(clusterPhyId, mirrorMakerList, metricNameList); + return Result.buildSuc(ConvertUtil.list2List(connectorLatestMetricList, MirrorMakerMetrics.class)); + } + + @Override + public Result getLatestMetricsFromES(Long connectClusterId, String connectorName, List metricsNames) { + ConnectorMetricPO connectorLatestMetric = mirrorMakerMetricESDAO.getConnectorLatestMetric(null, connectClusterId, connectorName, metricsNames); + MirrorMakerMetrics mirrorMakerMetrics = ConvertUtil.obj2Obj(connectorLatestMetric, MirrorMakerMetrics.class); + return Result.buildSuc(mirrorMakerMetrics); + } + + private List> listTopNMirrorMakerList(Long clusterPhyId, Integer topN) { + List poList = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); + + if (CollectionUtils.isEmpty(poList)) { + return new ArrayList<>(); + } + + return poList.subList(0, Math.min(topN, poList.size())) + .stream() + .map( c -> new Tuple<>(c.getId(), c.getConnectorName()) ) + .collect(Collectors.toList()); + } + + protected List metricMap2VO(Long connectClusterId, + Map, List>> map){ + List multiLinesVOS = new ArrayList<>(); + if (map == null || map.isEmpty()) { + // 如果为空,则直接返回 + return multiLinesVOS; + } + + for(String metric : map.keySet()){ + try { + MetricMultiLinesVO multiLinesVO = new MetricMultiLinesVO(); + multiLinesVO.setMetricName(metric); + + List metricLines = new ArrayList<>(); + + Map, List> metricPointMap = map.get(metric); + if(null == metricPointMap || metricPointMap.isEmpty()){continue;} + + for(Map.Entry, List> entry : metricPointMap.entrySet()){ + MetricLineVO metricLineVO = new MetricLineVO(); + metricLineVO.setName(entry.getKey().getV1() + "#" + entry.getKey().getV2()); + metricLineVO.setMetricName(metric); + metricLineVO.setMetricPoints(entry.getValue()); + + metricLines.add(metricLineVO); + } + + multiLinesVO.setMetricLines(metricLines); + multiLinesVOS.add(multiLinesVO); + }catch (Exception e){ + LOGGER.error("method=metricMap2VO||connectClusterId={}||msg=exception!", connectClusterId, e); + } + } + + return multiLinesVOS; + } + + private Result doNothing(VersionItemParam metricParam) { + MirrorMakerMetricParam param = (MirrorMakerMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + return Result.buildSuc(new MirrorMakerMetrics(connectClusterId,mirrorMakerName)); + } + + private Result getMetricHealthScore(VersionItemParam metricParam) { + MirrorMakerMetricParam param = (MirrorMakerMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + + MirrorMakerMetrics metrics = healthStateService.calMirrorMakerHealthMetrics(connectClusterId, mirrorMakerName); + return Result.buildSuc(metrics); + } + + private Result getTopicPartitionMetricListSum(VersionItemParam metricParam) { + MirrorMakerMetricParam param = (MirrorMakerMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + List mirrorMakerTopicList = param.getMirrorMakerTopicList(); + String metric = param.getMetric(); + + Result> ret = this.getTopicPartitionMetricList(connectClusterId, mirrorMakerName, mirrorMakerTopicList, metric); + if (!ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + Float sum = ret.getData().stream().map(elem -> elem.getMetric(metric)).reduce(Float::sum).get(); + return Result.buildSuc(MirrorMakerMetrics.initWithMetric(connectClusterId, mirrorMakerName, metric, sum)); + } + + private Result getTopicPartitionMetricListAvg(VersionItemParam metricParam) { + MirrorMakerMetricParam param = (MirrorMakerMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + List mirrorMakerTopicList = param.getMirrorMakerTopicList(); + String metric = param.getMetric(); + + Result> ret = this.getTopicPartitionMetricList(connectClusterId, mirrorMakerName, mirrorMakerTopicList, metric); + + if (!ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + Float sum = ret.getData().stream().map(elem -> elem.getMetric(metric)).reduce(Float::sum).get(); + return Result.buildSuc(MirrorMakerMetrics.initWithMetric(connectClusterId, mirrorMakerName, metric, sum / ret.getData().size())); + } + + private Result getTopicPartitionMetricListMax(VersionItemParam metricParam) { + MirrorMakerMetricParam param = (MirrorMakerMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + List mirrorMakerTopicList = param.getMirrorMakerTopicList(); + String metric = param.getMetric(); + + Result> ret = this.getTopicPartitionMetricList(connectClusterId, mirrorMakerName, mirrorMakerTopicList, metric); + + if (!ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + Float max = ret.getData().stream().max((a, b) -> a.getMetric(metric).compareTo(b.getMetric(metric))).get().getMetric(metric); + return Result.buildSuc(MirrorMakerMetrics.initWithMetric(connectClusterId, mirrorMakerName, metric, max)); + } + + private Result getTopicPartitionMetricListMin(VersionItemParam metricParam) { + MirrorMakerMetricParam param = (MirrorMakerMetricParam) metricParam; + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + List mirrorMakerTopicList = param.getMirrorMakerTopicList(); + String metric = param.getMetric(); + + Result> ret = this.getTopicPartitionMetricList(connectClusterId, mirrorMakerName, mirrorMakerTopicList, metric); + + if (!ret.hasData() || ret.getData().isEmpty()) { + return Result.buildFailure(NOT_EXIST); + } + + Float min = ret.getData().stream().max((a, b) -> b.getMetric(metric).compareTo(a.getMetric(metric))).get().getMetric(metric); + return Result.buildSuc(MirrorMakerMetrics.initWithMetric(connectClusterId, mirrorMakerName, metric, min)); + } + + + private Result> getTopicPartitionMetricList(Long connectClusterId, String mirrorMakerName, List mirrorMakerTopicList, String metric) { + List topicPartitionMetricsList = new ArrayList<>(); + for (MirrorMakerTopic mirrorMakerTopic : mirrorMakerTopicList) { + for (Map.Entry entry : mirrorMakerTopic.getPartitionMap().entrySet()) { + Result ret = this.getMirrorMakerTopicPartitionMetric(connectClusterId, mirrorMakerName, mirrorMakerTopic.getClusterAlias(), mirrorMakerTopic.getTopicName(), entry.getKey(), entry.getValue(), metric); + if (!ret.hasData() || ret.getData().getMetric(metric) == null) { + continue; + } + topicPartitionMetricsList.add(ret.getData()); + } + } + return Result.buildSuc(topicPartitionMetricsList); + } + + private Result getMirrorMakerTopicPartitionMetric(Long connectClusterId, String mirrorMakerName, String clusterAlias, String topicName, Integer partitionId, String workerId, String metric) { + VersionConnectJmxInfo jmxInfo = getJMXInfo(connectClusterId, metric); + if (null == jmxInfo) { + return Result.buildFailure(VC_ITEM_JMX_NOT_EXIST); + } + + String jmxObjectName = String.format(jmxInfo.getJmxObjectName(), clusterAlias, topicName, partitionId); + + JmxConnectorWrap jmxConnectorWrap = connectJMXClient.getClientWithCheck(connectClusterId, workerId); + if (ValidateUtils.isNull(jmxConnectorWrap)) { + return Result.buildFailure(VC_JMX_INIT_ERROR); + } + try { + //2、获取jmx指标 + String value = jmxConnectorWrap.getAttribute(new ObjectName(jmxObjectName), jmxInfo.getJmxAttribute()).toString(); + MirrorMakerTopicPartitionMetrics metrics = MirrorMakerTopicPartitionMetrics.initWithMetric(connectClusterId, mirrorMakerName, clusterAlias, topicName, partitionId, workerId, metric, Float.valueOf(value)); + return Result.buildSuc(metrics); + } catch (InstanceNotFoundException e) { + // 忽略该错误,该错误出现的原因是该指标在JMX中不存在 + return Result.buildSuc(new MirrorMakerTopicPartitionMetrics(connectClusterId, mirrorMakerName, clusterAlias, topicName, partitionId, workerId)); + } catch (Exception e) { + LOGGER.error("method=getMirrorMakerTopicPartitionMetric||connectClusterId={}||mirrorMakerName={}||clusterAlias={}||topicName={}||partitionId={}||workerId={}||metrics={}||jmx={}||msg={}", + connectClusterId, mirrorMakerName, clusterAlias, topicName, partitionId, workerId, metric, jmxObjectName, e.getClass().getName()); + return Result.buildFailure(VC_JMX_CONNECT_ERROR); + } + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java index b5256e31..b8095e86 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/connect/MirrorMakerMetricVersionItems.java @@ -1,26 +1,124 @@ package com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; +import static com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem.*; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_MIRROR_MAKER; +import static com.xiaojukeji.know.streaming.km.common.jmx.JmxAttribute.*; +import static com.xiaojukeji.know.streaming.km.common.jmx.JmxName.JMX_MIRROR_MAKER_SOURCE; +import static com.xiaojukeji.know.streaming.km.core.service.connect.mm2.impl.MirrorMakerMetricServiceImpl.*; @Component public class MirrorMakerMetricVersionItems extends BaseMetricVersionMetric { + public static final String MIRROR_MAKER_METRIC_COLLECT_COST_TIME = Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME; + + public static final String MIRROR_MAKER_METRIC_HEALTH_STATE = "HealthState"; + + public static final String MIRROR_MAKER_METRIC_HEALTH_CHECK_PASSED = "HealthCheckPassed"; + + public static final String MIRROR_MAKER_METRIC_HEALTH_CHECK_TOTAL = "HealthCheckTotal"; + + public static final String MIRROR_MAKER_METRIC_BYTE_COUNT = "ByteCount"; + + public static final String MIRROR_MAKER_METRIC_BYTE_RATE = "ByteRate"; + + public static final String MIRROR_MAKER_METRIC_RECORD_AGE_MS = "RecordAgeMs"; + + public static final String MIRROR_MAKER_METRIC_RECORD_AGE_MS_AVG = "RecordAgeMsAvg"; + + public static final String MIRROR_MAKER_METRIC_RECORD_AGE_MS_MAX = "RecordAgeMsMax"; + + public static final String MIRROR_MAKER_METRIC_RECORD_AGE_MS_MIN = "RecordAgeMsMin"; + + public static final String MIRROR_MAKER_METRIC_RECORD_COUNT = "RecordCount"; + + public static final String MIRROR_MAKER_METRIC_RECORD_RATE = "RecordRate"; + + public static final String MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS = "ReplicationLatencyMs"; + + public static final String MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_AVG = "ReplicationLatencyMsAvg"; + + public static final String MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MAX = "ReplicationLatencyMsMax"; + + public static final String MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MIN = "ReplicationLatencyMsMin"; + @Override public int versionItemType() { return METRIC_CONNECT_MIRROR_MAKER.getCode(); } @Override - public List init(){ + public List init() { List items = new ArrayList<>(); + // HealthScore 指标 + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_HEALTH_STATE).unit("0:好 1:中 2:差 3:宕机").desc("健康状态(0:好 1:中 2:差 3:宕机)").category(CATEGORY_HEALTH) + .extendMethod(MIRROR_MAKER_METHOD_GET_HEALTH_SCORE)); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_HEALTH_CHECK_PASSED).unit("个").desc("健康项检查通过数").category(CATEGORY_HEALTH) + .extendMethod(MIRROR_MAKER_METHOD_GET_HEALTH_SCORE)); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_HEALTH_CHECK_TOTAL).unit("个").desc("健康项检查总数").category(CATEGORY_HEALTH) + .extendMethod(MIRROR_MAKER_METHOD_GET_HEALTH_SCORE)); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_COLLECT_COST_TIME).unit("秒").desc("采集mirrorMaker指标的耗时").category(CATEGORY_PERFORMANCE) + .extendMethod(MIRROR_MAKER_METHOD_DO_NOTHING)); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_BYTE_COUNT).unit("byte").desc("消息复制流量大小").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_SUM) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(BYTE_COUNT))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_BYTE_RATE).unit(BYTE_PER_SEC).desc("复制流量速率").category(CATEGORY_FLOW) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_SUM) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(BYTE_RATE))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_RECORD_AGE_MS).unit("ms").desc("消息获取时年龄").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_AVG) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(RECORD_AGE_MS))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_RECORD_AGE_MS_AVG).unit("ms").desc("消息获取时平均年龄").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_AVG) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(RECORD_AGE_MS_AVG))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_RECORD_AGE_MS_MAX).unit("ms").desc("消息获取时最大年龄").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MAX) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(RECORD_AGE_MS_MAX))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_RECORD_AGE_MS_MIN).unit("ms").desc("消息获取时最小年龄").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MIN) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(RECORD_AGE_MS_MIN))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_RECORD_COUNT).unit("条").desc("消息复制条数").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_SUM) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(RECORD_COUNT))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_RECORD_RATE).unit("条/s").desc("消息复制速率").category(CATEGORY_FLOW) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_SUM) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(RECORD_RATE))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS).unit("ms").desc("消息复制延迟时间").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_AVG) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(REPLICATION_LATENCY_MS))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_AVG).unit("ms").desc("消息复制平均延迟时间").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_AVG) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(REPLICATION_LATENCY_MS_AVG))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MAX).unit("ms").desc("消息复制最大延迟时间").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MAX) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(REPLICATION_LATENCY_MS_MAX))); + items.add(buildAllVersionsItem() + .name(MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MIN).unit("ms").desc("消息复制最小延迟时间").category(CATEGORY_PERFORMANCE) + .extend(buildConnectJMXMethodExtend(MIRROR_MAKER_METHOD_GET_TOPIC_PARTITION_METRIC_LIST_MIN) + .jmxObjectName(JMX_MIRROR_MAKER_SOURCE).jmxAttribute(REPLICATION_LATENCY_MS_MIN))); return items; } } diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/metrics/MirrorMakerCollectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/metrics/MirrorMakerCollectorTask.java new file mode 100644 index 00000000..266014dc --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/metrics/MirrorMakerCollectorTask.java @@ -0,0 +1,31 @@ +package com.xiaojukeji.know.streaming.km.task.connect.mm2.metrics; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.common.TaskResult; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.xiaojukeji.know.streaming.km.collector.metric.connect.mm2.MirrorMakerMetricCollector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.task.connect.metrics.AbstractAsyncMetricsDispatchTask; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author wyb + * @date 2022/12/21 + */ +@Task(name = "MirrorMakerCollectorTask", + description = "MirrorMaker指标采集任务", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class MirrorMakerCollectorTask extends AbstractAsyncMetricsDispatchTask { + + @Autowired + private MirrorMakerMetricCollector mirrorMakerMetricCollector; + + @Override + public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) throws Exception { + mirrorMakerMetricCollector.collectConnectMetrics(connectCluster); + return TaskResult.SUCCESS; + } +} From caccf9cef506cc604058224e9ffd687e58a1fcdb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 14:53:38 +0800 Subject: [PATCH 105/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-?= =?UTF-8?q?=E9=87=87=E9=9B=86MM2=E6=8C=87=E6=A0=87=E4=BB=BB=E5=8A=A1(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mm2/MirrorMakerMetricCollector.java | 117 ++++++++++++++++++ .../entity/connect/connector/KSConnector.java | 10 ++ .../common/bean/po/connect/ConnectorPO.java | 10 ++ .../connect/mm2/MirrorMakerService.java | 18 +++ .../mm2/impl/MirrorMakerServiceImpl.java | 95 ++++++++++++++ 5 files changed, 250 insertions(+) create mode 100644 km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/mm2/MirrorMakerMetricCollector.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerServiceImpl.java diff --git a/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/mm2/MirrorMakerMetricCollector.java b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/mm2/MirrorMakerMetricCollector.java new file mode 100644 index 00000000..36436fba --- /dev/null +++ b/km-collector/src/main/java/com/xiaojukeji/know/streaming/km/collector/metric/connect/mm2/MirrorMakerMetricCollector.java @@ -0,0 +1,117 @@ +package com.xiaojukeji.know.streaming.km.collector.metric.connect.mm2; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.collector.metric.connect.AbstractConnectMetricCollector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionControlItem; +import com.xiaojukeji.know.streaming.km.common.bean.event.metric.mm2.MirrorMakerMetricEvent; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.FutureWaitUtil; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerService; +import com.xiaojukeji.know.streaming.km.core.service.version.VersionControlService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant.MIRROR_MAKER_SOURCE_CONNECTOR_TYPE; +import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.METRIC_CONNECT_MIRROR_MAKER; + +/** + * @author wyb + * @date 2022/12/15 + */ +@Component +public class MirrorMakerMetricCollector extends AbstractConnectMetricCollector { + protected static final ILog LOGGER = LogFactory.getLog(MirrorMakerMetricCollector.class); + + @Autowired + private VersionControlService versionControlService; + + @Autowired + private MirrorMakerService mirrorMakerService; + + @Autowired + private ConnectorService connectorService; + + @Autowired + private MirrorMakerMetricService mirrorMakerMetricService; + + @Override + public VersionItemTypeEnum collectorType() { + return METRIC_CONNECT_MIRROR_MAKER; + } + + @Override + public List collectConnectMetrics(ConnectCluster connectCluster) { + Long clusterPhyId = connectCluster.getKafkaClusterPhyId(); + Long connectClusterId = connectCluster.getId(); + + List mirrorMakerList = connectorService.listByConnectClusterIdFromDB(connectClusterId).stream().filter(elem -> elem.getConnectorClassName().equals(MIRROR_MAKER_SOURCE_CONNECTOR_TYPE)).collect(Collectors.toList()); + Map mirrorMakerTopicMap = mirrorMakerService.getMirrorMakerTopicMap(connectClusterId).getData(); + + List items = versionControlService.listVersionControlItem(this.getClusterVersion(connectCluster), collectorType().getCode()); + FutureWaitUtil future = this.getFutureUtilByClusterPhyId(clusterPhyId); + + List metricsList = new ArrayList<>(); + + for (ConnectorPO mirrorMaker : mirrorMakerList) { + MirrorMakerMetrics metrics = new MirrorMakerMetrics(clusterPhyId, connectClusterId, mirrorMaker.getConnectorName()); + metricsList.add(metrics); + + List mirrorMakerTopicList = mirrorMakerService.getMirrorMakerTopicList(mirrorMaker, mirrorMakerTopicMap); + future.runnableTask(String.format("class=MirrorMakerMetricCollector||connectClusterId=%d||mirrorMakerName=%s", connectClusterId, mirrorMaker.getConnectorName()), + 30000, + () -> collectMetrics(connectClusterId, mirrorMaker.getConnectorName(), metrics, items, mirrorMakerTopicList)); + } + future.waitResult(30000); + + this.publishMetric(new MirrorMakerMetricEvent(this,metricsList)); + + return metricsList; + } + + /**************************************************** private method ****************************************************/ + private void collectMetrics(Long connectClusterId, String mirrorMakerName, MirrorMakerMetrics metrics, List items, List mirrorMakerTopicList) { + long startTime = System.currentTimeMillis(); + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, Constant.COLLECT_METRICS_ERROR_COST_TIME); + + for (VersionControlItem v : items) { + try { + //已测量指标过滤 + if (metrics.getMetrics().get(v.getName()) != null) { + continue; + } + + Result ret = mirrorMakerMetricService.collectMirrorMakerMetricsFromKafka(connectClusterId, mirrorMakerName, mirrorMakerTopicList, v.getName()); + if (ret == null || !ret.hasData()) { + continue; + } + metrics.putMetric(ret.getData().getMetrics()); + + } catch (Exception e) { + LOGGER.error( + "method=collectMetrics||connectClusterId={}||mirrorMakerName={}||metric={}||errMsg=exception!", + connectClusterId, mirrorMakerName, v.getName(), e + ); + + } + } + metrics.putMetric(Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME, (System.currentTimeMillis() - startTime) / 1000.0f); + + } +} + + + diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java index b8fab0b6..6b3f7b57 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/connector/KSConnector.java @@ -45,4 +45,14 @@ public class KSConnector implements Serializable { * 状态 */ private String state; + + /** + * 心跳检测connector名称 + */ + private String heartbeatConnectorName; + + /** + * 进度确认connector名称 + */ + private String checkpointConnectorName; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java index 1853deef..1d82a9e1 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/connect/ConnectorPO.java @@ -47,4 +47,14 @@ public class ConnectorPO extends BasePO { * 状态 */ private String state; + + /** + * 心跳检测connector + */ + private String heartbeatConnectorName; + + /** + * 进度确认connector + */ + private String checkpointConnectorName; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerService.java new file mode 100644 index 00000000..7c2ff225 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/MirrorMakerService.java @@ -0,0 +1,18 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; + +import java.util.List; +import java.util.Map; + +/** + * @author wyb + * @date 2022/12/14 + */ +public interface MirrorMakerService { + Result> getMirrorMakerTopicMap(Long connectClusterId); + + List getMirrorMakerTopicList(ConnectorPO mirrorMaker, Map mirrorMakerTopicMap); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerServiceImpl.java new file mode 100644 index 00000000..b011cf8f --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/mm2/impl/MirrorMakerServiceImpl.java @@ -0,0 +1,95 @@ +package com.xiaojukeji.know.streaming.km.core.service.connect.mm2.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.jmx.JmxConnectorWrap; +import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.persistence.connect.ConnectJMXClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.management.ObjectName; +import java.util.*; + +import static com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant.MIRROR_MAKER_TOPIC_PARTITION_PATTERN; + +/** + * @author wyb + * @date 2022/12/14 + */ +@Service +public class MirrorMakerServiceImpl implements MirrorMakerService { + private static final ILog LOGGER = LogFactory.getLog(MirrorMakerServiceImpl.class); + + @Autowired + private WorkerService workerService; + + @Autowired + private ConnectJMXClient connectJMXClient; + + @Override + public Result> getMirrorMakerTopicMap(Long connectClusterId) { + + List connectWorkerList = workerService.listFromDB(connectClusterId); + + //Map + Map topicMap = new HashMap<>(); + + for (ConnectWorker connectWorker : connectWorkerList) { + JmxConnectorWrap jmxConnectorWrap = connectJMXClient.getClientWithCheck(connectClusterId, connectWorker.getWorkerId()); + Set objectNameSet = new HashSet<>(); + try { + objectNameSet = jmxConnectorWrap.queryNames(new ObjectName(MIRROR_MAKER_TOPIC_PARTITION_PATTERN), null); + } catch (Exception e) { + LOGGER.error("method=getMirrorMakerTopic||connectClusterId={}||workerId={}||queryNames failed!", + connectClusterId, connectWorker.getWorkerId()); + continue; + } + + //解析数据 + for (ObjectName objectName : objectNameSet) { + try { + String[] paramList = objectName.getCanonicalName().split(","); + + String clusterAlias = paramList[1].split("=")[1]; + String topicName = paramList[2].split("=")[1]; + Integer partition = Integer.valueOf(paramList[0].split("=")[1]); + + MirrorMakerTopic mirrorMakerTopic = topicMap.get(topicName); + + if (mirrorMakerTopic == null) { + mirrorMakerTopic = new MirrorMakerTopic(clusterAlias, topicName, new HashMap<>()); + topicMap.put(topicName, mirrorMakerTopic); + } + + mirrorMakerTopic.getPartitionMap().put(partition, connectWorker.getWorkerId()); + } catch (Exception e) { + LOGGER.error("method=getMirrorMakerTopic||connectClusterId={}||workerId={}||canonicalName={}||canonicalName explain error!", + connectClusterId, connectWorker.getWorkerId(), objectName.getCanonicalName()); + } + + } + } + return Result.buildSuc(topicMap); + } + + @Override + public List getMirrorMakerTopicList(ConnectorPO mirrorMaker, Map mirrorMakerTopicMap) { + List mirrorMakerTopicList = new ArrayList<>(); + List topicList = CommonUtils.string2StrList(mirrorMaker.getTopics()); + + for (String topicName : topicList) { + MirrorMakerTopic mirrorMakerTopic = mirrorMakerTopicMap.get(topicName); + if (mirrorMakerTopic != null) { + mirrorMakerTopicList.add(mirrorMakerTopic); + } + } + return mirrorMakerTopicList; + } +} From efdf624c67af1909e16faaace876a503aea47f06 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 15:10:06 +0800 Subject: [PATCH 106/150] =?UTF-8?q?[Feature]HA-=E6=BB=B4=E6=BB=B4Kafka?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E4=BF=A1=E6=81=AF=E5=85=BC=E5=AE=B9(#899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/common/enums/version/VersionEnum.java | 8 +- .../km/common/utils/VersionUtil.java | 120 ++++++++++++------ 2 files changed, 90 insertions(+), 38 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionEnum.java index 133cb828..5c594848 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/version/VersionEnum.java @@ -53,7 +53,11 @@ public enum VersionEnum { V_2_3_1("2.3.1", normailze("2.3.1")), V_2_4_0("2.4.0", normailze("2.4.0")), V_2_4_1("2.4.1", normailze("2.4.1")), + V_2_5_0("2.5.0", normailze("2.5.0")), + V_2_5_0_D_300("2.5.0-d-300", normailze("2.5.0-d-300")), + V_2_5_0_D_MAX("2.5.0-d-999", normailze("2.5.0-d-999")), + V_2_5_1("2.5.1", normailze("2.5.1")), V_2_6_0("2.6.0", normailze("2.6.0")), V_2_6_1("2.6.1", normailze("2.6.1")), @@ -77,9 +81,9 @@ public enum VersionEnum { ; - private String version; + private final String version; - private Long versionL; + private final Long versionL; VersionEnum(String version, Long versionL) { this.version = version; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/VersionUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/VersionUtil.java index c1bdd9b4..5eeb0a9b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/VersionUtil.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/VersionUtil.java @@ -3,29 +3,25 @@ package com.xiaojukeji.know.streaming.km.common.utils; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import org.apache.commons.lang.StringUtils; + public class VersionUtil { + /** + * apache的kafka相关的版本信息 + */ + private static final long BASE_VAL = 10000L; + private static final long APACHE_STEP_VAL = 100L; + public static final long APACHE_MAX_VAL = 100000000L; private static final int MIN_VERSION_SECTIONS_3 = 3; private static final int MIN_VERSION_SECTIONS_4 = 4; private static final String VERSION_FORMAT_3 = "%d.%d.%d"; private static final String VERSION_FORMAT_4 = "%d.%d.%d.%d"; - public static boolean isValid(String version){ - if(StringUtils.isBlank(version)){return false;} - - String[] vers = version.split("\\."); - if(null == vers){return false;} - - if(vers.length < MIN_VERSION_SECTIONS_3){return false;} - - for(String ver : vers){ - if(!ver.chars().allMatch(Character::isDigit)){ - return false; - } - } - - return true; - } + /** + * XiaoJu的kafka相关的版本信息 + */ + private static final String XIAO_JU_VERSION_FEATURE = "-d-"; + private static final String XIAO_JU_VERSION_FORMAT_4 = "%d.%d.%d-d-%d"; /** @@ -34,20 +30,64 @@ public class VersionUtil { * @param version * @return */ - public static long normailze(String version){ - if (!isValid(version)) { + public static long normailze(String version) { + if(StringUtils.isBlank(version)) { return -1; } - String[] vers = version.split("\\."); - - if(MIN_VERSION_SECTIONS_3 == vers.length){ - return Long.parseLong(vers[0]) * 1000000 + Long.parseLong(vers[1]) * 10000 + Long.parseLong(vers[2]) * 100; - }else if(MIN_VERSION_SECTIONS_4 == vers.length){ - return Long.parseLong(vers[0]) * 1000000 + Long.parseLong(vers[1]) * 10000 + Long.parseLong(vers[2]) * 100 + Long.parseLong(vers[3]); + if (version.contains(XIAO_JU_VERSION_FEATURE)) { + // XiaoJu的kafka + return normalizeXiaoJuVersion(version); } - return -1; + // 检查是否合法 + String[] vers = version.split("\\."); + if(vers.length < MIN_VERSION_SECTIONS_3) { + return -1; + } + for(String ver : vers){ + if(!ver.chars().allMatch(Character::isDigit)){ + return -1; + } + } + + // 转为数字 + long val = -1; + if(MIN_VERSION_SECTIONS_3 == vers.length) { + val = Long.parseLong(vers[0]) * APACHE_STEP_VAL * APACHE_STEP_VAL * APACHE_STEP_VAL + Long.parseLong(vers[1]) * APACHE_STEP_VAL * APACHE_STEP_VAL + Long.parseLong(vers[2]) * APACHE_STEP_VAL; + } else if(MIN_VERSION_SECTIONS_4 == vers.length) { + val = Long.parseLong(vers[0]) * APACHE_STEP_VAL * APACHE_STEP_VAL * APACHE_STEP_VAL + Long.parseLong(vers[1]) * APACHE_STEP_VAL * APACHE_STEP_VAL + Long.parseLong(vers[2]) * APACHE_STEP_VAL + Long.parseLong(vers[3]); + } + + return val == -1? val: val * BASE_VAL; + } + + public static long normalizeXiaoJuVersion(String version) { + if(StringUtils.isBlank(version)) { + return -1; + } + + if (!version.contains(XIAO_JU_VERSION_FEATURE)) { + // 非XiaoJu的kafka + return normailze(version); + } + + String[] vers = version.split(XIAO_JU_VERSION_FEATURE); + if (vers.length < 2) { + return -1; + } + + long apacheVal = normailze(vers[0]); + if (apacheVal == -1) { + return apacheVal; + } + + Long xiaoJuVal = ConvertUtil.string2Long(vers[1]); + if (xiaoJuVal == null) { + return apacheVal; + } + + return apacheVal + xiaoJuVal; } /** @@ -55,15 +95,17 @@ public class VersionUtil { * @param version * @return */ - public static String dNormailze(long version){ - long version4 = version % 100; - long version3 = (version / 100) % 100; - long version2 = (version / 10000) % 100; - long version1 = (version / 1000000) % 100; + public static String dNormailze(long version) { + long version4 = (version / BASE_VAL) % APACHE_STEP_VAL; + long version3 = (version / BASE_VAL / APACHE_STEP_VAL) % APACHE_STEP_VAL; + long version2 = (version / BASE_VAL / APACHE_STEP_VAL / APACHE_STEP_VAL) % APACHE_STEP_VAL; + long version1 = (version / BASE_VAL / APACHE_STEP_VAL / APACHE_STEP_VAL / APACHE_STEP_VAL) % APACHE_STEP_VAL; - if(0 == version4){ + if (version % BASE_VAL != 0) { + return String.format(XIAO_JU_VERSION_FORMAT_4, version1, version2, version3, version % BASE_VAL); + } else if (0 == version4) { return String.format(VERSION_FORMAT_3, version1, version2, version3); - }else { + } else { return String.format(VERSION_FORMAT_4, version1, version2, version3, version4); } } @@ -71,18 +113,24 @@ public class VersionUtil { public static void main(String[] args){ long n1 = VersionUtil.normailze(VersionEnum.V_0_10_0_0.getVersion()); String v1 = VersionUtil.dNormailze(n1); - System.out.println(VersionEnum.V_0_10_0_0.getVersion() + ":" + n1 + ":" + v1); + System.out.println(VersionEnum.V_0_10_0_0.getVersion() + "\t:\t" + n1 + "\t:\t" + v1); long n2 = VersionUtil.normailze(VersionEnum.V_0_10_0_1.getVersion()); String v2 = VersionUtil.dNormailze(n2); - System.out.println(VersionEnum.V_0_10_0_1.getVersion() + ":" + n2 + ":" + v2); + System.out.println(VersionEnum.V_0_10_0_1.getVersion() + "\t:\t" + n2 + "\t:\t" + v2); long n3 = VersionUtil.normailze(VersionEnum.V_0_11_0_3.getVersion()); String v3 = VersionUtil.dNormailze(n3); - System.out.println(VersionEnum.V_0_11_0_3.getVersion() + ":" + n3 + ":" + v3); + System.out.println(VersionEnum.V_0_11_0_3.getVersion() + "\t:\t" + n3 + "\t:\t" + v3); long n4 = VersionUtil.normailze(VersionEnum.V_2_5_0.getVersion()); String v4 = VersionUtil.dNormailze(n4); - System.out.println(VersionEnum.V_2_5_0.getVersion() + ":" + n4 + ":" + v4); + System.out.println(VersionEnum.V_2_5_0.getVersion() + "\t:\t" + n4 + "\t:\t" + v4); + + long n5 = VersionUtil.normailze(VersionEnum.V_2_5_0_D_300.getVersion()); + String v5 = VersionUtil.dNormailze(n5); + System.out.println(VersionEnum.V_2_5_0_D_300.getVersion() + "\t:\t" + n4 + "\t:\t" + v5); + + System.out.println(Long.MAX_VALUE); } } From 861faa5df5abab66e0c7e22f4d1a4529d01ea524 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 15:19:28 +0800 Subject: [PATCH 107/150] =?UTF-8?q?[Feature]HA-=E9=95=9C=E5=83=8FTopic?= =?UTF-8?q?=E7=AE=A1=E7=90=86(#899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、底层Kafka需要是滴滴版本的Kafka; 2、新增镜像Topic的增删改查; 3、新增镜像Topic的指标查看; --- .../impl/ClusterTopicsManagerImpl.java | 10 +- .../dto/ha/mirror/MirrorTopicCreateDTO.java | 38 +++++ .../dto/ha/mirror/MirrorTopicDeleteDTO.java | 29 ++++ .../entity/ha/HaActiveStandbyRelation.java | 23 +++ .../bean/po/ha/HaActiveStandbyRelationPO.java | 33 ++++ .../res/ClusterPhyTopicsOverviewVO.java | 3 + .../bean/vo/ha/mirror/TopicMirrorInfoVO.java | 37 +++++ .../km/common/constant/Constant.java | 1 + .../km/common/converter/TopicVOConverter.java | 3 +- .../km/common/enums/ha/HaResTypeEnum.java | 25 +++ .../km/core/flusher/DatabaseDataFlusher.java | 15 ++ .../ha/HaActiveStandbyRelationService.java | 25 +++ .../HaActiveStandbyRelationServiceImpl.java | 106 ++++++++++++ .../topic/impl/OpTopicServiceImpl.java | 25 +++ .../topic/impl/TopicMetricServiceImpl.java | 40 ++++- .../fe/FrontEndControlVersionItems.java | 9 ++ .../kafka/TopicMetricVersionItems.java | 8 + km-enterprise/km-ha/pom.xml | 31 ++++ .../ha/mirror/service/MirrorTopicService.java | 30 ++++ .../service/impl/MirrorTopicServiceImpl.java | 151 ++++++++++++++++++ .../cache/DataBaseDataLocalCache.java | 19 +++ .../mysql/ha/HaActiveStandbyRelationDAO.java | 9 ++ km-rest/pom.xml | 6 + .../mirror/MirrorTopicController.java | 51 ++++++ pom.xml | 1 + 25 files changed, 725 insertions(+), 3 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicCreateDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicDeleteDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/ha/HaActiveStandbyRelation.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/ha/HaActiveStandbyRelationPO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/ha/mirror/TopicMirrorInfoVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/ha/HaResTypeEnum.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/HaActiveStandbyRelationService.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/impl/HaActiveStandbyRelationServiceImpl.java create mode 100644 km-enterprise/km-ha/pom.xml create mode 100644 km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/MirrorTopicService.java create mode 100644 km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/impl/MirrorTopicServiceImpl.java create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/ha/HaActiveStandbyRelationDAO.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorTopicController.java diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java index 17a6c63c..3a2b11ef 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterTopicsManagerImpl.java @@ -14,10 +14,12 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.res.ClusterPhyTop import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.converter.TopicVOConverter; +import com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil; import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.ha.HaActiveStandbyRelationService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +40,9 @@ public class ClusterTopicsManagerImpl implements ClusterTopicsManager { @Autowired private TopicMetricService topicMetricService; + @Autowired + private HaActiveStandbyRelationService haActiveStandbyRelationService; + @Override public PaginationResult getClusterPhyTopicsOverview(Long clusterPhyId, ClusterTopicsOverviewDTO dto) { // 获取集群所有的Topic信息 @@ -46,8 +51,11 @@ public class ClusterTopicsManagerImpl implements ClusterTopicsManager { // 获取集群所有Topic的指标 Map metricsMap = topicMetricService.getLatestMetricsFromCache(clusterPhyId); + // 获取HA信息 + Set haTopicNameSet = haActiveStandbyRelationService.listByClusterAndType(clusterPhyId, HaResTypeEnum.MIRROR_TOPIC).stream().map(elem -> elem.getResName()).collect(Collectors.toSet()); + // 转换成vo - List voList = TopicVOConverter.convert2ClusterPhyTopicsOverviewVOList(topicList, metricsMap); + List voList = TopicVOConverter.convert2ClusterPhyTopicsOverviewVOList(topicList, metricsMap, haTopicNameSet); // 请求分页信息 PaginationResult voPaginationResult = this.pagingTopicInLocal(voList, dto); diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicCreateDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicCreateDTO.java new file mode 100644 index 00000000..58182670 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicCreateDTO.java @@ -0,0 +1,38 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author zengqiao + * @date 20/4/23 + */ +@Data +@ApiModel(description="Topic镜像信息") +public class MirrorTopicCreateDTO extends BaseDTO { + @Min(value = 0, message = "sourceClusterPhyId不允许为空,且最小值为0") + @ApiModelProperty(value = "源集群ID", example = "3") + private Long sourceClusterPhyId; + + @Min(value = 0, message = "destClusterPhyId不允许为空,且最小值为0") + @ApiModelProperty(value = "目标集群ID", example = "3") + private Long destClusterPhyId; + + @NotBlank(message = "topicName不允许为空串") + @ApiModelProperty(value = "Topic名称", example = "mirrorTopic") + private String topicName; + + @NotNull(message = "syncData不允许为空") + @ApiModelProperty(value = "同步数据", example = "true") + private Boolean syncData; + + @NotNull(message = "syncConfig不允许为空") + @ApiModelProperty(value = "同步配置", example = "false") + private Boolean syncConfig; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicDeleteDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicDeleteDTO.java new file mode 100644 index 00000000..8b7d0095 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/ha/mirror/MirrorTopicDeleteDTO.java @@ -0,0 +1,29 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; + +/** + * @author zengqiao + * @date 20/4/23 + */ +@Data +@ApiModel(description="Topic镜像信息") +public class MirrorTopicDeleteDTO extends BaseDTO { + @Min(value = 0, message = "sourceClusterPhyId不允许为空,且最小值为0") + @ApiModelProperty(value = "源集群ID", example = "3") + private Long sourceClusterPhyId; + + @Min(value = 0, message = "destClusterPhyId不允许为空,且最小值为0") + @ApiModelProperty(value = "目标集群ID", example = "3") + private Long destClusterPhyId; + + @NotBlank(message = "topicName不允许为空串") + @ApiModelProperty(value = "Topic名称", example = "mirrorTopic") + private String topicName; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/ha/HaActiveStandbyRelation.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/ha/HaActiveStandbyRelation.java new file mode 100644 index 00000000..db306641 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/ha/HaActiveStandbyRelation.java @@ -0,0 +1,23 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.ha; + +import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; +import com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum; +import lombok.Data; + +@Data +public class HaActiveStandbyRelation extends BasePO { + private Long activeClusterPhyId; + + private Long standbyClusterPhyId; + + /** + * 资源名称 + */ + private String resName; + + /** + * 资源类型,0:集群,1:镜像Topic,2:主备Topic + * @see HaResTypeEnum + */ + private Integer resType; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/ha/HaActiveStandbyRelationPO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/ha/HaActiveStandbyRelationPO.java new file mode 100644 index 00000000..e019a4f0 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/po/ha/HaActiveStandbyRelationPO.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.common.bean.po.ha; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.xiaojukeji.know.streaming.km.common.bean.po.BasePO; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@TableName(Constant.MYSQL_HA_TABLE_NAME_PREFIX + "active_standby_relation") +public class HaActiveStandbyRelationPO extends BasePO { + private Long activeClusterPhyId; + + private Long standbyClusterPhyId; + + /** + * 资源名称 + */ + private String resName; + + /** + * 资源类型,0:集群,1:镜像Topic,2:主备Topic + */ + private Integer resType; + + public HaActiveStandbyRelationPO(Long activeClusterPhyId, Long standbyClusterPhyId, String resName, Integer resType) { + this.activeClusterPhyId = activeClusterPhyId; + this.standbyClusterPhyId = standbyClusterPhyId; + this.resName = resName; + this.resType = resType; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/res/ClusterPhyTopicsOverviewVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/res/ClusterPhyTopicsOverviewVO.java index a767b9f2..10258bf1 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/res/ClusterPhyTopicsOverviewVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/res/ClusterPhyTopicsOverviewVO.java @@ -32,6 +32,9 @@ public class ClusterPhyTopicsOverviewVO extends BaseTimeVO { @ApiModelProperty(value = "副本数", example = "2") private Integer replicaNum; + @ApiModelProperty(value = "处于镜像复制中", example = "true") + private Boolean inMirror; + @ApiModelProperty(value = "多个指标的当前值, 包括健康分/LogSize等") private BaseMetrics latestMetrics; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/ha/mirror/TopicMirrorInfoVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/ha/mirror/TopicMirrorInfoVO.java new file mode 100644 index 00000000..b1f748c0 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/ha/mirror/TopicMirrorInfoVO.java @@ -0,0 +1,37 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.ha.mirror; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author zengqiao + * @date 20/4/29 + */ +@Data +@ApiModel(description="Topic复制信息") +public class TopicMirrorInfoVO { + @ApiModelProperty(value="源集群ID", example = "1") + private Long sourceClusterId; + + @ApiModelProperty(value="源集群名称", example = "know-streaming-1") + private String sourceClusterName; + + @ApiModelProperty(value="目标集群ID", example = "2") + private Long destClusterId; + + @ApiModelProperty(value="目标集群名称", example = "know-streaming-2") + private String destClusterName; + + @ApiModelProperty(value="Topic名称", example = "know-streaming") + private String topicName; + + @ApiModelProperty(value="写入速率(bytes/s)", example = "100") + private Double bytesIn; + + @ApiModelProperty(value="复制速率(bytes/s)", example = "100") + private Double replicationBytesIn; + + @ApiModelProperty(value="延迟消息数", example = "100") + private Long lag; +} \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java index c8f4075b..5ba73baa 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/Constant.java @@ -46,6 +46,7 @@ public class Constant { public static final String MYSQL_TABLE_NAME_PREFIX = "ks_km_"; public static final String MYSQL_KC_TABLE_NAME_PREFIX = "ks_kc_"; + public static final String MYSQL_HA_TABLE_NAME_PREFIX = "ks_ha_"; public static final String SWAGGER_API_TAG_PREFIX = "KS-KM-"; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java index 0240fde1..14afdd9f 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/TopicVOConverter.java @@ -77,7 +77,7 @@ public class TopicVOConverter { return vo; } - public static List convert2ClusterPhyTopicsOverviewVOList(List topicList, Map metricsMap) { + public static List convert2ClusterPhyTopicsOverviewVOList(List topicList, Map metricsMap, Set haTopicNameSet) { List voList = new ArrayList<>(); for (Topic topic: topicList) { ClusterPhyTopicsOverviewVO vo = new ClusterPhyTopicsOverviewVO(); @@ -92,6 +92,7 @@ public class TopicVOConverter { vo.setLatestMetrics(metricsMap.getOrDefault(topic.getTopicName(), new TopicMetrics(topic.getTopicName(), topic.getClusterPhyId()))); + vo.setInMirror(haTopicNameSet.contains(topic.getTopicName())); voList.add(vo); } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/ha/HaResTypeEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/ha/HaResTypeEnum.java new file mode 100644 index 00000000..1821f7ab --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/ha/HaResTypeEnum.java @@ -0,0 +1,25 @@ +package com.xiaojukeji.know.streaming.km.common.enums.ha; + +import lombok.Getter; + +/** + * @author zengqiao + * @date 20/7/28 + */ +@Getter +public enum HaResTypeEnum { + CLUSTER(0, "Cluster"), + + MIRROR_TOPIC(1, "镜像Topic"), + + ; + + private final int code; + + private final String msg; + + HaResTypeEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java index b91e27c1..d5ae89e3 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/DatabaseDataFlusher.java @@ -3,6 +3,7 @@ package com.xiaojukeji.know.streaming.km.core.flusher; 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.ha.HaActiveStandbyRelation; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition; @@ -13,6 +14,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil; import com.xiaojukeji.know.streaming.km.persistence.cache.DataBaseDataLocalCache; 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.ha.HaActiveStandbyRelationService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; @@ -50,6 +52,9 @@ public class DatabaseDataFlusher { @Autowired private PartitionService partitionService; + @Autowired + private HaActiveStandbyRelationService haActiveStandbyRelationService; + @PostConstruct public void init() { this.flushPartitionsCache(); @@ -59,6 +64,8 @@ public class DatabaseDataFlusher { this.flushTopicLatestMetricsCache(); this.flushHealthCheckResultCache(); + + this.flushHaTopicCache(); } @Scheduled(cron="0 0/1 * * * ?") @@ -159,4 +166,12 @@ public class DatabaseDataFlusher { }); } } + + @Scheduled(cron="0 0/1 * * * ?") + public void flushHaTopicCache() { + List haTopicList = haActiveStandbyRelationService.listAllTopicHa(); + for (HaActiveStandbyRelation topic : haTopicList) { + DataBaseDataLocalCache.putHaTopic(topic.getStandbyClusterPhyId(), topic.getResName()); + } + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/HaActiveStandbyRelationService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/HaActiveStandbyRelationService.java new file mode 100644 index 00000000..934125de --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/HaActiveStandbyRelationService.java @@ -0,0 +1,25 @@ +package com.xiaojukeji.know.streaming.km.core.service.ha; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.ha.HaActiveStandbyRelation; +import com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum; + +import java.util.List; + +public interface HaActiveStandbyRelationService { + /** + * 新增或者变更,支持幂等 + */ + void batchReplaceTopicHA(Long activeClusterPhyId, Long standbyClusterPhyId, List topicNameList); + + /** + * 删除 + */ + void batchDeleteTopicHA(Long activeClusterPhyId, Long standbyClusterPhyId, List topicNameList); + + /** + * 按照集群ID查询 + */ + List listByClusterAndType(Long firstClusterId, HaResTypeEnum haResTypeEnum); + + List listAllTopicHa(); +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/impl/HaActiveStandbyRelationServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/impl/HaActiveStandbyRelationServiceImpl.java new file mode 100644 index 00000000..7c194edd --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/ha/impl/HaActiveStandbyRelationServiceImpl.java @@ -0,0 +1,106 @@ +package com.xiaojukeji.know.streaming.km.core.service.ha.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.xiaojukeji.know.streaming.km.common.bean.entity.ha.HaActiveStandbyRelation; +import com.xiaojukeji.know.streaming.km.common.bean.po.ha.HaActiveStandbyRelationPO; +import com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.ha.HaActiveStandbyRelationService; +import com.xiaojukeji.know.streaming.km.persistence.mysql.ha.HaActiveStandbyRelationDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum.MIRROR_TOPIC; + +@Service +public class HaActiveStandbyRelationServiceImpl implements HaActiveStandbyRelationService { + @Autowired + private HaActiveStandbyRelationDAO haActiveStandbyRelationDAO; + + @Override + public void batchReplaceTopicHA(Long activeClusterPhyId, Long standbyClusterPhyId, List topicNameList) { + Map poMap = this.listPOs(activeClusterPhyId, standbyClusterPhyId, MIRROR_TOPIC) + .stream() + .collect(Collectors.toMap(HaActiveStandbyRelationPO::getResName, Function.identity())); + for (String topicName: topicNameList) { + HaActiveStandbyRelationPO oldPO = poMap.get(topicName); + if (oldPO != null) { + continue; + } + + try { + haActiveStandbyRelationDAO.insert(new HaActiveStandbyRelationPO(activeClusterPhyId, standbyClusterPhyId, topicName, MIRROR_TOPIC.getCode())); + } catch (DuplicateKeyException dke) { + // ignore + } + } + } + + @Override + public void batchDeleteTopicHA(Long activeClusterPhyId, Long standbyClusterPhyId, List topicNameList) { + Map poMap = this.listPOs(activeClusterPhyId, standbyClusterPhyId, MIRROR_TOPIC) + .stream() + .collect(Collectors.toMap(HaActiveStandbyRelationPO::getResName, Function.identity())); + for (String topicName: topicNameList) { + HaActiveStandbyRelationPO oldPO = poMap.get(topicName); + if (oldPO == null) { + continue; + } + + haActiveStandbyRelationDAO.deleteById(oldPO.getId()); + } + } + + @Override + public List listByClusterAndType(Long firstClusterId, HaResTypeEnum haResTypeEnum) { + // 查询HA列表 + List poList = this.listPOs(firstClusterId, haResTypeEnum); + if (ValidateUtils.isNull(poList)) { + return new ArrayList<>(); + } + + return ConvertUtil.list2List(poList, HaActiveStandbyRelation.class); + } + + @Override + public List listAllTopicHa() { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(HaActiveStandbyRelationPO::getResType, MIRROR_TOPIC.getCode()); + List poList = haActiveStandbyRelationDAO.selectList(lambdaQueryWrapper); + if (ValidateUtils.isNull(poList)) { + return new ArrayList<>(); + } + + return ConvertUtil.list2List(poList, HaActiveStandbyRelation.class); + } + + private List listPOs(Long firstClusterId, HaResTypeEnum haResTypeEnum) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(HaActiveStandbyRelationPO::getResType, haResTypeEnum.getCode()); + lambdaQueryWrapper.and(lambda -> + lambda.eq(HaActiveStandbyRelationPO::getActiveClusterPhyId, firstClusterId).or().eq(HaActiveStandbyRelationPO::getStandbyClusterPhyId, firstClusterId) + ); + + // 查询HA列表 + return haActiveStandbyRelationDAO.selectList(lambdaQueryWrapper); + } + + private List listPOs(Long activeClusterPhyId, Long standbyClusterPhyId, HaResTypeEnum haResTypeEnum) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(HaActiveStandbyRelationPO::getResType, haResTypeEnum.getCode()); + lambdaQueryWrapper.eq(HaActiveStandbyRelationPO::getActiveClusterPhyId, activeClusterPhyId); + lambdaQueryWrapper.eq(HaActiveStandbyRelationPO::getStandbyClusterPhyId, standbyClusterPhyId); + + + // 查询HA列表 + return haActiveStandbyRelationDAO.selectList(lambdaQueryWrapper); + } +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java index 5a7b0f76..466f7a2f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/OpTopicServiceImpl.java @@ -3,6 +3,7 @@ package com.xiaojukeji.know.streaming.km.core.service.topic.impl; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.didiglobal.logi.security.common.dto.oplog.OplogDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.ha.HaActiveStandbyRelation; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.VersionItemParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicCreateParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam; @@ -12,11 +13,13 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus; import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; import com.xiaojukeji.know.streaming.km.common.converter.TopicConverter; +import com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum; import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.ModuleEnum; import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.OperationEnum; import com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum; import com.xiaojukeji.know.streaming.km.common.exception.NotExistException; import com.xiaojukeji.know.streaming.km.common.exception.VCHandlerNotExistException; +import com.xiaojukeji.know.streaming.km.core.service.ha.HaActiveStandbyRelationService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; import com.xiaojukeji.know.streaming.km.core.service.topic.OpTopicService; import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService; @@ -70,6 +73,9 @@ public class OpTopicServiceImpl extends BaseKafkaVersionControlService implement @Autowired private KafkaZKDAO kafkaZKDAO; + @Autowired + private HaActiveStandbyRelationService haActiveStandbyRelationService; + @Override protected VersionItemTypeEnum getVersionItemType() { return SERVICE_OP_TOPIC; @@ -138,6 +144,25 @@ public class OpTopicServiceImpl extends BaseKafkaVersionControlService implement // 删除DB中的Topic数据 topicService.deleteTopicInDB(param.getClusterPhyId(), param.getTopicName()); + //解除高可用Topic关联 + List haActiveStandbyRelations = haActiveStandbyRelationService.listByClusterAndType(param.getClusterPhyId(), HaResTypeEnum.MIRROR_TOPIC); + for (HaActiveStandbyRelation activeStandbyRelation : haActiveStandbyRelations) { + if (activeStandbyRelation.getResName().equals(param.getTopicName())) { + try { + KafkaZkClient kafkaZkClient = kafkaAdminZKClient.getClient(activeStandbyRelation.getStandbyClusterPhyId()); + Properties haTopics = kafkaZkClient.getEntityConfigs("ha-topics", activeStandbyRelation.getResName()); + if (haTopics.size() != 0) { + kafkaZkClient.setOrCreateEntityConfigs("ha-topics", activeStandbyRelation.getResName(), new Properties()); + kafkaZkClient.createConfigChangeNotification("ha-topics/" + activeStandbyRelation.getResName()); + } + haActiveStandbyRelationService.batchDeleteTopicHA(activeStandbyRelation.getActiveClusterPhyId(), activeStandbyRelation.getStandbyClusterPhyId(), Collections.singletonList(activeStandbyRelation.getResName())); + } catch (Exception e) { + log.error("method=deleteTopic||topicName:{}||errMsg=exception", activeStandbyRelation.getResName(), e); + return Result.buildFailure(e.getMessage()); + } + } + } + // 记录操作 OplogDTO oplogDTO = new OplogDTO(operator, OperationEnum.DELETE.getDesc(), diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java index bc275821..d42fb5db 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/topic/impl/TopicMetricServiceImpl.java @@ -61,7 +61,7 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe public static final String TOPIC_METHOD_GET_METRIC_FROM_KAFKA_BY_TOTAL_PARTITION_OF_BROKER_JMX = "getMetricFromKafkaByTotalPartitionOfBrokerJmx"; public static final String TOPIC_METHOD_GET_MESSAGES = "getMessages"; public static final String TOPIC_METHOD_GET_REPLICAS_COUNT = "getReplicasCount"; - + public static final String TOPIC_METHOD_GET_TOPIC_MIRROR_FETCH_LAG = "getTopicMirrorFetchLag"; @Autowired private HealthStateService healthStateService; @@ -98,6 +98,7 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe registerVCHandler( TOPIC_METHOD_GET_METRIC_FROM_KAFKA_BY_TOTAL_PARTITION_OF_BROKER_JMX, this::getMetricFromKafkaByTotalPartitionOfBrokerJmx ); registerVCHandler( TOPIC_METHOD_GET_REPLICAS_COUNT, this::getReplicasCount); registerVCHandler( TOPIC_METHOD_GET_MESSAGES, this::getMessages); + registerVCHandler( TOPIC_METHOD_GET_TOPIC_MIRROR_FETCH_LAG, this::getTopicMirrorFetchLag); } @Override @@ -502,4 +503,41 @@ public class TopicMetricServiceImpl extends BaseMetricService implements TopicMe return aliveBrokerList.stream().filter(elem -> topic.getBrokerIdSet().contains(elem.getBrokerId())).collect(Collectors.toList()); } + + private Result> getTopicMirrorFetchLag(VersionItemParam param) { + TopicMetricParam topicMetricParam = (TopicMetricParam)param; + + String topic = topicMetricParam.getTopic(); + Long clusterId = topicMetricParam.getClusterId(); + String metric = topicMetricParam.getMetric(); + + VersionJmxInfo jmxInfo = getJMXInfo(clusterId, metric); + if(null == jmxInfo){return Result.buildFailure(VC_ITEM_JMX_NOT_EXIST);} + + if (!DataBaseDataLocalCache.isHaTopic(clusterId, topic)) { + return Result.buildFailure(NOT_EXIST); + } + + List brokers = this.listAliveBrokersByTopic(clusterId, topic); + if(CollectionUtils.isEmpty(brokers)){return Result.buildFailure(BROKER_NOT_EXIST);} + + Float sumLag = 0f; + for (Broker broker : brokers) { + JmxConnectorWrap jmxConnectorWrap = kafkaJMXClient.getClientWithCheck(clusterId, broker.getBrokerId()); + try { + String jmxObjectName = String.format(jmxInfo.getJmxObjectName(), topic); + Set objectNameSet = jmxConnectorWrap.queryNames(new ObjectName(jmxObjectName), null); + for (ObjectName name : objectNameSet) { + Object attribute = jmxConnectorWrap.getAttribute(name, jmxInfo.getJmxAttribute()); + sumLag += Float.valueOf(attribute.toString()); + } + } catch (Exception e) { + LOGGER.error("method=getTopicMirrorFetchLag||cluster={}||brokerId={}||topic={}||metrics={}||jmx={}||msg={}", + clusterId, broker.getBrokerId(), topic, metric, jmxInfo.getJmxObjectName(), e.getClass().getName()); + } + } + TopicMetrics topicMetric = new TopicMetrics(topic, clusterId, true); + topicMetric.putMetric(metric, sumLag); + return Result.buildSuc(Arrays.asList(topicMetric)); + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java index 769bd225..3f4c0a68 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/fe/FrontEndControlVersionItems.java @@ -33,6 +33,9 @@ public class FrontEndControlVersionItems extends BaseMetricVersionMetric { private static final String FE_CREATE_TOPIC_CLEANUP_POLICY = "FECreateTopicCleanupPolicy"; + private static final String FE_HA_CREATE_MIRROR_TOPIC = "FEHaCreateMirrorTopic"; + private static final String FE_HA_DELETE_MIRROR_TOPIC = "FEHaDeleteMirrorTopic"; + public FrontEndControlVersionItems(){} @Override @@ -80,6 +83,12 @@ public class FrontEndControlVersionItems extends BaseMetricVersionMetric { itemList.add(buildItem().minVersion(VersionEnum.V_0_10_1_0).maxVersion(VersionEnum.V_MAX) .name(FE_CREATE_TOPIC_CLEANUP_POLICY).desc("Topic-创建Topic-Cleanup-Policy")); + // HA-Topic复制 + itemList.add(buildItem().minVersion(VersionEnum.V_2_5_0_D_300).maxVersion(VersionEnum.V_2_5_0_D_MAX) + .name(FE_HA_CREATE_MIRROR_TOPIC).desc("HA-创建Topic复制")); + itemList.add(buildItem().minVersion(VersionEnum.V_2_5_0_D_300).maxVersion(VersionEnum.V_2_5_0_D_MAX) + .name(FE_HA_DELETE_MIRROR_TOPIC).desc("HA-取消Topic复制")); + return itemList; } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java index 86296a5d..ed192561 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/TopicMetricVersionItems.java @@ -2,6 +2,7 @@ package com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka; import com.xiaojukeji.know.streaming.km.common.bean.entity.version.VersionMetricControlItem; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BaseMetricVersionMetric; import org.springframework.stereotype.Component; @@ -36,6 +37,8 @@ public class TopicMetricVersionItems extends BaseMetricVersionMetric { public static final String TOPIC_METRIC_BYTES_OUT_MIN_15 = "BytesOut_min_15"; public static final String TOPIC_METRIC_LOG_SIZE = "LogSize"; public static final String TOPIC_METRIC_UNDER_REPLICA_PARTITIONS = "PartitionURP"; + + public static final String TOPIC_METRIC_MIRROR_FETCH_LAG = "MirrorFetchLag"; public static final String TOPIC_METRIC_COLLECT_COST_TIME = Constant.COLLECT_METRICS_COST_TIME_METRICS_NAME; @Override @@ -148,6 +151,11 @@ public class TopicMetricVersionItems extends BaseMetricVersionMetric { .name(TOPIC_METRIC_COLLECT_COST_TIME).unit("秒").desc("采集Topic指标的耗时").category(CATEGORY_PERFORMANCE) .extendMethod(TOPIC_METHOD_DO_NOTHING)); + itemList.add(buildItem().minVersion(VersionEnum.V_2_5_0_D_300).maxVersion(VersionEnum.V_2_5_0_D_MAX) + .name(TOPIC_METRIC_MIRROR_FETCH_LAG).unit("条").desc("Topic复制延迟消息数").category(CATEGORY_FLOW) + .extend(buildJMXMethodExtend(TOPIC_METHOD_GET_TOPIC_MIRROR_FETCH_LAG) + .jmxObjectName(JMX_SERVER_TOPIC_MIRROR).jmxAttribute(VALUE))); + return itemList; } } diff --git a/km-enterprise/km-ha/pom.xml b/km-enterprise/km-ha/pom.xml new file mode 100644 index 00000000..88a2e1e0 --- /dev/null +++ b/km-enterprise/km-ha/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + com.xiaojukeji.kafka + km-ha + ${km.revision} + jar + + + km + com.xiaojukeji.kafka + ${km.revision} + ../../pom.xml + + + + + + com.xiaojukeji.kafka + km-common + ${project.parent.version} + + + com.xiaojukeji.kafka + km-core + ${project.parent.version} + + + \ No newline at end of file diff --git a/km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/MirrorTopicService.java b/km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/MirrorTopicService.java new file mode 100644 index 00000000..96830b0f --- /dev/null +++ b/km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/MirrorTopicService.java @@ -0,0 +1,30 @@ +package com.xiaojukeji.know.streaming.km.ha.mirror.service; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror.MirrorTopicCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror.MirrorTopicDeleteDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.ha.mirror.TopicMirrorInfoVO; + +import java.util.List; + +public interface MirrorTopicService { + + /** + * @param dtoList + * @return + */ + Result batchCreateMirrorTopic(List dtoList); + + /** + * @param dtoList + * @return + */ + Result batchDeleteMirrorTopic(List dtoList); + + /** + * @param clusterPhyId + * @param topicName + * @return + */ + Result> getTopicsMirrorInfo(Long clusterPhyId, String topicName); +} diff --git a/km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/impl/MirrorTopicServiceImpl.java b/km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/impl/MirrorTopicServiceImpl.java new file mode 100644 index 00000000..15770ef7 --- /dev/null +++ b/km-enterprise/km-ha/src/main/java/com/xiaojukeji/know/streaming/km/ha/mirror/service/impl/MirrorTopicServiceImpl.java @@ -0,0 +1,151 @@ +package com.xiaojukeji.know.streaming.km.ha.mirror.service.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror.MirrorTopicCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror.MirrorTopicDeleteDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.ha.HaActiveStandbyRelation; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.ha.mirror.TopicMirrorInfoVO; +import com.xiaojukeji.know.streaming.km.common.enums.ha.HaResTypeEnum; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; +import com.xiaojukeji.know.streaming.km.core.service.ha.HaActiveStandbyRelationService; +import com.xiaojukeji.know.streaming.km.core.service.topic.TopicMetricService; +import com.xiaojukeji.know.streaming.km.ha.mirror.service.MirrorTopicService; +import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; +import kafka.zk.KafkaZkClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; + +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.TOPIC_METRIC_BYTES_IN; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.TOPIC_METRIC_MIRROR_FETCH_LAG; + +@Service +public class MirrorTopicServiceImpl implements MirrorTopicService { + private static final ILog logger = LogFactory.getLog(MirrorTopicServiceImpl.class); + + @Autowired + private ClusterPhyService clusterPhyService; + + @Autowired + private TopicMetricService topicMetricService; + + @Autowired + private KafkaAdminZKClient kafkaAdminZKClient; + + @Autowired + private HaActiveStandbyRelationService haActiveStandbyRelationService; + + @Override + public Result batchCreateMirrorTopic(List dtoList) { + for (MirrorTopicCreateDTO mirrorTopicCreateDTO : dtoList) { + try { + KafkaZkClient kafkaZkClient = kafkaAdminZKClient.getClient(mirrorTopicCreateDTO.getDestClusterPhyId()); + ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(mirrorTopicCreateDTO.getSourceClusterPhyId()); + Properties newHaClusters = ConvertUtil.str2ObjByJson(clusterPhy.getClientProperties(), Properties.class); + newHaClusters.put("bootstrap.servers", clusterPhy.getBootstrapServers()); + if(clusterPhy.getKafkaVersion().contains("2.5.0-d-")){ + newHaClusters.put("didi.kafka.enable", "true"); + }else{ + newHaClusters.put("didi.kafka.enable", "false"); + } + Properties oldHaClusters = kafkaZkClient.getEntityConfigs("ha-clusters", String.valueOf(mirrorTopicCreateDTO.getSourceClusterPhyId())); + if (!oldHaClusters.equals(newHaClusters)) { + kafkaZkClient.setOrCreateEntityConfigs("ha-clusters", String.valueOf(mirrorTopicCreateDTO.getSourceClusterPhyId()), newHaClusters); + kafkaZkClient.createConfigChangeNotification("ha-clusters/" + mirrorTopicCreateDTO.getSourceClusterPhyId()); + } + boolean pathExists = kafkaZkClient.pathExists("/brokers/topics/" + mirrorTopicCreateDTO.getTopicName()); + if (pathExists) { + return Result.buildFailure(String.format("目标集群已存在%s,保证数据一致性,请删除后再创建", mirrorTopicCreateDTO.getTopicName())); + } + Properties haTopics = kafkaZkClient.getEntityConfigs("ha-topics", mirrorTopicCreateDTO.getTopicName()); + haTopics.put("didi.ha.remote.cluster", String.valueOf(mirrorTopicCreateDTO.getSourceClusterPhyId())); + haTopics.put("didi.ha.sync.topic.partitions.enabled", "true"); + if (mirrorTopicCreateDTO.getSyncConfig()) { + haTopics.put("didi.ha.sync.topic.configs.enabled", "true"); + } + kafkaZkClient.setOrCreateEntityConfigs("ha-topics", mirrorTopicCreateDTO.getTopicName(), haTopics); + kafkaZkClient.createConfigChangeNotification("ha-topics/" + mirrorTopicCreateDTO.getTopicName()); + haActiveStandbyRelationService.batchReplaceTopicHA(mirrorTopicCreateDTO.getSourceClusterPhyId(), mirrorTopicCreateDTO.getDestClusterPhyId(), Collections.singletonList(mirrorTopicCreateDTO.getTopicName())); + } catch (Exception e) { + logger.error("method=batchCreateMirrorTopic||topicName:{}||errMsg=exception", mirrorTopicCreateDTO.getTopicName(), e); + return Result.buildFailure(e.getMessage()); + } + } + return Result.buildSuc(); + } + + @Override + public Result batchDeleteMirrorTopic(List dtoList) { + for (MirrorTopicDeleteDTO mirrorTopicDeleteDTO : dtoList) { + try { + KafkaZkClient kafkaZkClient = kafkaAdminZKClient.getClient(mirrorTopicDeleteDTO.getDestClusterPhyId()); + Properties haTopics = kafkaZkClient.getEntityConfigs("ha-topics", mirrorTopicDeleteDTO.getTopicName()); + if (haTopics.size() != 0) { + kafkaZkClient.setOrCreateEntityConfigs("ha-topics", mirrorTopicDeleteDTO.getTopicName(), new Properties()); + kafkaZkClient.createConfigChangeNotification("ha-topics/" + mirrorTopicDeleteDTO.getTopicName()); + } + haActiveStandbyRelationService.batchDeleteTopicHA(mirrorTopicDeleteDTO.getSourceClusterPhyId(), mirrorTopicDeleteDTO.getDestClusterPhyId(), Collections.singletonList(mirrorTopicDeleteDTO.getTopicName())); + } catch (Exception e) { + logger.error("method=batchDeleteMirrorTopic||topicName:{}||errMsg=exception", mirrorTopicDeleteDTO.getTopicName(), e); + return Result.buildFailure(e.getMessage()); + } + } + return Result.buildSuc(); + } + + @Override + public Result> getTopicsMirrorInfo(Long clusterPhyId, String topicName) { + List haActiveStandbyRelations = haActiveStandbyRelationService.listByClusterAndType(clusterPhyId, HaResTypeEnum.MIRROR_TOPIC); + List topicMirrorInfoVOList = new ArrayList<>(); + for (HaActiveStandbyRelation activeStandbyRelation : haActiveStandbyRelations) { + if (activeStandbyRelation.getResName().equals(topicName)) { + ClusterPhy standbyClusterPhy = clusterPhyService.getClusterByCluster(activeStandbyRelation.getStandbyClusterPhyId()); + ClusterPhy activeClusterPhy = clusterPhyService.getClusterByCluster(activeStandbyRelation.getActiveClusterPhyId()); + TopicMirrorInfoVO topicMirrorInfoVO = new TopicMirrorInfoVO(); + topicMirrorInfoVO.setSourceClusterId(activeStandbyRelation.getActiveClusterPhyId()); + topicMirrorInfoVO.setDestClusterId(activeStandbyRelation.getStandbyClusterPhyId()); + topicMirrorInfoVO.setTopicName(activeStandbyRelation.getResName()); + topicMirrorInfoVO.setSourceClusterName(activeClusterPhy.getName()); + topicMirrorInfoVO.setDestClusterName(standbyClusterPhy.getName()); + Result> ret = topicMetricService.collectTopicMetricsFromKafka(activeStandbyRelation.getStandbyClusterPhyId(), activeStandbyRelation.getResName(), TOPIC_METRIC_BYTES_IN); + if (ret.hasData()) { + Double value = this.getTopicAggMetric(ret.getData(), TOPIC_METRIC_BYTES_IN); + topicMirrorInfoVO.setReplicationBytesIn(value); + } + + ret = topicMetricService.collectTopicMetricsFromKafka(activeStandbyRelation.getActiveClusterPhyId(), activeStandbyRelation.getResName(), TOPIC_METRIC_BYTES_IN); + if (ret.hasData()) { + Double value = this.getTopicAggMetric(ret.getData(), TOPIC_METRIC_BYTES_IN); + topicMirrorInfoVO.setBytesIn(value); + } + + ret = topicMetricService.collectTopicMetricsFromKafka(activeStandbyRelation.getStandbyClusterPhyId(), activeStandbyRelation.getResName(), TOPIC_METRIC_MIRROR_FETCH_LAG); + if (ret.hasData()) { + Float lag = ret.getData().get(0).getMetric(TOPIC_METRIC_MIRROR_FETCH_LAG); + topicMirrorInfoVO.setLag(lag == null ? 0 : lag.longValue()); + } + + topicMirrorInfoVOList.add(topicMirrorInfoVO); + } + } + return Result.buildSuc(topicMirrorInfoVOList); + } + + private Double getTopicAggMetric(List topicMetricsList, String metricName) { + for (TopicMetrics topicMetrics : topicMetricsList) { + if (topicMetrics.isBBrokerAgg()) { + Float value = topicMetrics.getMetric(metricName); + if (value != null) { + return value.doubleValue(); + } + } + } + return Double.NaN; + } +} \ No newline at end of file diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java index 5c6a1fbf..32ac7ce8 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java @@ -29,6 +29,9 @@ public class DataBaseDataLocalCache { @Value(value = "${cache.metadata.health-check-result-size:10000}") private Long healthCheckResultCacheSize; + @Value(value = "${cache.metadata.ha-topic-size:10000}") + private Long haTopicCacheSize; + private static Cache> topicLatestMetricsCache; private static Cache clusterLatestMetricsCache; @@ -36,6 +39,7 @@ public class DataBaseDataLocalCache { private static Cache>> partitionsCache; private static Cache>> healthCheckResultCache; + private static Cache haTopicCache; @PostConstruct private void init() { @@ -58,6 +62,11 @@ public class DataBaseDataLocalCache { .expireAfterWrite(90, TimeUnit.SECONDS) .maximumSize(healthCheckResultCacheSize) .build(); + + haTopicCache = Caffeine.newBuilder() + .expireAfterWrite(90, TimeUnit.SECONDS) + .maximumSize(haTopicCacheSize) + .build(); } public static Map getTopicMetrics(Long clusterPhyId) { @@ -100,6 +109,16 @@ public class DataBaseDataLocalCache { return clusterId * HealthCheckDimensionEnum.MAX_VAL.getDimension() + dimensionCode; } + public static void putHaTopic(Long clusterPhyId, String topicName) { + String key = clusterPhyId + "@" + topicName; + haTopicCache.put(key, true); + } + + public static boolean isHaTopic(Long clusterPhyId, String topicName) { + String key = clusterPhyId + "@" + topicName; + return haTopicCache.getIfPresent(key) != null; + } + /**************************************************** private method ****************************************************/ private DataBaseDataLocalCache() { diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/ha/HaActiveStandbyRelationDAO.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/ha/HaActiveStandbyRelationDAO.java new file mode 100644 index 00000000..9cf182e6 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/mysql/ha/HaActiveStandbyRelationDAO.java @@ -0,0 +1,9 @@ +package com.xiaojukeji.know.streaming.km.persistence.mysql.ha; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xiaojukeji.know.streaming.km.common.bean.po.ha.HaActiveStandbyRelationPO; +import org.springframework.stereotype.Repository; + +@Repository +public interface HaActiveStandbyRelationDAO extends BaseMapper { +} diff --git a/km-rest/pom.xml b/km-rest/pom.xml index 091e2696..b86151e3 100644 --- a/km-rest/pom.xml +++ b/km-rest/pom.xml @@ -56,6 +56,12 @@ ${project.parent.version} + + com.xiaojukeji.kafka + km-ha + ${project.parent.version} + + org.apache.kafka kafka-clients diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorTopicController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorTopicController.java new file mode 100644 index 00000000..15972678 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorTopicController.java @@ -0,0 +1,51 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.enterprise.mirror; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror.MirrorTopicCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.ha.mirror.MirrorTopicDeleteDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.ha.mirror.TopicMirrorInfoVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.ha.mirror.service.MirrorTopicService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author zengqiao + * @date 22/12/12 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Mirror-Topic-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_HA_MIRROR_PREFIX) +public class MirrorTopicController { + + @Autowired + private MirrorTopicService mirrorTopicService; + + @ApiOperation(value = "批量创建Topic镜像", notes = "") + @PostMapping(value = "topics") + @ResponseBody + public Result batchCreateMirrorTopic(@Validated @RequestBody List dtoList) { + return mirrorTopicService.batchCreateMirrorTopic(dtoList); + } + + @ApiOperation(value = "批量删除Topic镜像", notes = "") + @DeleteMapping(value = "topics") + @ResponseBody + public Result batchDeleteMirrorTopic(@Validated @RequestBody List dtoList) { + return mirrorTopicService.batchDeleteMirrorTopic(dtoList); + } + + @ApiOperation(value = "Topic镜像信息", notes = "") + @GetMapping(value = "clusters/{clusterPhyId}/topics/{topicName}/mirror-info") + @ResponseBody + public Result> getTopicsMirrorInfo(@PathVariable Long clusterPhyId, + @PathVariable String topicName) { + return mirrorTopicService.getTopicsMirrorInfo(clusterPhyId, topicName); + } +} diff --git a/pom.xml b/pom.xml index e288d0ce..0ba4d00d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,7 @@ km-collector km-rest km-dist + km-enterprise/km-ha From 5110b30f622b8a209e7a0392d178dd43ee0c4eec Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 15:35:19 +0800 Subject: [PATCH 108/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-MM2?= =?UTF-8?q?=E5=81=A5=E5=BA=B7=E5=B7=A1=E6=A3=80(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/entity/cluster/ClusterPhysState.java | 2 + .../param/connect/mm2/MirrorMakerParam.java | 32 +++ .../bean/vo/cluster/ClusterPhysStateVO.java | 3 + .../health/HealthCheckDimensionEnum.java | 2 + .../enums/health/HealthCheckNameEnum.java | 43 +++- .../connect/HealthCheckConnectorService.java | 31 ++- .../mm2/HealthCheckMirrorMakerService.java | 205 ++++++++++++++++++ .../checkresult/HealthCheckResultService.java | 2 + .../impl/HealthCheckResultServiceImpl.java | 22 +- .../health/state/HealthStateService.java | 2 + .../state/impl/HealthStateServiceImpl.java | 51 +++++ .../api/v3/health/KafkaHealthController.java | 8 + .../health/MirrorMakerHealthCheckTask.java | 33 +++ 13 files changed, 414 insertions(+), 22 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/mm2/MirrorMakerParam.java create mode 100644 km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/mm2/HealthCheckMirrorMakerService.java create mode 100644 km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/health/MirrorMakerHealthCheckTask.java diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhysState.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhysState.java index 38daaebc..2f1abc29 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhysState.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/cluster/ClusterPhysState.java @@ -18,5 +18,7 @@ public class ClusterPhysState { private Integer downCount; + private Integer unknownCount; + private Integer total; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/mm2/MirrorMakerParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/mm2/MirrorMakerParam.java new file mode 100644 index 00000000..357559c6 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/mm2/MirrorMakerParam.java @@ -0,0 +1,32 @@ +package com.xiaojukeji.know.streaming.km.common.bean.entity.param.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ConnectClusterParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author wyb + * @date 2022/12/21 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MirrorMakerParam extends ConnectClusterParam { + + private String mirrorMakerName; + + private String connectorType; + + List mirrorMakerTopicList; + + public MirrorMakerParam(Long connectClusterId, String connectorType, String mirrorMakerName, List mirrorMakerTopicList) { + super(connectClusterId); + this.mirrorMakerName = mirrorMakerName; + this.connectorType = connectorType; + this.mirrorMakerTopicList = mirrorMakerTopicList; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/ClusterPhysStateVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/ClusterPhysStateVO.java index fdd814c0..b8a7c8dd 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/ClusterPhysStateVO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/ClusterPhysStateVO.java @@ -18,6 +18,9 @@ public class ClusterPhysStateVO { @ApiModelProperty(value = "挂掉集群数", example = "10") private Integer downCount; + @ApiModelProperty(value = "未知状态集群数", example = "10") + private Integer unknownCount; + @ApiModelProperty(value = "集群总数", example = "40") private Integer total; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java index eaa730f4..5312f57a 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckDimensionEnum.java @@ -24,6 +24,8 @@ public enum HealthCheckDimensionEnum { CONNECTOR(6, "Connector", "Connect"), + MIRROR_MAKER(7,"MirrorMaker","MirrorMaker"), + MAX_VAL(100, "所有的dimension的值需要小于MAX_VAL", "Ignore") ; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java index 2d6d4133..20ddccc3 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/enums/health/HealthCheckNameEnum.java @@ -136,7 +136,7 @@ public enum HealthCheckNameEnum { HealthCheckDimensionEnum.CONNECT_CLUSTER, "TaskStartupFailurePercentage", Constant.HC_CONFIG_NAME_PREFIX+"CONNECT_CLUSTER_TASK_STARTUP_FAILURE_PERCENTAGE", - "connect集群任务启动失败概率", + "Connect集群任务启动失败概率", HealthCompareValueConfig.class, false ), @@ -145,7 +145,7 @@ public enum HealthCheckNameEnum { HealthCheckDimensionEnum.CONNECTOR, "ConnectorFailedTaskCount", Constant.HC_CONFIG_NAME_PREFIX+"CONNECTOR_FAILED_TASK_COUNT", - "connector失败状态的任务数量", + "Connector失败状态的任务数量", HealthCompareValueConfig.class, false ), @@ -154,13 +154,50 @@ public enum HealthCheckNameEnum { HealthCheckDimensionEnum.CONNECTOR, "ConnectorUnassignedTaskCount", Constant.HC_CONFIG_NAME_PREFIX+"CONNECTOR_UNASSIGNED_TASK_COUNT", - "connector未被分配的任务数量", + "Connector未被分配的任务数量", + HealthCompareValueConfig.class, + false + ), + + MIRROR_MAKER_FAILED_TASK_COUNT( + HealthCheckDimensionEnum.MIRROR_MAKER, + "MirrorMakerFailedTaskCount", + Constant.HC_CONFIG_NAME_PREFIX+"MIRROR_MAKER_FAILED_TASK_COUNT", + "MirrorMaker失败状态的任务数量", + HealthCompareValueConfig.class, + false + ), + + MIRROR_MAKER_UNASSIGNED_TASK_COUNT( + HealthCheckDimensionEnum.MIRROR_MAKER, + "MirrorMakerUnassignedTaskCount", + Constant.HC_CONFIG_NAME_PREFIX+"MIRROR_MAKER_UNASSIGNED_TASK_COUNT", + "MirrorMaker未被分配的任务数量", + HealthCompareValueConfig.class, + false + ), + + MIRROR_MAKER_TOTAL_RECORD_ERRORS( + HealthCheckDimensionEnum.MIRROR_MAKER, + "TotalRecord-errors", + Constant.HC_CONFIG_NAME_PREFIX + "MIRROR_MAKER_TOTAL_RECORD_ERRORS", + "MirrorMaker消息处理错误的次数", + HealthCompareValueConfig.class, + false + ), + + MIRROR_MAKER_REPLICATION_LATENCY_MS_MAX( + HealthCheckDimensionEnum.MIRROR_MAKER, + "ReplicationLatencyMsMax", + Constant.HC_CONFIG_NAME_PREFIX + "MIRROR_MAKER_REPLICATION_LATENCY_MS_MAX", + "MirrorMaker消息复制最大延迟时间", HealthCompareValueConfig.class, false ) + ; /** diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java index e4286423..fe60bb86 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/HealthCheckConnectorService.java @@ -10,7 +10,9 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.Conne import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.connect.ConnectorParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; 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.utils.Tuple; @@ -55,13 +57,10 @@ public class HealthCheckConnectorService extends AbstractHealthCheckService { @Override public List getResList(Long connectClusterId) { List paramList = new ArrayList<>(); - Result> ret = connectorService.listConnectorsFromCluster(connectClusterId); - if (!ret.hasData()) { - return paramList; - } + List connectorPOList = connectorService.listByConnectClusterIdFromDB(connectClusterId); - for (String connectorName : ret.getData()) { - paramList.add(new ConnectorParam(connectClusterId, connectorName)); + for (ConnectorPO connectorPO : connectorPOList) { + paramList.add(new ConnectorParam(connectClusterId, connectorPO.getConnectorName(), connectorPO.getConnectorType())); } return paramList; @@ -88,9 +87,10 @@ public class HealthCheckConnectorService extends AbstractHealthCheckService { Long connectClusterId = param.getConnectClusterId(); String connectorName = param.getConnectorName(); + String connectorType = param.getConnectorType(); Double compareValue = compareConfig.getValue(); - return this.getHealthCompareResult(connectClusterId, connectorName, CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT, HealthCheckNameEnum.CONNECTOR_FAILED_TASK_COUNT, compareValue); + return this.getHealthCompareResult(connectClusterId, connectorName, connectorType, HealthCheckDimensionEnum.CONNECTOR.getDimension(), CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT, HealthCheckNameEnum.CONNECTOR_FAILED_TASK_COUNT, compareValue); } private HealthCheckResult checkUnassignedTaskCount(Tuple paramTuple) { @@ -99,17 +99,18 @@ public class HealthCheckConnectorService extends AbstractHealthCheckService { Long connectClusterId = param.getConnectClusterId(); String connectorName = param.getConnectorName(); + String connectorType = param.getConnectorType(); Double compareValue = compareConfig.getValue(); - return this.getHealthCompareResult(connectClusterId, connectorName, CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT, HealthCheckNameEnum.CONNECTOR_UNASSIGNED_TASK_COUNT, compareValue); + return this.getHealthCompareResult(connectClusterId, connectorName, connectorType, HealthCheckDimensionEnum.CONNECTOR.getDimension(), CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT, HealthCheckNameEnum.CONNECTOR_UNASSIGNED_TASK_COUNT, compareValue); } - private HealthCheckResult getHealthCompareResult(Long connectClusterId, String connectorName, String metricName, HealthCheckNameEnum healthCheckNameEnum, Double compareValue) { + public HealthCheckResult getHealthCompareResult(Long connectClusterId, String connectorName, String connectorType, Integer dimension, String metricName, HealthCheckNameEnum healthCheckNameEnum, Double compareValue) { - Result ret = connectorMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, connectorName, metricName); + Result ret = connectorMetricService.collectConnectClusterMetricsFromKafka(connectClusterId, connectorName, metricName , ConnectorTypeEnum.getByName(connectorType)); - if (!ret.hasData()) { + if (!ret.hasData() || ret.getData().getMetric(metricName) == null) { log.error("method=getHealthCompareResult||connectClusterId={}||connectorName={}||metricName={}||errMsg=get metrics failed", connectClusterId, connectorName, metricName); return null; @@ -117,14 +118,8 @@ public class HealthCheckConnectorService extends AbstractHealthCheckService { Float value = ret.getData().getMetric(metricName); - if (value == null) { - log.error("method=getHealthCompareResult||connectClusterId={}||connectorName={}||metricName={}||errMsg=get metrics failed", - connectClusterId, connectorName, metricName); - return null; - } - HealthCheckResult checkResult = new HealthCheckResult( - HealthCheckDimensionEnum.CONNECTOR.getDimension(), + dimension, healthCheckNameEnum.getConfigName(), connectClusterId, connectorName diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/mm2/HealthCheckMirrorMakerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/mm2/HealthCheckMirrorMakerService.java new file mode 100644 index 00000000..70df3134 --- /dev/null +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/connect/mm2/HealthCheckMirrorMakerService.java @@ -0,0 +1,205 @@ +package com.xiaojukeji.know.streaming.km.core.service.health.checker.connect.mm2; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.google.common.collect.Table; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.mm2.MirrorMakerTopic; +import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.param.connect.mm2.MirrorMakerParam; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; +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.utils.Tuple; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.connect.HealthCheckConnectorService; +import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; +import com.xiaojukeji.know.streaming.km.persistence.es.dao.connect.connector.ConnectorMetricESDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.*; +import java.util.stream.Collectors; + +import static com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant.MIRROR_MAKER_SOURCE_CONNECTOR_TYPE; +import static com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum.SOURCE; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectorMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.MirrorMakerMetricVersionItems.MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MAX; + +/** + * @author wyb + * @date 2022/12/21 + */ +@Service +public class HealthCheckMirrorMakerService extends AbstractHealthCheckService { + private static final ILog log = LogFactory.getLog(HealthCheckMirrorMakerService.class); + + @Autowired + private ConnectorService connectorService; + + @Autowired + private MirrorMakerService mirrorMakerService; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private MirrorMakerMetricService mirrorMakerMetricService; + + @Autowired + private ConnectorMetricESDAO connectorMetricESDAO; + + @Autowired + private HealthCheckConnectorService healthCheckConnectorService; + + private static final Long TEN_MIN = 10 * 60 * 1000L; + + @PostConstruct + private void init() { + functionMap.put(HealthCheckNameEnum.MIRROR_MAKER_UNASSIGNED_TASK_COUNT.getConfigName(), this::checkUnassignedTaskCount); + functionMap.put(HealthCheckNameEnum.MIRROR_MAKER_FAILED_TASK_COUNT.getConfigName(), this::checkFailedTaskCount); + functionMap.put(HealthCheckNameEnum.MIRROR_MAKER_REPLICATION_LATENCY_MS_MAX.getConfigName(), this::checkReplicationLatencyMsMax); + functionMap.put(HealthCheckNameEnum.MIRROR_MAKER_TOTAL_RECORD_ERRORS.getConfigName(), this::checkTotalRecordErrors); + } + + @Override + public List getResList(Long connectClusterId) { + List paramList = new ArrayList<>(); + List mirrorMakerList = connectorService.listByConnectClusterIdFromDB(connectClusterId).stream().filter(elem -> elem.getConnectorType().equals(SOURCE.name()) && elem.getConnectorClassName().equals(MIRROR_MAKER_SOURCE_CONNECTOR_TYPE)).collect(Collectors.toList()); + + if (mirrorMakerList.isEmpty()) { + return paramList; + } + Result> ret = mirrorMakerService.getMirrorMakerTopicMap(connectClusterId); + + if (!ret.hasData()) { + log.error("method=getResList||connectClusterId={}||get MirrorMakerTopicMap failed!", connectClusterId); + return paramList; + } + + Map mirrorMakerTopicMap = ret.getData(); + + for (ConnectorPO mirrorMaker : mirrorMakerList) { + List mirrorMakerTopicList = mirrorMakerService.getMirrorMakerTopicList(mirrorMaker, mirrorMakerTopicMap); + paramList.add(new MirrorMakerParam(connectClusterId, mirrorMaker.getConnectorType(), mirrorMaker.getConnectorName(), mirrorMakerTopicList)); + } + return paramList; + } + + @Override + public HealthCheckDimensionEnum getHealthCheckDimensionEnum() { + return HealthCheckDimensionEnum.MIRROR_MAKER; + } + + @Override + public Integer getDimensionCodeIfSupport(Long kafkaClusterPhyId) { + List clusterList = connectClusterService.listByKafkaCluster(kafkaClusterPhyId); + if (ValidateUtils.isEmptyList(clusterList)) { + return null; + } + + return this.getHealthCheckDimensionEnum().getDimension(); + } + + private HealthCheckResult checkFailedTaskCount(Tuple paramTuple) { + MirrorMakerParam param = (MirrorMakerParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + String connectorType = param.getConnectorType(); + Double compareValue = compareConfig.getValue(); + + return healthCheckConnectorService.getHealthCompareResult(connectClusterId, mirrorMakerName, connectorType, HealthCheckDimensionEnum.MIRROR_MAKER.getDimension(), CONNECTOR_METRIC_CONNECTOR_FAILED_TASK_COUNT, HealthCheckNameEnum.MIRROR_MAKER_FAILED_TASK_COUNT, compareValue); + } + + private HealthCheckResult checkUnassignedTaskCount(Tuple paramTuple) { + MirrorMakerParam param = (MirrorMakerParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + String connectorType = param.getConnectorType(); + Double compareValue = compareConfig.getValue(); + + return healthCheckConnectorService.getHealthCompareResult(connectClusterId, mirrorMakerName, connectorType, HealthCheckDimensionEnum.MIRROR_MAKER.getDimension(), CONNECTOR_METRIC_CONNECTOR_UNASSIGNED_TASK_COUNT, HealthCheckNameEnum.MIRROR_MAKER_UNASSIGNED_TASK_COUNT, compareValue); + } + + private HealthCheckResult checkReplicationLatencyMsMax(Tuple paramTuple) { + MirrorMakerParam param = (MirrorMakerParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + List mirrorMakerTopicList = param.getMirrorMakerTopicList(); + String metricName = MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MAX; + + Result ret = mirrorMakerMetricService.collectMirrorMakerMetricsFromKafka(connectClusterId, mirrorMakerName, mirrorMakerTopicList, metricName); + + if (!ret.hasData() || ret.getData().getMetric(metricName) == null) { + log.error("method=checkReplicationLatencyMsMax||connectClusterId={}||metricName={}||errMsg=get metrics failed", + param.getConnectClusterId(), metricName); + return null; + } + + Float value = ret.getData().getMetric(metricName); + + HealthCheckResult checkResult = new HealthCheckResult( + HealthCheckDimensionEnum.MIRROR_MAKER.getDimension(), + HealthCheckNameEnum.MIRROR_MAKER_REPLICATION_LATENCY_MS_MAX.getConfigName(), + connectClusterId, + mirrorMakerName + ); + checkResult.setPassed(value <= compareConfig.getValue() ? Constant.YES : Constant.NO); + return checkResult; + } + + private HealthCheckResult checkTotalRecordErrors(Tuple paramTuple){ + MirrorMakerParam param = (MirrorMakerParam) paramTuple.getV1(); + HealthCompareValueConfig compareConfig = (HealthCompareValueConfig) paramTuple.getV2(); + + Long connectClusterId = param.getConnectClusterId(); + String mirrorMakerName = param.getMirrorMakerName(); + List mirrorMakerTopicList = param.getMirrorMakerTopicList(); + + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectClusterId); + Long endTime = System.currentTimeMillis(); + Long startTime = endTime - TEN_MIN; + Tuple connectClusterIdAndName = new Tuple<>(connectClusterId, mirrorMakerName); + String metricName = CONNECTOR_METRIC_TOTAL_RECORD_ERRORS; + + Table, List> table = connectorMetricESDAO.listMetricsByConnectors(connectCluster.getKafkaClusterPhyId(), Arrays.asList(metricName), "avg", Arrays.asList(connectClusterIdAndName), startTime, endTime); + List pointVOList = table.get(metricName, connectClusterIdAndName); + Collections.sort(pointVOList, (p1, p2) -> p2.getTimeStamp().compareTo(p1.getTimeStamp())); + + HealthCheckResult checkResult = new HealthCheckResult( + HealthCheckDimensionEnum.MIRROR_MAKER.getDimension(), + HealthCheckNameEnum.MIRROR_MAKER_TOTAL_RECORD_ERRORS.getConfigName(), + connectClusterId, + mirrorMakerName + ); + + double diff = 0; + if (pointVOList.size() > 1) { + diff = Double.valueOf(pointVOList.get(0).getValue()) - Double.valueOf(pointVOList.get(1).getValue()); + } + checkResult.setPassed(diff <= compareConfig.getValue() ? Constant.YES : Constant.NO); + + return checkResult; + } + + +} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java index 05346d0d..d8038b5f 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/HealthCheckResultService.java @@ -26,4 +26,6 @@ public interface HealthCheckResultService { void batchReplace(Long clusterPhyId, Integer dimension, List healthCheckResults); List getConnectorHealthCheckResult(Long clusterPhyId); + + List getMirrorMakerHealthCheckResult(Long clusterPhyId); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java index 0d15561f..4c1471b6 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java @@ -27,6 +27,7 @@ import java.util.*; import java.util.stream.Collectors; import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum.CONNECTOR; +import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum.MIRROR_MAKER; @Service public class HealthCheckResultServiceImpl implements HealthCheckResultService { @@ -146,7 +147,26 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(HealthCheckResultPO::getDimension, CONNECTOR.getDimension()); wrapper.in(HealthCheckResultPO::getClusterPhyId, connectClusterIdList); - resultPOList.addAll(healthCheckResultDAO.selectList(wrapper)); + resultPOList = healthCheckResultDAO.selectList(wrapper); + return resultPOList; + } + + @Override + public List getMirrorMakerHealthCheckResult(Long clusterPhyId) { + List resultPOList = new ArrayList<>(); + + //查找connect集群 + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ConnectClusterPO::getKafkaClusterPhyId, clusterPhyId); + List connectClusterIdList = connectClusterDAO.selectList(lambdaQueryWrapper).stream().map(elem -> elem.getId()).collect(Collectors.toList()); + if (ValidateUtils.isEmptyList(connectClusterIdList)) { + return resultPOList; + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(HealthCheckResultPO::getDimension, MIRROR_MAKER.getDimension()); + wrapper.in(HealthCheckResultPO::getClusterPhyId, connectClusterIdList); + resultPOList = healthCheckResultDAO.selectList(wrapper); return resultPOList; } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java index f9fc3edb..00b4fcc6 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/HealthStateService.java @@ -3,6 +3,7 @@ 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.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; import java.util.List; @@ -17,6 +18,7 @@ public interface HealthStateService { GroupMetrics calGroupHealthMetrics(Long clusterPhyId, String groupName); ZookeeperMetrics calZookeeperHealthMetrics(Long clusterPhyId); ConnectorMetrics calConnectorHealthMetrics(Long connectClusterId, String connectorName); + MirrorMakerMetrics calMirrorMakerHealthMetrics(Long connectClusterId, String mirrorMakerName); /** * 获取集群健康检查结果 diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java index 3610cfb7..5247564b 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java @@ -7,6 +7,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAgg 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.bean.entity.metrics.connect.ConnectorMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO; import com.xiaojukeji.know.streaming.km.common.component.SpringTool; import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum; @@ -29,6 +30,7 @@ import java.util.List; import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectorMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.MirrorMakerMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; @@ -72,6 +74,7 @@ public class HealthStateServiceImpl implements HealthStateService { metrics.putMetric(this.calClusterGroupsHealthMetrics(clusterPhyId).getMetrics()); metrics.putMetric(this.calZookeeperHealthMetrics(clusterPhyId).getMetrics()); metrics.putMetric(this.calClusterConnectsHealthMetrics(clusterPhyId).getMetrics()); + metrics.putMetric(this.calClusterMirrorMakersHealthMetrics(clusterPhyId).getMetrics()); // 统计最终结果 Float passed = 0.0f; @@ -81,6 +84,7 @@ public class HealthStateServiceImpl implements HealthStateService { passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS); passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER); passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CONNECTOR); + passed += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_PASSED_MIRROR_MAKER); Float total = 0.0f; total += metrics.getMetric(ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL); @@ -89,6 +93,7 @@ public class HealthStateServiceImpl implements HealthStateService { total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS); total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER); total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CONNECTOR); + total += metrics.getMetric(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_MIRROR_MAKER); // 状态 Float state = 0.0f; @@ -98,6 +103,7 @@ public class HealthStateServiceImpl implements HealthStateService { state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_GROUPS)); state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_CLUSTER)); state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_CONNECTOR)); + state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_MIRROR_MAKER)); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED, passed); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL, total); @@ -225,6 +231,31 @@ public class HealthStateServiceImpl implements HealthStateService { return metrics; } + @Override + public MirrorMakerMetrics calMirrorMakerHealthMetrics(Long connectClusterId, String mirrorMakerName) { + ConnectCluster connectCluster = LoadedConnectClusterCache.getByPhyId(connectClusterId); + MirrorMakerMetrics metrics = new MirrorMakerMetrics(connectClusterId, mirrorMakerName); + + if (connectCluster == null) { + metrics.putMetric(MIRROR_MAKER_METRIC_HEALTH_STATE, (float) HealthStateEnum.DEAD.getDimension()); + return metrics; + } + + List resultList = healthCheckResultService.getHealthCheckAggResult(connectClusterId, HealthCheckDimensionEnum.MIRROR_MAKER, mirrorMakerName); + + if (ValidateUtils.isEmptyList(resultList)) { + metrics.getMetrics().put(MIRROR_MAKER_METRIC_HEALTH_CHECK_PASSED, 0.0f); + metrics.getMetrics().put(MIRROR_MAKER_METRIC_HEALTH_CHECK_TOTAL, 0.0f); + } else { + metrics.getMetrics().put(MIRROR_MAKER_METRIC_HEALTH_CHECK_PASSED, this.getHealthCheckPassed(resultList)); + metrics.getMetrics().put(MIRROR_MAKER_METRIC_HEALTH_CHECK_TOTAL, (float) resultList.size()); + } + + metrics.putMetric(MIRROR_MAKER_METRIC_HEALTH_STATE, (float) this.calHealthState(resultList).getDimension()); + return metrics; + } + + @Override public List getAllDimensionHealthResult(Long clusterPhyId) { List supportedDimensionCodeList = new ArrayList<>(); @@ -249,6 +280,8 @@ public class HealthStateServiceImpl implements HealthStateService { for (Integer dimensionCode : dimensionCodeList) { if (dimensionCode.equals(HealthCheckDimensionEnum.CONNECTOR.getDimension())) { poList.addAll(healthCheckResultService.getConnectorHealthCheckResult(clusterPhyId)); + } else if (dimensionCode.equals(HealthCheckDimensionEnum.MIRROR_MAKER.getDimension())) { + poList.addAll(healthCheckResultService.getMirrorMakerHealthCheckResult(clusterPhyId)); } else { poList.addAll(healthCheckResultService.listCheckResult(clusterPhyId, dimensionCode)); } @@ -366,6 +399,24 @@ public class HealthStateServiceImpl implements HealthStateService { return metrics; } + private ClusterMetrics calClusterMirrorMakersHealthMetrics(Long clusterPhyId){ + List mirrorMakerHealthCheckResult = healthCheckResultService.getMirrorMakerHealthCheckResult(clusterPhyId); + List resultList = this.getDimensionHealthCheckAggResult(mirrorMakerHealthCheckResult, Arrays.asList(MIRROR_MAKER.getDimension())); + + ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); + + if (ValidateUtils.isEmptyList(resultList)) { + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_MIRROR_MAKER, 0.0f); + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_MIRROR_MAKER, 0.0f); + } else { + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_MIRROR_MAKER, this.getHealthCheckPassed(resultList)); + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_MIRROR_MAKER, (float) resultList.size()); + } + + metrics.putMetric(CLUSTER_METRIC_HEALTH_STATE_MIRROR_MAKER, (float) this.calHealthState(resultList).getDimension()); + return metrics; + } + /**************************************************** 聚合数据 ****************************************************/ diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java index 48a3bf46..062cf954 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/health/KafkaHealthController.java @@ -60,6 +60,14 @@ public class KafkaHealthController { @ResponseBody public Result> getClusterHealthCheckResultDetail(@PathVariable Long clusterPhyId, @RequestBody List dimensionCodeList) { + if (dimensionCodeList.isEmpty()) { + return Result.buildSuc( + HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( + healthStateService.getAllDimensionHealthResult(clusterPhyId) + ) + ); + } + return Result.buildSuc(HealthScoreVOConverter.convert2HealthScoreResultDetailVOList( healthStateService.getDimensionHealthResult(clusterPhyId, dimensionCodeList) )); diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/health/MirrorMakerHealthCheckTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/health/MirrorMakerHealthCheckTask.java new file mode 100644 index 00000000..f96396a0 --- /dev/null +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/mm2/health/MirrorMakerHealthCheckTask.java @@ -0,0 +1,33 @@ +package com.xiaojukeji.know.streaming.km.task.connect.mm2.health; + +import com.didiglobal.logi.job.annotation.Task; +import com.didiglobal.logi.job.core.consensual.ConsensualEnum; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; +import com.xiaojukeji.know.streaming.km.core.service.health.checker.connect.mm2.HealthCheckMirrorMakerService; +import com.xiaojukeji.know.streaming.km.task.connect.health.AbstractHealthCheckTask; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author wyb + * @date 2022/12/21 + */ +@NoArgsConstructor +@AllArgsConstructor +@Task(name = "MirrorMakerHealthCheckTask", + description = "MirrorMaker健康检查", + cron = "0 0/1 * * * ? *", + autoRegister = true, + consensual = ConsensualEnum.BROADCAST, + timeout = 2 * 60) +public class MirrorMakerHealthCheckTask extends AbstractHealthCheckTask { + + @Autowired + private HealthCheckMirrorMakerService healthCheckMirrorMakerService; + + @Override + public AbstractHealthCheckService getCheckService() { + return healthCheckMirrorMakerService; + } +} From f95be2c1b35fa2f2238a5cc4b43aab880af3f7fb Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:36:19 +0800 Subject: [PATCH 109/150] =?UTF-8?q?[Optimize]TaskResult=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E4=BB=BB=E5=8A=A1=E5=88=86=E7=BB=84=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../know/streaming/km/task/AbstractDispatchTask.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java index fc211c14..5150430e 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/AbstractDispatchTask.java @@ -9,6 +9,7 @@ import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.EntityIdInterface; import com.xiaojukeji.know.streaming.km.common.exception.AdminTaskCodeException; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import javax.annotation.PostConstruct; @@ -82,7 +83,15 @@ public abstract class AbstractDispatchTask Date: Thu, 9 Feb 2023 16:43:02 +0800 Subject: [PATCH 110/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E6=83=85=E5=86=B5=E4=B8=8B=EF=BC=8C=E9=9B=86=E7=BE=A4?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=BB=9F=E8=AE=A1=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98(#865)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biz/cluster/MultiClusterPhyManager.java | 6 ++ .../impl/MultiClusterPhyManagerImpl.java | 57 ++++++------------- .../impl/HealthCheckResultServiceImpl.java | 4 ++ .../state/impl/HealthStateServiceImpl.java | 24 ++++++++ .../v3/cluster/MultiClusterPhyController.java | 10 +++- 5 files changed, 61 insertions(+), 40 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/MultiClusterPhyManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/MultiClusterPhyManager.java index 0bd2f6e4..2d57d719 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/MultiClusterPhyManager.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/MultiClusterPhyManager.java @@ -4,8 +4,12 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysHe 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; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyBaseVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyDashboardVO; +import java.util.List; + /** * 多集群总体状态 */ @@ -24,4 +28,6 @@ public interface MultiClusterPhyManager { * @return */ PaginationResult getClusterPhysDashboard(MultiClusterDashboardDTO dto); + + Result> getClusterPhysBasic(); } diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java index e5fa31e1..7e379c99 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/MultiClusterPhyManagerImpl.java @@ -9,13 +9,12 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysHe 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; -import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.KafkaController; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics; 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.vo.cluster.ClusterPhyBaseVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyDashboardVO; 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; @@ -24,7 +23,6 @@ import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; 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.kafkacontroller.KafkaControllerService; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -42,37 +40,26 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager { @Autowired private ClusterMetricService clusterMetricService; - @Autowired - private KafkaControllerService kafkaControllerService; - @Override public ClusterPhysState getClusterPhysState() { List clusterPhyList = clusterPhyService.listAllClusters(); + ClusterPhysState physState = new ClusterPhysState(0, 0, 0, clusterPhyList.size()); - Map controllerMap = kafkaControllerService.getKafkaControllersFromDB( - clusterPhyList.stream().map(elem -> elem.getId()).collect(Collectors.toList()), - false - ); - - ClusterPhysState physState = new ClusterPhysState(0, 0, clusterPhyList.size()); - for (ClusterPhy clusterPhy: clusterPhyList) { - KafkaController kafkaController = controllerMap.get(clusterPhy.getId()); - - if (kafkaController != null && !kafkaController.alive()) { - // 存在明确的信息表示controller挂了 - physState.setDownCount(physState.getDownCount() + 1); - } else if ((System.currentTimeMillis() - clusterPhy.getCreateTime().getTime() >= 5 * 60 * 1000) && kafkaController == null) { - // 集群接入时间是在近5分钟内,同时kafkaController信息不存在,则设置为down + 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.DEAD.getDimension()) { physState.setDownCount(physState.getDownCount() + 1); } else { - // 其他情况都设置为alive physState.setLiveCount(physState.getLiveCount() + 1); } } - return physState; } + @Override public ClusterPhysHealthState getClusterPhysHealthState() { List clusterPhyList = clusterPhyService.listAllClusters(); @@ -107,23 +94,6 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager { // 转为vo格式,方便后续进行分页筛选等 List voList = ConvertUtil.list2List(clusterPhyList, ClusterPhyDashboardVO.class); - // 获取集群controller信息并补充到vo中, - Map controllerMap = kafkaControllerService.getKafkaControllersFromDB(clusterPhyList.stream().map(elem -> elem.getId()).collect(Collectors.toList()), false); - for (ClusterPhyDashboardVO vo: voList) { - KafkaController kafkaController = controllerMap.get(vo.getId()); - - if (kafkaController != null && !kafkaController.alive()) { - // 存在明确的信息表示controller挂了 - vo.setAlive(Constant.DOWN); - } else if ((System.currentTimeMillis() - vo.getCreateTime().getTime() >= 5 * 60L * 1000L) && kafkaController == null) { - // 集群接入时间是在近5分钟内,同时kafkaController信息不存在,则设置为down - vo.setAlive(Constant.DOWN); - } else { - // 其他情况都设置为alive - vo.setAlive(Constant.ALIVE); - } - } - // 本地分页过滤 voList = this.getAndPagingDataInLocal(voList, dto); @@ -148,6 +118,15 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager { ); } + @Override + public Result> getClusterPhysBasic() { + // 获取集群 + List clusterPhyList = clusterPhyService.listAllClusters(); + + // 转为vo格式,方便后续进行分页筛选等 + return Result.buildSuc(ConvertUtil.list2List(clusterPhyList, ClusterPhyBaseVO.class)); + } + /**************************************************** private method ****************************************************/ diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java index 4c1471b6..322f1926 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checkresult/impl/HealthCheckResultServiceImpl.java @@ -18,6 +18,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; import com.xiaojukeji.know.streaming.km.core.service.config.PlatformClusterConfigService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectClusterDAO; +import com.xiaojukeji.know.streaming.km.persistence.mysql.connect.ConnectorDAO; import com.xiaojukeji.know.streaming.km.persistence.mysql.health.HealthCheckResultDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; @@ -39,6 +40,9 @@ public class HealthCheckResultServiceImpl implements HealthCheckResultService { @Autowired private ConnectClusterDAO connectClusterDAO; + @Autowired + private ConnectorDAO connectorDAO; + @Autowired private PlatformClusterConfigService platformClusterConfigService; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java index 5247564b..b2d34f2a 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/state/impl/HealthStateServiceImpl.java @@ -1,10 +1,12 @@ package com.xiaojukeji.know.streaming.km.core.service.health.state.impl; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckAggResult; import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult; +import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.KafkaController; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.*; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect.ConnectorMetrics; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; @@ -19,7 +21,9 @@ import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClus import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService; import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService; import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService; +import com.xiaojukeji.know.streaming.km.core.service.kafkacontroller.KafkaControllerService; import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; import com.xiaojukeji.know.streaming.km.persistence.connect.cache.LoadedConnectClusterCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -29,6 +33,7 @@ import java.util.stream.Collectors; import java.util.List; import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum.*; +import static com.xiaojukeji.know.streaming.km.common.enums.health.HealthStateEnum.DEAD; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.ConnectorMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.MirrorMakerMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.BrokerMetricVersionItems.*; @@ -52,6 +57,9 @@ public class HealthStateServiceImpl implements HealthStateService { @Autowired private ConnectClusterService connectClusterService; + @Autowired + private KafkaControllerService kafkaControllerService; + @Override public ClusterMetrics calClusterHealthMetrics(Long clusterPhyId) { ClusterMetrics metrics = new ClusterMetrics(clusterPhyId); @@ -105,6 +113,10 @@ public class HealthStateServiceImpl implements HealthStateService { state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_CONNECTOR)); state = Math.max(state, metrics.getMetric(CLUSTER_METRIC_HEALTH_STATE_MIRROR_MAKER)); + if (isKafkaClusterDown(clusterPhyId)) { + state = Float.valueOf(HealthStateEnum.DEAD.getDimension()); + } + metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED, passed); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL, total); metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_STATE, state); @@ -531,4 +543,16 @@ public class HealthStateServiceImpl implements HealthStateService { return existNotPassed? HealthStateEnum.MEDIUM: HealthStateEnum.GOOD; } + + private boolean isKafkaClusterDown(Long clusterPhyId) { + ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(clusterPhyId); + KafkaController kafkaController = kafkaControllerService.getKafkaControllerFromDB(clusterPhyId); + if (kafkaController != null && !kafkaController.alive()) { + return true; + } else if ((System.currentTimeMillis() - clusterPhy.getCreateTime().getTime() >= 5 * 60 * 1000) && kafkaController == null) { + // 集群接入时间是在近5分钟内,同时kafkaController信息不存在,则设置为down + return true; + } + return false; + } } diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/MultiClusterPhyController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/MultiClusterPhyController.java index aa9bf781..c93ec94f 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/MultiClusterPhyController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/MultiClusterPhyController.java @@ -4,6 +4,7 @@ import com.xiaojukeji.know.streaming.km.biz.cluster.MultiClusterPhyManager; import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.MultiClusterDashboardDTO; 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.vo.cluster.ClusterPhyBaseVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhysHealthStateVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhysStateVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyDashboardVO; @@ -37,10 +38,17 @@ public class MultiClusterPhyController { @ApiOperation(value = "多物理集群-大盘", notes = "") @PostMapping(value = "physical-clusters/dashboard") @ResponseBody - public PaginationResult getClusterPhyBasic(@RequestBody @Validated MultiClusterDashboardDTO dto) { + public PaginationResult getClusterPhyDashboard(@RequestBody @Validated MultiClusterDashboardDTO dto) { return multiClusterPhyManager.getClusterPhysDashboard(dto); } + @ApiOperation(value = "多物理集群-基本信息", notes = "") + @GetMapping(value = "physical-clusters/basic") + @ResponseBody + public Result> getClusterPhyBasic() { + return multiClusterPhyManager.getClusterPhysBasic(); + } + @ApiOperation(value = "多物理集群-状态", notes = "") @GetMapping(value = "physical-clusters/state") @ResponseBody From 235c0ed30e3239191257c48b6242afc152810357 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:48:31 +0800 Subject: [PATCH 111/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-MM2?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E5=AE=9E=E4=BD=93=E7=B1=BB?= =?UTF-8?q?(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClusterMirrorMakersOverviewDTO.java | 12 ++++ .../bean/dto/connect/ClusterConnectorDTO.java | 4 +- .../connect/connector/ConnectorCreateDTO.java | 9 ++- .../connect/mm2/MirrorMaker2ActionDTO.java | 15 ++++ .../connect/mm2/MirrorMaker2DeleteDTO.java | 14 ++++ .../dto/connect/mm2/MirrorMakerCreateDTO.java | 69 +++++++++++++++++++ .../metrices/mm2/MetricsMirrorMakersDTO.java | 23 +++++++ .../bean/entity/connect/ConnectWorker.java | 1 - .../entity/param/connect/ConnectorParam.java | 7 +- .../mm2/ClusterMirrorMakerOverviewVO.java | 52 ++++++++++++++ .../cluster/mm2/MirrorMakerBaseStateVO.java | 25 +++++++ .../vo/cluster/mm2/MirrorMakerBasicVO.java | 16 +++++ .../vo/cluster/mm2/MirrorMakerStateVO.java | 34 +++++++++ .../km/common/constant/MsgConstant.java | 7 ++ .../connect/KafkaConnectConstant.java | 17 +++++ .../km/common/utils/MirrorMakerUtil.java | 11 +++ .../know/streaming/km/common/utils/Tuple.java | 9 ++- 17 files changed, 317 insertions(+), 8 deletions(-) create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterMirrorMakersOverviewDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2ActionDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2DeleteDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMakerCreateDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/mm2/MetricsMirrorMakersDTO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/ClusterMirrorMakerOverviewVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBaseStateVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBasicVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerStateVO.java create mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/MirrorMakerUtil.java diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterMirrorMakersOverviewDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterMirrorMakersOverviewDTO.java new file mode 100644 index 00000000..c109ebba --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterMirrorMakersOverviewDTO.java @@ -0,0 +1,12 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.cluster; + +import lombok.Data; + + +/** + * @author zengqiao + * @date 22/12/12 + */ +@Data +public class ClusterMirrorMakersOverviewDTO extends ClusterConnectorsOverviewDTO { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java index 71cfcce8..bbc84ccf 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/ClusterConnectorDTO.java @@ -19,11 +19,11 @@ import javax.validation.constraints.NotNull; public class ClusterConnectorDTO extends BaseDTO { @NotNull(message = "connectClusterId不允许为空") @ApiModelProperty(value = "Connector集群ID", example = "1") - private Long connectClusterId; + protected Long connectClusterId; @NotBlank(message = "name不允许为空串") @ApiModelProperty(value = "Connector名称", example = "know-streaming-connector") - private String connectorName; + protected String connectorName; public ClusterConnectorDTO(Long connectClusterId, String connectorName) { this.connectClusterId = connectClusterId; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java index a2272118..46639f0e 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorCreateDTO.java @@ -4,6 +4,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnector import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.NoArgsConstructor; import javax.validation.constraints.NotNull; import java.util.Properties; @@ -13,9 +14,15 @@ import java.util.Properties; * @date 2022-10-17 */ @Data +@NoArgsConstructor @ApiModel(description = "创建Connector") public class ConnectorCreateDTO extends ClusterConnectorDTO { @NotNull(message = "configs不允许为空") @ApiModelProperty(value = "配置", example = "") - private Properties configs; + protected Properties configs; + + public ConnectorCreateDTO(Long connectClusterId, String connectorName, Properties configs) { + super(connectClusterId, connectorName); + this.configs = configs; + } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2ActionDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2ActionDTO.java new file mode 100644 index 00000000..1d06c6af --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2ActionDTO.java @@ -0,0 +1,15 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorActionDTO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + + +/** + * @author zengqiao + * @date 2022-12-12 + */ +@Data +@ApiModel(description = "操作MM2") +public class MirrorMaker2ActionDTO extends ConnectorActionDTO { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2DeleteDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2DeleteDTO.java new file mode 100644 index 00000000..9b219cfc --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMaker2DeleteDTO.java @@ -0,0 +1,14 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorDeleteDTO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +/** + * @author zengqiao + * @date 2022-12-12 + */ +@Data +@ApiModel(description = "删除MM2") +public class MirrorMaker2DeleteDTO extends ConnectorDeleteDTO { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMakerCreateDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMakerCreateDTO.java new file mode 100644 index 00000000..fa9867ec --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/mm2/MirrorMakerCreateDTO.java @@ -0,0 +1,69 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.kafka.clients.CommonClientConfigs; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.Properties; + +/** + * @author zengqiao + * @date 2022-12-12 + */ +@Data +@ApiModel(description = "创建MM2") +public class MirrorMakerCreateDTO extends ConnectorCreateDTO { + @NotNull(message = "sourceKafkaClusterId不允许为空") + @ApiModelProperty(value = "源Kafka集群ID", example = "") + private Long sourceKafkaClusterId; + + @Valid + @ApiModelProperty(value = "heartbeat-connector的信息", example = "") + private Properties heartbeatConnectorConfigs; + + @Valid + @ApiModelProperty(value = "checkpoint-connector的信息", example = "") + private Properties checkpointConnectorConfigs; + + public void unifyData(Long sourceKafkaClusterId, String sourceBootstrapServers, Properties sourceKafkaProps, + Long targetKafkaClusterId, String targetBootstrapServers, Properties targetKafkaProps) { + if (sourceKafkaProps == null) { + sourceKafkaProps = new Properties(); + } + + if (targetKafkaProps == null) { + targetKafkaProps = new Properties(); + } + + this.unifyData(this.configs, sourceKafkaClusterId, sourceBootstrapServers, sourceKafkaProps, targetKafkaClusterId, targetBootstrapServers, targetKafkaProps); + + if (heartbeatConnectorConfigs != null) { + this.unifyData(this.heartbeatConnectorConfigs, sourceKafkaClusterId, sourceBootstrapServers, sourceKafkaProps, targetKafkaClusterId, targetBootstrapServers, targetKafkaProps); + } + + if (checkpointConnectorConfigs != null) { + this.unifyData(this.checkpointConnectorConfigs, sourceKafkaClusterId, sourceBootstrapServers, sourceKafkaProps, targetKafkaClusterId, targetBootstrapServers, targetKafkaProps); + } + } + + private void unifyData(Properties dataConfig, + Long sourceKafkaClusterId, String sourceBootstrapServers, Properties sourceKafkaProps, + Long targetKafkaClusterId, String targetBootstrapServers, Properties targetKafkaProps) { + dataConfig.put(KafkaConnectConstant.MIRROR_MAKER_SOURCE_CLUSTER_ALIAS_FIELD_NAME, sourceKafkaClusterId); + dataConfig.put(KafkaConnectConstant.MIRROR_MAKER_SOURCE_CLUSTER_FIELD_NAME + "." + CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, sourceBootstrapServers); + for (Object configKey: sourceKafkaProps.keySet()) { + dataConfig.put(KafkaConnectConstant.MIRROR_MAKER_SOURCE_CLUSTER_FIELD_NAME + "." + configKey, sourceKafkaProps.getProperty((String) configKey)); + } + + dataConfig.put(KafkaConnectConstant.MIRROR_MAKER_TARGET_CLUSTER_ALIAS_FIELD_NAME, targetKafkaClusterId); + dataConfig.put(KafkaConnectConstant.MIRROR_MAKER_TARGET_CLUSTER_FIELD_NAME + "." + CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, targetBootstrapServers); + for (Object configKey: targetKafkaProps.keySet()) { + dataConfig.put(KafkaConnectConstant.MIRROR_MAKER_TARGET_CLUSTER_FIELD_NAME + "." + configKey, targetKafkaProps.getProperty((String) configKey)); + } + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/mm2/MetricsMirrorMakersDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/mm2/MetricsMirrorMakersDTO.java new file mode 100644 index 00000000..512832f9 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/metrices/mm2/MetricsMirrorMakersDTO.java @@ -0,0 +1,23 @@ +package com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author didi + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(description = "MirrorMaker指标查询信息") +public class MetricsMirrorMakersDTO extends MetricDTO { + @ApiModelProperty("MirrorMaker的SourceConnect列表") + private List connectorNameList; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java index 69a4f747..703a7bc7 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/connect/ConnectWorker.java @@ -7,7 +7,6 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; -import java.net.URI; @Data @NoArgsConstructor diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java index 0f5b0a75..fb923a0c 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/param/connect/ConnectorParam.java @@ -1,7 +1,5 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.param.connect; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterParam; -import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam; import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ConnectClusterParam; import lombok.AllArgsConstructor; import lombok.Data; @@ -18,9 +16,12 @@ public class ConnectorParam extends ConnectClusterParam { private String connectorName; - public ConnectorParam(Long connectClusterId, String connectorName) { + private String connectorType; + + public ConnectorParam(Long connectClusterId, String connectorName, String connectorType) { super(connectClusterId); this.connectorName = connectorName; + this.connectorType = connectorType; } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/ClusterMirrorMakerOverviewVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/ClusterMirrorMakerOverviewVO.java new file mode 100644 index 00000000..27a3bd94 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/ClusterMirrorMakerOverviewVO.java @@ -0,0 +1,52 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 集群MM2信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "MM2概览信息") +public class ClusterMirrorMakerOverviewVO extends MirrorMakerBasicVO { + @ApiModelProperty(value = "源Kafka集群Id", example = "1") + private Long sourceKafkaClusterId; + + @ApiModelProperty(value = "源Kafka集群名称", example = "aaa") + private String sourceKafkaClusterName; + + @ApiModelProperty(value = "目标Kafka集群Id", example = "1") + private Long destKafkaClusterId; + + @ApiModelProperty(value = "目标Kafka集群名称", example = "aaa") + private String destKafkaClusterName; + + /** + * @see org.apache.kafka.connect.runtime.AbstractStatus.State + */ + @ApiModelProperty(value = "状态", example = "RUNNING") + private String state; + + @ApiModelProperty(value = "Task数", example = "100") + private Integer taskCount; + + @ApiModelProperty(value = "心跳检测connector", example = "heartbeatConnector") + private String heartbeatConnector; + + @ApiModelProperty(value = "进度确认connector", example = "checkpointConnector") + private String checkpointConnector; + + + @ApiModelProperty(value = "多个指标的当前值, 包括健康分/LogSize等") + private BaseMetrics latestMetrics; + + @ApiModelProperty(value = "多个指标的历史曲线值,包括LogSize/BytesIn等") + private List metricLines; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBaseStateVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBaseStateVO.java new file mode 100644 index 00000000..04aed035 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBaseStateVO.java @@ -0,0 +1,25 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 集群MM2状态信息 + * @author fengqiongfeng + * @date 22/12/29 + */ +@Data +@ApiModel(description = "集群MM2状态信息") +public class MirrorMakerBaseStateVO extends BaseVO { + + @ApiModelProperty(value = "worker数", example = "1") + private Integer workerCount; + + @ApiModelProperty(value = "总Task数", example = "1") + private Integer totalTaskCount; + + @ApiModelProperty(value = "存活Task数", example = "1") + private Integer aliveTaskCount; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBasicVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBasicVO.java new file mode 100644 index 00000000..177d76eb --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerBasicVO.java @@ -0,0 +1,16 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicVO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + + +/** + * 集群MM2信息 + * @author zengqiao + * @date 22/02/23 + */ +@Data +@ApiModel(description = "MM2基本信息") +public class MirrorMakerBasicVO extends ConnectorBasicVO { +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerStateVO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerStateVO.java new file mode 100644 index 00000000..e0a1782d --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/vo/cluster/mm2/MirrorMakerStateVO.java @@ -0,0 +1,34 @@ +package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.vo.BaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 集群MM2状态信息 + * @author zengqiao + * @date 22/12/12 + */ +@Data +@ApiModel(description = "集群MM2状态信息") +public class MirrorMakerStateVO extends BaseVO { + + @ApiModelProperty(value = "MM2数", example = "1") + private Integer mirrorMakerCount; + + @ApiModelProperty(value = "worker数", example = "1") + private Integer workerCount; + + @ApiModelProperty(value = "总Connector数", example = "1") + private Integer totalConnectorCount; + + @ApiModelProperty(value = "存活Connector数", example = "1") + private Integer aliveConnectorCount; + + @ApiModelProperty(value = "总Task数", example = "1") + private Integer totalTaskCount; + + @ApiModelProperty(value = "存活Task数", example = "1") + private Integer aliveTaskCount; +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java index 768ebddf..2f510a84 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/MsgConstant.java @@ -110,4 +110,11 @@ public class MsgConstant { public static String getConnectorBizStr(Long clusterPhyId, String topicName) { return String.format("Connect集群ID:[%d] Connector名称:[%s]", clusterPhyId, topicName); } + + + /**************************************************** Connector ****************************************************/ + + public static String getConnectorNotExist(Long connectClusterId, String connectorName) { + return String.format("Connect集群ID:[%d] Connector名称:[%s] 不存在", connectClusterId, connectorName); + } } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java index 5746bfd9..63612874 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/connect/KafkaConnectConstant.java @@ -10,6 +10,23 @@ public class KafkaConnectConstant { public static final String CONNECTOR_TOPICS_FILED_NAME = "topics"; public static final String CONNECTOR_TOPICS_FILED_ERROR_VALUE = "know-streaming-connect-illegal-value"; + public static final String MIRROR_MAKER_TOPIC_PARTITION_PATTERN = "kafka.connect.mirror:type=MirrorSourceConnector,target=*,topic=*,partition=*"; + + public static final String MIRROR_MAKER_SOURCE_CONNECTOR_TYPE = "org.apache.kafka.connect.mirror.MirrorSourceConnector"; + + public static final String MIRROR_MAKER_HEARTBEAT_CONNECTOR_TYPE = "org.apache.kafka.connect.mirror.MirrorHeartbeatConnector"; + + public static final String MIRROR_MAKER_CHECKPOINT_CONNECTOR_TYPE = "org.apache.kafka.connect.mirror.MirrorCheckpointConnector"; + + public static final String MIRROR_MAKER_TARGET_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME = "target.cluster.bootstrap.servers"; + public static final String MIRROR_MAKER_TARGET_CLUSTER_ALIAS_FIELD_NAME = "target.cluster.alias"; + public static final String MIRROR_MAKER_TARGET_CLUSTER_FIELD_NAME = "target.cluster"; + + public static final String MIRROR_MAKER_SOURCE_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME = "source.cluster.bootstrap.servers"; + public static final String MIRROR_MAKER_SOURCE_CLUSTER_ALIAS_FIELD_NAME = "source.cluster.alias"; + public static final String MIRROR_MAKER_SOURCE_CLUSTER_FIELD_NAME = "source.cluster"; + + public static final String MIRROR_MAKER_NAME_FIELD_NAME = "name"; private KafkaConnectConstant() { } } \ No newline at end of file diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/MirrorMakerUtil.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/MirrorMakerUtil.java new file mode 100644 index 00000000..d8145832 --- /dev/null +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/MirrorMakerUtil.java @@ -0,0 +1,11 @@ +package com.xiaojukeji.know.streaming.km.common.utils; + +public class MirrorMakerUtil { + public static String genCheckpointName(String sourceName) { + return sourceName == null? "-checkpoint": sourceName + "-checkpoint"; + } + + public static String genHeartbeatName(String sourceName) { + return sourceName == null? "-heartbeat": sourceName + "-heartbeat"; + } +} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/Tuple.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/Tuple.java index 405845a1..c4984604 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/Tuple.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/utils/Tuple.java @@ -12,7 +12,6 @@ import lombok.Data; @JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" }) @Data public class Tuple { - private T v1; private V v2; @@ -58,4 +57,12 @@ public class Tuple { result = 31 * result + (v2 != null ? v2.hashCode() : 0); return result; } + + @Override + public String toString() { + return "Tuple{" + + "v1=" + v1 + + ", v2=" + v2 + + '}'; + } } From add2af4f3fda477803e9100dd053fee23c76d6d2 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:52:27 +0800 Subject: [PATCH 112/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-MM2?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E6=9C=8D=E5=8A=A1=E7=B1=BB?= =?UTF-8?q?(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/VersionControlManagerImpl.java | 9 ++ .../impl/ConnectClusterServiceImpl.java | 4 +- .../connect/connector/ConnectorService.java | 3 + .../connector/impl/ConnectorServiceImpl.java | 104 +++++++++++++++++- .../version/BaseConnectorMetricService.java | 38 ------- .../kafka/ClusterMetricVersionItems.java | 9 ++ 6 files changed, 126 insertions(+), 41 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java index d87e1bcc..740974d7 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/version/impl/VersionControlManagerImpl.java @@ -34,6 +34,7 @@ import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafk import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ClusterMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.GroupMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.TopicMetricVersionItems.*; +import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.connect.MirrorMakerMetricVersionItems.*; import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.kafka.ZookeeperMetricVersionItems.*; @Service @@ -114,6 +115,14 @@ public class VersionControlManagerImpl implements VersionControlManager { defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_KAFKA_ZK_DISCONNECTS_PER_SEC, true)); defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_KAFKA_ZK_SYNC_CONNECTS_PER_SEC, true)); defaultMetrics.add(new UserMetricConfig(METRIC_ZOOKEEPER.getCode(), ZOOKEEPER_METRIC_KAFKA_ZK_REQUEST_LATENCY_99TH, true)); + + // mm2 + defaultMetrics.add(new UserMetricConfig(METRIC_CONNECT_MIRROR_MAKER.getCode(), MIRROR_MAKER_METRIC_BYTE_COUNT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_CONNECT_MIRROR_MAKER.getCode(), MIRROR_MAKER_METRIC_BYTE_RATE, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_CONNECT_MIRROR_MAKER.getCode(), MIRROR_MAKER_METRIC_RECORD_AGE_MS_MAX, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_CONNECT_MIRROR_MAKER.getCode(), MIRROR_MAKER_METRIC_RECORD_COUNT, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_CONNECT_MIRROR_MAKER.getCode(), MIRROR_MAKER_METRIC_RECORD_RATE, true)); + defaultMetrics.add(new UserMetricConfig(METRIC_CONNECT_MIRROR_MAKER.getCode(), MIRROR_MAKER_METRIC_REPLICATION_LATENCY_MS_MAX, true)); } @Autowired diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java index 6597b8ec..030b78ad 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/cluster/impl/ConnectClusterServiceImpl.java @@ -69,8 +69,8 @@ public class ConnectClusterServiceImpl implements ConnectClusterService { if (ValidateUtils.isBlank(oldPO.getVersion())) { oldPO.setVersion(KafkaConstant.DEFAULT_CONNECT_VERSION); } - if (ValidateUtils.isBlank(oldPO.getClusterUrl())) { - oldPO.setClusterUrl(metadata.getMemberLeaderUrl()); + if (!ValidateUtils.isBlank(clusterUrl)) { + oldPO.setClusterUrl(clusterUrl); } connectClusterDAO.updateById(oldPO); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java index 076f5c11..220e4e89 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/ConnectorService.java @@ -1,5 +1,6 @@ package com.xiaojukeji.know.streaming.km.core.service.connect.connector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnector; import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorStateInfo; @@ -56,4 +57,6 @@ public interface ConnectorService { ConnectorPO getConnectorFromDB(Long connectClusterId, String connectorName); ConnectorTypeEnum getConnectorType(Long connectClusterId, String connectorName); + + void completeMirrorMakerInfo(ConnectCluster connectCluster, List connectorList); } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java index 9d2136a9..c042276d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/connect/connector/impl/ConnectorServiceImpl.java @@ -13,6 +13,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus; import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; import com.xiaojukeji.know.streaming.km.common.component.RestTool; import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; import com.xiaojukeji.know.streaming.km.common.converter.ConnectConverter; import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectorTypeEnum; import com.xiaojukeji.know.streaming.km.common.enums.operaterecord.ModuleEnum; @@ -33,7 +34,10 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import java.util.*; +import java.util.stream.Collectors; +import static com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant.MIRROR_MAKER_SOURCE_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME; +import static com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant.MIRROR_MAKER_TARGET_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME; import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionItemTypeEnum.SERVICE_OP_CONNECT_CONNECTOR; @Service @@ -79,7 +83,7 @@ public class ConnectorServiceImpl extends BaseVersionControlService implements C // 构造参数 Properties props = new Properties(); - props.put("name", connectorName); + props.put(KafkaConnectConstant.MIRROR_MAKER_NAME_FIELD_NAME, connectorName); props.put("config", configs); ConnectorInfo connectorInfo = restTool.postObjectWithJsonContent( @@ -477,6 +481,45 @@ public class ConnectorServiceImpl extends BaseVersionControlService implements C return connectorType; } + @Override + public void completeMirrorMakerInfo(ConnectCluster connectCluster, List connectorList) { + List sourceConnectorList = connectorList.stream().filter(elem -> elem.getConnectorClassName().equals(KafkaConnectConstant.MIRROR_MAKER_SOURCE_CONNECTOR_TYPE)).collect(Collectors.toList()); + if (sourceConnectorList.isEmpty()) { + return; + } + + List heartBeatConnectorList = connectorList.stream().filter(elem -> elem.getConnectorClassName().equals(KafkaConnectConstant.MIRROR_MAKER_HEARTBEAT_CONNECTOR_TYPE)).collect(Collectors.toList()); + List checkpointConnectorList = connectorList.stream().filter(elem -> elem.getConnectorClassName().equals(KafkaConnectConstant.MIRROR_MAKER_CHECKPOINT_CONNECTOR_TYPE)).collect(Collectors.toList()); + + Map heartbeatMap = this.buildMirrorMakerMap(connectCluster, heartBeatConnectorList); + Map checkpointMap = this.buildMirrorMakerMap(connectCluster, checkpointConnectorList); + + for (KSConnector sourceConnector : sourceConnectorList) { + Result ret = this.getConnectorInfoFromCluster(connectCluster, sourceConnector.getConnectorName()); + + if (!ret.hasData()) { + LOGGER.error( + "method=completeMirrorMakerInfo||connectClusterId={}||connectorName={}||get connectorInfo fail!", + connectCluster.getId(), sourceConnector.getConnectorName() + ); + continue; + } + KSConnectorInfo ksConnectorInfo = ret.getData(); + String targetServers = ksConnectorInfo.getConfig().get(MIRROR_MAKER_TARGET_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME); + String sourceServers = ksConnectorInfo.getConfig().get(MIRROR_MAKER_SOURCE_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME); + + if (ValidateUtils.anyBlank(targetServers, sourceServers)) { + continue; + } + + String[] targetBrokerList = getBrokerList(targetServers); + String[] sourceBrokerList = getBrokerList(sourceServers); + sourceConnector.setHeartbeatConnectorName(this.findBindConnector(targetBrokerList, sourceBrokerList, heartbeatMap)); + sourceConnector.setCheckpointConnectorName(this.findBindConnector(targetBrokerList, sourceBrokerList, checkpointMap)); + } + + } + /**************************************************** private method ****************************************************/ private int deleteConnectorInDB(Long connectClusterId, String connectorName) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); @@ -578,4 +621,63 @@ public class ConnectorServiceImpl extends BaseVersionControlService implements C ); } } + + private Map buildMirrorMakerMap(ConnectCluster connectCluster, List ksConnectorList) { + Map bindMap = new HashMap<>(); + + for (KSConnector ksConnector : ksConnectorList) { + Result ret = this.getConnectorInfoFromCluster(connectCluster, ksConnector.getConnectorName()); + + if (!ret.hasData()) { + LOGGER.error( + "method=buildMirrorMakerMap||connectClusterId={}||connectorName={}||get connectorInfo fail!", + connectCluster.getId(), ksConnector.getConnectorName() + ); + continue; + } + + KSConnectorInfo ksConnectorInfo = ret.getData(); + String targetServers = ksConnectorInfo.getConfig().get(MIRROR_MAKER_TARGET_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME); + String sourceServers = ksConnectorInfo.getConfig().get(MIRROR_MAKER_SOURCE_CLUSTER_BOOTSTRAP_SERVERS_FIELD_NAME); + + if (ValidateUtils.anyBlank(targetServers, sourceServers)) { + continue; + } + + String[] targetBrokerList = getBrokerList(targetServers); + String[] sourceBrokerList = getBrokerList(sourceServers); + for (String targetBroker : targetBrokerList) { + for (String sourceBroker : sourceBrokerList) { + bindMap.put(targetBroker + "@" + sourceBroker, ksConnector.getConnectorName()); + } + } + + } + return bindMap; + } + + private String findBindConnector(String[] targetBrokerList, String[] sourceBrokerList, Map connectorBindMap) { + for (String targetBroker : targetBrokerList) { + for (String sourceBroker : sourceBrokerList) { + String connectorName = connectorBindMap.get(targetBroker + "@" + sourceBroker); + if (connectorName != null) { + return connectorName; + } + } + } + return ""; + } + + private String[] getBrokerList(String str) { + if (ValidateUtils.isBlank(str)) { + return new String[0]; + } + if (str.contains(";")) { + return str.split(";"); + } + if (str.contains(",")) { + return str.split(","); + } + return new String[]{str}; + } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java index 5efc4438..febfdcf4 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/BaseConnectorMetricService.java @@ -1,17 +1,8 @@ package com.xiaojukeji.know.streaming.km.core.service.version; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.common.bean.entity.search.SearchQuery; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; -import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.point.MetricPointVO; -import org.springframework.util.CollectionUtils; - import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; /** @@ -19,10 +10,7 @@ import java.util.stream.Collectors; * @date 2022/11/9 */ public abstract class BaseConnectorMetricService extends BaseConnectorVersionControlService{ - private static final ILog LOGGER = LogFactory.getLog(BaseMetricService.class); - private List metricNames = new ArrayList<>(); - private List metricFields = new ArrayList<>(); @PostConstruct public void init(){ @@ -32,7 +20,6 @@ public abstract class BaseConnectorMetricService extends BaseConnectorVersionCon protected void initMetricFieldAndNameList(){ metricNames = listVersionControlItems().stream().map(v -> v.getName()).collect(Collectors.toList()); - metricFields = listMetricPOFields(); } protected abstract List listMetricPOFields(); @@ -46,29 +33,4 @@ public abstract class BaseConnectorMetricService extends BaseConnectorVersionCon protected boolean isMetricName(String str){ return metricNames.contains(str); } - - /** - * 检查 str 是不是一个 fieldName - * @param str - */ - protected boolean isMetricField(String str){ - return metricFields.contains(str); - } - - protected void setQueryMetricFlag(SearchQuery query){ - if(null == query){return;} - - String fieldName = query.getQueryName(); - - query.setMetric(isMetricName(fieldName)); - query.setField(isMetricField(fieldName)); - } - - protected void setQueryMetricFlag(List matches){ - if(CollectionUtils.isEmpty(matches)){return;} - - for (SearchQuery match : matches){ - setQueryMetricFlag(match); - } - } } diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java index 5a72b38c..cefe8930 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/version/metrics/kafka/ClusterMetricVersionItems.java @@ -62,6 +62,15 @@ public class ClusterMetricVersionItems extends BaseMetricVersionMetric { public static final String CLUSTER_METRIC_HEALTH_CHECK_PASSED_CONNECTOR = "HealthCheckPassed_Connector"; public static final String CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CONNECTOR = "HealthCheckTotal_Connector"; + /** + * mm2健康指标 + */ + public static final String CLUSTER_METRIC_HEALTH_STATE_MIRROR_MAKER = "HealthState_MirrorMaker"; + public static final String CLUSTER_METRIC_HEALTH_CHECK_PASSED_MIRROR_MAKER = "HealthCheckPassed_MirrorMaker"; + public static final String CLUSTER_METRIC_HEALTH_CHECK_TOTAL_MIRROR_MAKER = "HealthCheckTotal_MirrorMaker"; + + + public static final String CLUSTER_METRIC_TOTAL_REQ_QUEUE_SIZE = "TotalRequestQueueSize"; public static final String CLUSTER_METRIC_TOTAL_RES_QUEUE_SIZE = "TotalResponseQueueSize"; public static final String CLUSTER_METRIC_EVENT_QUEUE_SIZE = "EventQueueSize"; From 78b2b8a45e80a65fc1c4fd426566a8b4a39fe5b0 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:53:12 +0800 Subject: [PATCH 113/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-MM2?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E4=B8=9A=E5=8A=A1=E7=B1=BB?= =?UTF-8?q?(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connect/connector/ConnectorManager.java | 1 + .../connector/impl/ConnectorManagerImpl.java | 30 +- .../biz/connect/mm2/MirrorMakerManager.java | 43 ++ .../mm2/impl/MirrorMakerManagerImpl.java | 632 ++++++++++++++++++ 4 files changed, 702 insertions(+), 4 deletions(-) create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/MirrorMakerManager.java create mode 100644 km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java index 0247a7d3..3752504d 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/ConnectorManager.java @@ -10,6 +10,7 @@ public interface ConnectorManager { Result updateConnectorConfig(Long connectClusterId, String connectorName, Properties configs, String operator); Result createConnector(ConnectorCreateDTO dto, String operator); + Result createConnector(ConnectorCreateDTO dto, String heartbeatName, String checkpointName, String operator); Result getConnectorStateVO(Long connectClusterId, String connectorName); } diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java index c28f310f..5800b26f 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/connector/impl/ConnectorManagerImpl.java @@ -1,7 +1,5 @@ package com.xiaojukeji.know.streaming.km.biz.connect.connector.impl; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.biz.connect.connector.ConnectorManager; import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; @@ -12,6 +10,7 @@ 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.po.connect.ConnectorPO; import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.connector.ConnectorStateVO; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; import com.xiaojukeji.know.streaming.km.core.service.connect.plugin.PluginService; import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; @@ -25,8 +24,6 @@ import java.util.stream.Collectors; @Service public class ConnectorManagerImpl implements ConnectorManager { - private static final ILog LOGGER = LogFactory.getLog(ConnectorManagerImpl.class); - @Autowired private PluginService pluginService; @@ -52,6 +49,8 @@ public class ConnectorManagerImpl implements ConnectorManager { @Override public Result createConnector(ConnectorCreateDTO dto, String operator) { + dto.getConfigs().put(KafkaConnectConstant.MIRROR_MAKER_NAME_FIELD_NAME, dto.getConnectorName()); + Result createResult = connectorService.createConnector(dto.getConnectClusterId(), dto.getConnectorName(), dto.getConfigs(), operator); if (createResult.failed()) { return Result.buildFromIgnoreData(createResult); @@ -66,6 +65,29 @@ public class ConnectorManagerImpl implements ConnectorManager { return Result.buildSuc(); } + @Override + public Result createConnector(ConnectorCreateDTO dto, String heartbeatName, String checkpointName, String operator) { + dto.getConfigs().put(KafkaConnectConstant.MIRROR_MAKER_NAME_FIELD_NAME, dto.getConnectorName()); + + Result createResult = connectorService.createConnector(dto.getConnectClusterId(), dto.getConnectorName(), dto.getConfigs(), operator); + if (createResult.failed()) { + return Result.buildFromIgnoreData(createResult); + } + + Result ksConnectorResult = connectorService.getAllConnectorInfoFromCluster(dto.getConnectClusterId(), dto.getConnectorName()); + if (ksConnectorResult.failed()) { + return Result.buildFromRSAndMsg(ResultStatus.SUCCESS, "创建成功,但是获取元信息失败,页面元信息会存在1分钟延迟"); + } + + KSConnector connector = ksConnectorResult.getData(); + connector.setCheckpointConnectorName(checkpointName); + connector.setHeartbeatConnectorName(heartbeatName); + + connectorService.addNewToDB(connector); + return Result.buildSuc(); + } + + @Override public Result getConnectorStateVO(Long connectClusterId, String connectorName) { ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, connectorName); diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/MirrorMakerManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/MirrorMakerManager.java new file mode 100644 index 00000000..6851ca5e --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/MirrorMakerManager.java @@ -0,0 +1,43 @@ +package com.xiaojukeji.know.streaming.km.biz.connect.mm2; + +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterMirrorMakersOverviewDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2.MirrorMakerCreateDTO; +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.vo.cluster.mm2.ClusterMirrorMakerOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerBaseStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin.ConnectConfigInfosVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task.KCTaskOverviewVO; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * @author wyb + * @date 2022/12/26 + */ +public interface MirrorMakerManager { + Result createMirrorMaker(MirrorMakerCreateDTO dto, String operator); + + Result deleteMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator); + + Result modifyMirrorMakerConfig(MirrorMakerCreateDTO dto, String operator); + + Result restartMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator); + Result stopMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator); + Result resumeMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator); + + Result getMirrorMakerStateVO(Long clusterPhyId); + + PaginationResult getClusterMirrorMakersOverview(Long clusterPhyId, ClusterMirrorMakersOverviewDTO dto); + + + Result getMirrorMakerState(Long connectId, String connectName); + + Result>> getTaskOverview(Long connectClusterId, String connectorName); + Result> getMM2Configs(Long connectClusterId, String connectorName); + + Result> validateConnectors(MirrorMakerCreateDTO dto); +} diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java new file mode 100644 index 00000000..7d7351f2 --- /dev/null +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java @@ -0,0 +1,632 @@ +package com.xiaojukeji.know.streaming.km.biz.connect.mm2.impl; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.biz.connect.connector.ConnectorManager; +import com.xiaojukeji.know.streaming.km.biz.connect.mm2.MirrorMakerManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterMirrorMakersOverviewDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2.MirrorMakerCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.mm2.MetricsMirrorMakersDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectCluster; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.ConnectWorker; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.WorkerConnector; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; +import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.connector.KSConnectorInfo; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +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; +import com.xiaojukeji.know.streaming.km.common.bean.po.connect.ConnectorPO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.ClusterMirrorMakerOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerBaseStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.plugin.ConnectConfigInfosVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task.KCTaskOverviewVO; +import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant; +import com.xiaojukeji.know.streaming.km.common.utils.*; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.common.utils.MirrorMakerUtil; +import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerMetricService; +import com.xiaojukeji.know.streaming.km.core.service.connect.plugin.PluginService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.worker.WorkerService; +import com.xiaojukeji.know.streaming.km.persistence.cache.LoadedClusterPhyCache; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.apache.kafka.connect.runtime.AbstractStatus.State.RUNNING; +import static com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant.*; + + +/** + * @author wyb + * @date 2022/12/26 + */ +@Service +public class MirrorMakerManagerImpl implements MirrorMakerManager { + private static final ILog LOGGER = LogFactory.getLog(MirrorMakerManagerImpl.class); + + @Autowired + private ConnectorService connectorService; + + @Autowired + private WorkerConnectorService workerConnectorService; + + @Autowired + private WorkerService workerService; + + @Autowired + private ConnectorManager connectorManager; + + @Autowired + private ClusterPhyService clusterPhyService; + + @Autowired + private MirrorMakerMetricService mirrorMakerMetricService; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private PluginService pluginService; + + @Override + public Result createMirrorMaker(MirrorMakerCreateDTO dto, String operator) { + // 检查基本参数 + Result rv = this.checkCreateMirrorMakerParamAndUnifyData(dto); + if (rv.failed()) { + return rv; + } + + // 创建MirrorSourceConnector + Result sourceConnectResult = connectorManager.createConnector( + dto, + dto.getCheckpointConnectorConfigs() != null? MirrorMakerUtil.genCheckpointName(dto.getConnectorName()): "", + dto.getHeartbeatConnectorConfigs() != null? MirrorMakerUtil.genHeartbeatName(dto.getConnectorName()): "", + operator + ); + if (sourceConnectResult.failed()) { + // 创建失败, 直接返回 + return Result.buildFromIgnoreData(sourceConnectResult); + } + + // 创建 checkpoint 任务 + Result checkpointResult = Result.buildSuc(); + if (dto.getCheckpointConnectorConfigs() != null) { + checkpointResult = connectorManager.createConnector( + new ConnectorCreateDTO(dto.getConnectClusterId(), MirrorMakerUtil.genCheckpointName(dto.getConnectorName()), dto.getCheckpointConnectorConfigs()), + operator + ); + } + + // 创建 heartbeat 任务 + Result heartbeatResult = Result.buildSuc(); + if (dto.getHeartbeatConnectorConfigs() != null) { + heartbeatResult = connectorManager.createConnector( + new ConnectorCreateDTO(dto.getConnectClusterId(), MirrorMakerUtil.genHeartbeatName(dto.getConnectorName()), dto.getHeartbeatConnectorConfigs()), + operator + ); + } + + // 全都成功 + if (checkpointResult.successful() && checkpointResult.successful()) { + return Result.buildSuc(); + } else if (checkpointResult.failed() && checkpointResult.failed()) { + return Result.buildFromRSAndMsg( + ResultStatus.KAFKA_CONNECTOR_OPERATE_FAILED, + String.format("创建 checkpoint & heartbeat 失败.\n失败信息分别为:%s\n\n%s", checkpointResult.getMessage(), heartbeatResult.getMessage()) + ); + } else if (checkpointResult.failed()) { + return Result.buildFromRSAndMsg( + ResultStatus.KAFKA_CONNECTOR_OPERATE_FAILED, + String.format("创建 checkpoint 失败.\n失败信息分别为:%s", checkpointResult.getMessage()) + ); + } else{ + return Result.buildFromRSAndMsg( + ResultStatus.KAFKA_CONNECTOR_OPERATE_FAILED, + String.format("创建 heartbeat 失败.\n失败信息分别为:%s", heartbeatResult.getMessage()) + ); + } + } + + @Override + public Result deleteMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, sourceConnectorName); + if (connectorPO == null) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectorNotExist(connectClusterId, sourceConnectorName)); + } + + Result rv = Result.buildSuc(); + if (!ValidateUtils.isBlank(connectorPO.getCheckpointConnectorName())) { + rv = connectorService.deleteConnector(connectClusterId, connectorPO.getCheckpointConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + if (!ValidateUtils.isBlank(connectorPO.getHeartbeatConnectorName())) { + rv = connectorService.deleteConnector(connectClusterId, connectorPO.getHeartbeatConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + return connectorService.deleteConnector(connectClusterId, sourceConnectorName, operator); + } + + @Override + public Result modifyMirrorMakerConfig(MirrorMakerCreateDTO dto, String operator) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(dto.getConnectClusterId(), dto.getConnectorName()); + if (connectorPO == null) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectorNotExist(dto.getConnectClusterId(), dto.getConnectorName())); + } + + Result rv = Result.buildSuc(); + if (!ValidateUtils.isBlank(connectorPO.getCheckpointConnectorName()) && dto.getCheckpointConnectorConfigs() != null) { + rv = connectorService.updateConnectorConfig(dto.getConnectClusterId(), connectorPO.getCheckpointConnectorName(), dto.getCheckpointConnectorConfigs(), operator); + } + if (rv.failed()) { + return rv; + } + + if (!ValidateUtils.isBlank(connectorPO.getHeartbeatConnectorName()) && dto.getHeartbeatConnectorConfigs() != null) { + rv = connectorService.updateConnectorConfig(dto.getConnectClusterId(), connectorPO.getHeartbeatConnectorName(), dto.getHeartbeatConnectorConfigs(), operator); + } + if (rv.failed()) { + return rv; + } + + return connectorService.updateConnectorConfig(dto.getConnectClusterId(), dto.getConnectorName(), dto.getConfigs(), operator); + } + + @Override + public Result restartMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, sourceConnectorName); + if (connectorPO == null) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectorNotExist(connectClusterId, sourceConnectorName)); + } + + Result rv = Result.buildSuc(); + if (!ValidateUtils.isBlank(connectorPO.getCheckpointConnectorName())) { + rv = connectorService.restartConnector(connectClusterId, connectorPO.getCheckpointConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + if (!ValidateUtils.isBlank(connectorPO.getHeartbeatConnectorName())) { + rv = connectorService.restartConnector(connectClusterId, connectorPO.getHeartbeatConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + return connectorService.restartConnector(connectClusterId, sourceConnectorName, operator); + } + + @Override + public Result stopMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, sourceConnectorName); + if (connectorPO == null) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectorNotExist(connectClusterId, sourceConnectorName)); + } + + Result rv = Result.buildSuc(); + if (!ValidateUtils.isBlank(connectorPO.getCheckpointConnectorName())) { + rv = connectorService.stopConnector(connectClusterId, connectorPO.getCheckpointConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + if (!ValidateUtils.isBlank(connectorPO.getHeartbeatConnectorName())) { + rv = connectorService.stopConnector(connectClusterId, connectorPO.getHeartbeatConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + return connectorService.stopConnector(connectClusterId, sourceConnectorName, operator); + } + + @Override + public Result resumeMirrorMaker(Long connectClusterId, String sourceConnectorName, String operator) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, sourceConnectorName); + if (connectorPO == null) { + return Result.buildFromRSAndMsg(ResultStatus.NOT_EXIST, MsgConstant.getConnectorNotExist(connectClusterId, sourceConnectorName)); + } + + Result rv = Result.buildSuc(); + if (!ValidateUtils.isBlank(connectorPO.getCheckpointConnectorName())) { + rv = connectorService.resumeConnector(connectClusterId, connectorPO.getCheckpointConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + if (!ValidateUtils.isBlank(connectorPO.getHeartbeatConnectorName())) { + rv = connectorService.resumeConnector(connectClusterId, connectorPO.getHeartbeatConnectorName(), operator); + } + if (rv.failed()) { + return rv; + } + + return connectorService.resumeConnector(connectClusterId, sourceConnectorName, operator); + } + + @Override + public Result getMirrorMakerStateVO(Long clusterPhyId) { + List connectorPOList = connectorService.listByKafkaClusterIdFromDB(clusterPhyId); + List workerConnectorList = workerConnectorService.listByKafkaClusterIdFromDB(clusterPhyId); + List workerList = workerService.listByKafkaClusterIdFromDB(clusterPhyId); + + return Result.buildSuc(convert2MirrorMakerStateVO(connectorPOList, workerConnectorList, workerList)); + } + + @Override + public PaginationResult getClusterMirrorMakersOverview(Long clusterPhyId, ClusterMirrorMakersOverviewDTO dto) { + List mirrorMakerList = connectorService.listByKafkaClusterIdFromDB(clusterPhyId).stream().filter(elem -> elem.getConnectorClassName().equals(MIRROR_MAKER_SOURCE_CONNECTOR_TYPE)).collect(Collectors.toList()); + List connectClusterList = connectClusterService.listByKafkaCluster(clusterPhyId); + + + Result> latestMetricsResult = mirrorMakerMetricService.getLatestMetricsFromES(clusterPhyId, + mirrorMakerList.stream().map(elem -> new Tuple<>(elem.getConnectClusterId(), elem.getConnectorName())).collect(Collectors.toList()), + dto.getLatestMetricNames()); + + if (latestMetricsResult.failed()) { + LOGGER.error("method=getClusterMirrorMakersOverview||clusterPhyId={}||result={}||errMsg=get latest metric failed", clusterPhyId, latestMetricsResult); + return PaginationResult.buildFailure(latestMetricsResult, dto); + } + + List mirrorMakerOverviewVOList = this.convert2ClusterMirrorMakerOverviewVO(mirrorMakerList, connectClusterList, latestMetricsResult.getData()); + + PaginationResult voPaginationResult = this.pagingMirrorMakerInLocal(mirrorMakerOverviewVOList, dto); + + if (voPaginationResult.failed()) { + LOGGER.error("method=ClusterMirrorMakerOverviewVO||clusterPhyId={}||result={}||errMsg=pagination in local failed", clusterPhyId, voPaginationResult); + + return PaginationResult.buildFailure(voPaginationResult, dto); + } + + //这里再补充源集群和目的集群信息,减少网络请求。 + this.completeClusterInfo(voPaginationResult.getData().getBizData()); + + + // 查询历史指标 + Result> lineMetricsResult = mirrorMakerMetricService.listMirrorMakerClusterMetricsFromES( + clusterPhyId, + this.buildMetricsConnectorsDTO( + voPaginationResult.getData().getBizData().stream().map(elem -> new ClusterConnectorDTO(elem.getConnectClusterId(), elem.getConnectorName())).collect(Collectors.toList()), + dto.getMetricLines() + )); + + return PaginationResult.buildSuc( + this.supplyData2ClusterMirrorMakerOverviewVOList( + voPaginationResult.getData().getBizData(), + lineMetricsResult.getData() + ), + voPaginationResult + ); + } + + @Override + public Result getMirrorMakerState(Long connectClusterId, String connectName) { + //mm2任务 + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, connectName); + if (connectorPO == null){ + return Result.buildFrom(ResultStatus.NOT_EXIST); + } + + List workerConnectorList = workerConnectorService.listFromDB(connectClusterId).stream() + .filter(workerConnector -> workerConnector.getConnectorName().equals(connectorPO.getConnectorName()) + || (!StringUtils.isBlank(connectorPO.getCheckpointConnectorName()) && workerConnector.getConnectorName().equals(connectorPO.getCheckpointConnectorName())) + || (!StringUtils.isBlank(connectorPO.getHeartbeatConnectorName()) && workerConnector.getConnectorName().equals(connectorPO.getHeartbeatConnectorName()))) + .collect(Collectors.toList()); + + MirrorMakerBaseStateVO mirrorMakerBaseStateVO = new MirrorMakerBaseStateVO(); + mirrorMakerBaseStateVO.setTotalTaskCount(workerConnectorList.size()); + mirrorMakerBaseStateVO.setAliveTaskCount(workerConnectorList.stream().filter(elem -> elem.getState().equals(RUNNING.name())).collect(Collectors.toList()).size()); + mirrorMakerBaseStateVO.setWorkerCount(workerConnectorList.stream().collect(Collectors.groupingBy(WorkerConnector::getWorkerId)).size()); + return Result.buildSuc(mirrorMakerBaseStateVO); + } + + @Override + public Result>> getTaskOverview(Long connectClusterId, String connectorName) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, connectorName); + if (connectorPO == null){ + return Result.buildFrom(ResultStatus.NOT_EXIST); + } + + Map> listMap = new HashMap<>(); + List workerConnectorList = workerConnectorService.listFromDB(connectClusterId); + if (workerConnectorList.isEmpty()){ + return Result.buildSuc(listMap); + } + workerConnectorList.forEach(workerConnector -> { + if (workerConnector.getConnectorName().equals(connectorPO.getConnectorName())){ + listMap.putIfAbsent(KafkaConnectConstant.MIRROR_MAKER_SOURCE_CONNECTOR_TYPE, new ArrayList<>()); + listMap.get(MIRROR_MAKER_SOURCE_CONNECTOR_TYPE).add(ConvertUtil.obj2Obj(workerConnector, KCTaskOverviewVO.class)); + } else if (workerConnector.getConnectorName().equals(connectorPO.getCheckpointConnectorName())) { + listMap.putIfAbsent(KafkaConnectConstant.MIRROR_MAKER_HEARTBEAT_CONNECTOR_TYPE, new ArrayList<>()); + listMap.get(MIRROR_MAKER_HEARTBEAT_CONNECTOR_TYPE).add(ConvertUtil.obj2Obj(workerConnector, KCTaskOverviewVO.class)); + } else if (workerConnector.getConnectorName().equals(connectorPO.getHeartbeatConnectorName())) { + listMap.putIfAbsent(KafkaConnectConstant.MIRROR_MAKER_CHECKPOINT_CONNECTOR_TYPE, new ArrayList<>()); + listMap.get(MIRROR_MAKER_CHECKPOINT_CONNECTOR_TYPE).add(ConvertUtil.obj2Obj(workerConnector, KCTaskOverviewVO.class)); + } + + }); + + return Result.buildSuc(listMap); + } + + @Override + public Result> getMM2Configs(Long connectClusterId, String connectorName) { + ConnectorPO connectorPO = connectorService.getConnectorFromDB(connectClusterId, connectorName); + if (connectorPO == null){ + return Result.buildFrom(ResultStatus.NOT_EXIST); + } + + List propList = new ArrayList<>(); + + // source + Result connectorResult = connectorService.getConnectorInfoFromCluster(connectClusterId, connectorPO.getConnectorName()); + if (connectorResult.failed()) { + return Result.buildFromIgnoreData(connectorResult); + } + + Properties props = new Properties(); + props.putAll(connectorResult.getData().getConfig()); + propList.add(props); + + // checkpoint + if (!ValidateUtils.isBlank(connectorPO.getCheckpointConnectorName())) { + connectorResult = connectorService.getConnectorInfoFromCluster(connectClusterId, connectorPO.getCheckpointConnectorName()); + if (connectorResult.failed()) { + return Result.buildFromIgnoreData(connectorResult); + } + + props = new Properties(); + props.putAll(connectorResult.getData().getConfig()); + propList.add(props); + } + + + // heartbeat + if (!ValidateUtils.isBlank(connectorPO.getHeartbeatConnectorName())) { + connectorResult = connectorService.getConnectorInfoFromCluster(connectClusterId, connectorPO.getHeartbeatConnectorName()); + if (connectorResult.failed()) { + return Result.buildFromIgnoreData(connectorResult); + } + + props = new Properties(); + props.putAll(connectorResult.getData().getConfig()); + propList.add(props); + } + + return Result.buildSuc(propList); + } + + @Override + public Result> validateConnectors(MirrorMakerCreateDTO dto) { + List voList = new ArrayList<>(); + + Result infoResult = pluginService.validateConfig(dto.getConnectClusterId(), dto.getConfigs()); + if (infoResult.failed()) { + return Result.buildFromIgnoreData(infoResult); + } + + voList.add(ConvertUtil.obj2Obj(infoResult.getData(), ConnectConfigInfosVO.class)); + + if (dto.getHeartbeatConnectorConfigs() != null) { + infoResult = pluginService.validateConfig(dto.getConnectClusterId(), dto.getHeartbeatConnectorConfigs()); + if (infoResult.failed()) { + return Result.buildFromIgnoreData(infoResult); + } + + voList.add(ConvertUtil.obj2Obj(infoResult.getData(), ConnectConfigInfosVO.class)); + } + + if (dto.getCheckpointConnectorConfigs() != null) { + infoResult = pluginService.validateConfig(dto.getConnectClusterId(), dto.getCheckpointConnectorConfigs()); + if (infoResult.failed()) { + return Result.buildFromIgnoreData(infoResult); + } + + voList.add(ConvertUtil.obj2Obj(infoResult.getData(), ConnectConfigInfosVO.class)); + } + + return Result.buildSuc(voList); + } + + + /**************************************************** private method ****************************************************/ + + private MetricsMirrorMakersDTO buildMetricsConnectorsDTO(List connectorDTOList, MetricDTO metricDTO) { + MetricsMirrorMakersDTO dto = ConvertUtil.obj2Obj(metricDTO, MetricsMirrorMakersDTO.class); + dto.setConnectorNameList(connectorDTOList == null? new ArrayList<>(): connectorDTOList); + + return dto; + } + + public Result checkCreateMirrorMakerParamAndUnifyData(MirrorMakerCreateDTO dto) { + ClusterPhy sourceClusterPhy = clusterPhyService.getClusterByCluster(dto.getSourceKafkaClusterId()); + if (sourceClusterPhy == null) { + return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, MsgConstant.getClusterPhyNotExist(dto.getSourceKafkaClusterId())); + } + + ConnectCluster connectCluster = connectClusterService.getById(dto.getConnectClusterId()); + if (connectCluster == null) { + return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, MsgConstant.getConnectClusterNotExist(dto.getConnectClusterId())); + } + + ClusterPhy targetClusterPhy = clusterPhyService.getClusterByCluster(connectCluster.getKafkaClusterPhyId()); + if (targetClusterPhy == null) { + return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, MsgConstant.getClusterPhyNotExist(connectCluster.getKafkaClusterPhyId())); + } + + if (!dto.getConfigs().containsKey(CONNECTOR_CLASS_FILED_NAME)) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "SourceConnector缺少connector.class"); + } + + if (!MIRROR_MAKER_SOURCE_CONNECTOR_TYPE.equals(dto.getConfigs().getProperty(CONNECTOR_CLASS_FILED_NAME))) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "SourceConnector的connector.class类型错误"); + } + + if (dto.getCheckpointConnectorConfigs() != null) { + if (!dto.getCheckpointConnectorConfigs().containsKey(CONNECTOR_CLASS_FILED_NAME)) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "CheckpointConnector缺少connector.class"); + } + + if (!MIRROR_MAKER_CHECKPOINT_CONNECTOR_TYPE.equals(dto.getCheckpointConnectorConfigs().getProperty(CONNECTOR_CLASS_FILED_NAME))) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "Checkpoint的connector.class类型错误"); + } + } + + if (dto.getHeartbeatConnectorConfigs() != null) { + if (!dto.getHeartbeatConnectorConfigs().containsKey(CONNECTOR_CLASS_FILED_NAME)) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "HeartbeatConnector缺少connector.class"); + } + + if (!MIRROR_MAKER_HEARTBEAT_CONNECTOR_TYPE.equals(dto.getHeartbeatConnectorConfigs().getProperty(CONNECTOR_CLASS_FILED_NAME))) { + return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "Heartbeat的connector.class类型错误"); + } + } + + dto.unifyData( + sourceClusterPhy.getId(), sourceClusterPhy.getBootstrapServers(), ConvertUtil.str2ObjByJson(sourceClusterPhy.getClientProperties(), Properties.class), + targetClusterPhy.getId(), targetClusterPhy.getBootstrapServers(), ConvertUtil.str2ObjByJson(targetClusterPhy.getClientProperties(), Properties.class) + ); + + return Result.buildSuc(); + } + + private MirrorMakerStateVO convert2MirrorMakerStateVO(List connectorPOList,List workerConnectorList,List workerList){ + MirrorMakerStateVO mirrorMakerStateVO = new MirrorMakerStateVO(); + + List sourceSet = connectorPOList.stream().filter(elem -> elem.getConnectorClassName().equals(MIRROR_MAKER_SOURCE_CONNECTOR_TYPE)).collect(Collectors.toList()); + mirrorMakerStateVO.setMirrorMakerCount(sourceSet.size()); + + Set connectClusterIdSet = sourceSet.stream().map(ConnectorPO::getConnectClusterId).collect(Collectors.toSet()); + mirrorMakerStateVO.setWorkerCount(workerList.stream().filter(elem -> connectClusterIdSet.contains(elem.getConnectClusterId())).collect(Collectors.toList()).size()); + + List mirrorMakerConnectorList = new ArrayList<>(); + mirrorMakerConnectorList.addAll(sourceSet); + mirrorMakerConnectorList.addAll(connectorPOList.stream().filter(elem -> elem.getConnectorClassName().equals(MIRROR_MAKER_CHECKPOINT_CONNECTOR_TYPE)).collect(Collectors.toList())); + mirrorMakerConnectorList.addAll(connectorPOList.stream().filter(elem -> elem.getConnectorClassName().equals(MIRROR_MAKER_HEARTBEAT_CONNECTOR_TYPE)).collect(Collectors.toList())); + mirrorMakerStateVO.setTotalConnectorCount(mirrorMakerConnectorList.size()); + mirrorMakerStateVO.setAliveConnectorCount(mirrorMakerConnectorList.stream().filter(elem -> elem.getState().equals(RUNNING.name())).collect(Collectors.toList()).size()); + + Set connectorNameSet = mirrorMakerConnectorList.stream().map(elem -> elem.getConnectorName()).collect(Collectors.toSet()); + List taskList = workerConnectorList.stream().filter(elem -> connectorNameSet.contains(elem.getConnectorName())).collect(Collectors.toList()); + mirrorMakerStateVO.setTotalTaskCount(taskList.size()); + mirrorMakerStateVO.setAliveTaskCount(taskList.stream().filter(elem -> elem.getState().equals(RUNNING.name())).collect(Collectors.toList()).size()); + + return mirrorMakerStateVO; + } + + private List convert2ClusterMirrorMakerOverviewVO(List mirrorMakerList, List connectClusterList, List latestMetric) { + List clusterMirrorMakerOverviewVOList = new ArrayList<>(); + Map metricsMap = latestMetric.stream().collect(Collectors.toMap(elem -> elem.getConnectClusterId() + "@" + elem.getConnectorName(), Function.identity())); + Map connectClusterMap = connectClusterList.stream().collect(Collectors.toMap(elem -> elem.getId(), Function.identity())); + + for (ConnectorPO mirrorMaker : mirrorMakerList) { + ClusterMirrorMakerOverviewVO clusterMirrorMakerOverviewVO = new ClusterMirrorMakerOverviewVO(); + clusterMirrorMakerOverviewVO.setConnectClusterId(mirrorMaker.getConnectClusterId()); + clusterMirrorMakerOverviewVO.setConnectClusterName(connectClusterMap.get(mirrorMaker.getConnectClusterId()).getName()); + clusterMirrorMakerOverviewVO.setConnectorName(mirrorMaker.getConnectorName()); + clusterMirrorMakerOverviewVO.setState(mirrorMaker.getState()); + clusterMirrorMakerOverviewVO.setCheckpointConnector(mirrorMaker.getCheckpointConnectorName()); + clusterMirrorMakerOverviewVO.setTaskCount(mirrorMaker.getTaskCount()); + clusterMirrorMakerOverviewVO.setHeartbeatConnector(mirrorMaker.getHeartbeatConnectorName()); + clusterMirrorMakerOverviewVO.setLatestMetrics(metricsMap.getOrDefault(mirrorMaker.getConnectClusterId() + "@" + mirrorMaker.getConnectorName(), new MirrorMakerMetrics(mirrorMaker.getConnectClusterId(), mirrorMaker.getConnectorName()))); + clusterMirrorMakerOverviewVOList.add(clusterMirrorMakerOverviewVO); + } + return clusterMirrorMakerOverviewVOList; + } + + PaginationResult pagingMirrorMakerInLocal(List mirrorMakerOverviewVOList, ClusterMirrorMakersOverviewDTO dto) { + List mirrorMakerVOList = PaginationUtil.pageByFuzzyFilter(mirrorMakerOverviewVOList, dto.getSearchKeywords(), Arrays.asList("connectClusterName")); + + //排序 + if (!dto.getLatestMetricNames().isEmpty()) { + PaginationMetricsUtil.sortMetrics(mirrorMakerVOList, "latestMetrics", dto.getSortMetricNameList(), "connectClusterName", dto.getSortType()); + } else { + PaginationUtil.pageBySort(mirrorMakerVOList, dto.getSortField(), dto.getSortType(), "connectClusterName", dto.getSortType()); + } + + //分页 + return PaginationUtil.pageBySubData(mirrorMakerVOList, dto); + } + + public static List supplyData2ClusterMirrorMakerOverviewVOList(List voList, + List metricLineVOList) { + Map> metricLineMap = new HashMap<>(); + if (metricLineVOList != null) { + for (MetricMultiLinesVO metricMultiLinesVO : metricLineVOList) { + metricMultiLinesVO.getMetricLines() + .forEach(metricLineVO -> { + String key = metricLineVO.getName(); + List metricLineVOS = metricLineMap.getOrDefault(key, new ArrayList<>()); + metricLineVOS.add(metricLineVO); + metricLineMap.put(key, metricLineVOS); + }); + } + } + + voList.forEach(elem -> { + elem.setMetricLines(metricLineMap.get(elem.getConnectClusterId() + "#" + elem.getConnectorName())); + }); + + return voList; + } + + private void completeClusterInfo(List mirrorMakerVOList) { + + for (ClusterMirrorMakerOverviewVO mirrorMakerVO : mirrorMakerVOList) { + Result connectorInfoRet = connectorService.getConnectorInfoFromCluster(mirrorMakerVO.getConnectClusterId(), mirrorMakerVO.getConnectorName()); + if (!connectorInfoRet.hasData()) { + continue; + } + KSConnectorInfo connectorInfo = connectorInfoRet.getData(); + + String sourceClusterAlias = connectorInfo.getConfig().get(MIRROR_MAKER_SOURCE_CLUSTER_ALIAS_FIELD_NAME); + String targetClusterAlias = connectorInfo.getConfig().get(MIRROR_MAKER_TARGET_CLUSTER_ALIAS_FIELD_NAME); + //先默认设置为集群别名 + mirrorMakerVO.setSourceKafkaClusterName(sourceClusterAlias); + mirrorMakerVO.setDestKafkaClusterName(targetClusterAlias); + + if (!ValidateUtils.isBlank(sourceClusterAlias) && CommonUtils.isNumeric(sourceClusterAlias)) { + ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(Long.valueOf(sourceClusterAlias)); + if (clusterPhy != null) { + mirrorMakerVO.setSourceKafkaClusterId(clusterPhy.getId()); + mirrorMakerVO.setSourceKafkaClusterName(clusterPhy.getName()); + } + } + + if (!ValidateUtils.isBlank(targetClusterAlias) && CommonUtils.isNumeric(targetClusterAlias)) { + ClusterPhy clusterPhy = LoadedClusterPhyCache.getByPhyId(Long.valueOf(targetClusterAlias)); + if (clusterPhy != null) { + mirrorMakerVO.setDestKafkaClusterId(clusterPhy.getId()); + mirrorMakerVO.setDestKafkaClusterName(clusterPhy.getName()); + } + } + + } + } +} From ab6a4d7099ce11a2ee0d2081074ac83b1384ef92 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:54:31 +0800 Subject: [PATCH 114/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-MM2?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E7=B1=BB?= =?UTF-8?q?(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connector/ConnectorConfigModifyDTO.java | 21 ----- .../metrics/connect/ConnectorTaskMetrics.java | 1 - .../km/common/constant/ApiPrefix.java | 4 + .../km/common/converter/ConnectConverter.java | 20 +++++ .../ClusterMirrorMakersController.java | 82 +++++++++++++++++++ .../v3/connect/KafkaConnectorController.java | 5 +- .../mm2/KafkaMirrorMakerController.java | 76 +++++++++++++++++ .../mm2/KafkaMirrorMakerStateController.java | 68 +++++++++++++++ 8 files changed, 252 insertions(+), 25 deletions(-) delete mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterMirrorMakersController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerController.java create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerStateController.java diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java deleted file mode 100644 index 40f617c8..00000000 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/connect/connector/ConnectorConfigModifyDTO.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector; - -import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.ClusterConnectorDTO; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import javax.validation.constraints.NotNull; -import java.util.Properties; - -/** - * @author zengqiao - * @date 2022-10-17 - */ -@Data -@ApiModel(description = "修改Connector配置") -public class ConnectorConfigModifyDTO extends ClusterConnectorDTO { - @NotNull(message = "configs不允许为空") - @ApiModelProperty(value = "配置", example = "") - private Properties configs; -} diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java index 26b244e7..eb0dc42d 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/metrics/connect/ConnectorTaskMetrics.java @@ -1,7 +1,6 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.connect; import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BaseMetrics; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java index 332f639c..c7c1803b 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/ApiPrefix.java @@ -14,6 +14,10 @@ public class ApiPrefix { public static final String API_V3_CONNECT_PREFIX = API_V3_PREFIX + "kafka-connect/"; + public static final String API_V3_MM2_PREFIX = API_V3_PREFIX + "kafka-mm2/"; + + public static final String API_V3_HA_MIRROR_PREFIX = API_V3_PREFIX + "ha-mirror/"; + public static final String API_V3_OPEN_PREFIX = API_V3_PREFIX + "open/"; private ApiPrefix() { diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java index 387c8469..6dcc30e4 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/converter/ConnectConverter.java @@ -10,6 +10,7 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connect.ConnectCl import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ClusterConnectorOverviewVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicCombineExistVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.connector.ConnectorBasicVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerBasicVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricLineVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; @@ -58,6 +59,25 @@ public class ConnectConverter { return voList; } + public static List convert2MirrorMakerBasicVOList( + List clusterList, + List poList) { + Map clusterMap = new HashMap<>(); + clusterList.stream().forEach(elem -> clusterMap.put(elem.getId(), elem)); + + List voList = new ArrayList<>(); + poList.stream().filter(item -> clusterMap.containsKey(item.getConnectClusterId())).forEach(elem -> { + MirrorMakerBasicVO vo = new MirrorMakerBasicVO(); + vo.setConnectClusterId(elem.getConnectClusterId()); + vo.setConnectClusterName(clusterMap.get(elem.getConnectClusterId()).getName()); + vo.setConnectorName(elem.getConnectorName()); + + voList.add(vo); + }); + + return voList; + } + public static ConnectClusterBasicCombineExistVO convert2ConnectClusterBasicCombineExistVO(ConnectCluster connectCluster) { if (connectCluster == null) { ConnectClusterBasicCombineExistVO combineExistVO = new ConnectClusterBasicCombineExistVO(); diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterMirrorMakersController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterMirrorMakersController.java new file mode 100644 index 00000000..e74b7a99 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterMirrorMakersController.java @@ -0,0 +1,82 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.cluster; + +import com.xiaojukeji.know.streaming.km.biz.connect.mm2.MirrorMakerManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterMirrorMakersOverviewDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.mm2.MetricsMirrorMakersDTO; +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.vo.cluster.mm2.ClusterMirrorMakerOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerBasicVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.constant.connect.KafkaConnectConstant; +import com.xiaojukeji.know.streaming.km.common.converter.ConnectConverter; +import com.xiaojukeji.know.streaming.km.core.service.connect.cluster.ConnectClusterService; +import com.xiaojukeji.know.streaming.km.core.service.connect.connector.ConnectorService; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerMetricService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author zengqiao + * @date 22/12/12 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "集群MM2s-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_PREFIX) // 这里使用 API_V3_PREFIX 没有使用 API_V3_CONNECT_PREFIX 的原因是这个接口在Kafka集群页面下 +public class ClusterMirrorMakersController { + @Autowired + private MirrorMakerMetricService mirrorMakerMetricService; + + @Autowired + private MirrorMakerManager mirrorMakerManager; + + @Autowired + private ConnectClusterService connectClusterService; + + @Autowired + private ConnectorService connectorService; + + @ApiOperation(value = "集群MM2状态", notes = "") + @GetMapping(value = "kafka-clusters/{clusterPhyId}/mirror-makers-state") + @ResponseBody + public Result getClusterMM2State(@PathVariable Long clusterPhyId) { + return mirrorMakerManager.getMirrorMakerStateVO(clusterPhyId); + } + + @ApiOperation(value = "集群MM2基本信息", notes = "") + @GetMapping(value = "clusters/{clusterPhyId}/mirror-makers-basic") + @ResponseBody + public Result> getClusterMirrorMakersBasic(@PathVariable Long clusterPhyId) { + return Result.buildSuc( + ConnectConverter.convert2MirrorMakerBasicVOList( + connectClusterService.listByKafkaCluster(clusterPhyId), + connectorService.listByKafkaClusterIdFromDB(clusterPhyId).stream().filter(elem -> elem.getConnectorClassName().equals(KafkaConnectConstant.MIRROR_MAKER_SOURCE_CONNECTOR_TYPE)).collect(Collectors.toList()) + ) + ); + } + + @ApiOperation(value = "集群MM2概览列表", notes = "") + @PostMapping(value = "clusters/{clusterPhyId}/mirror-makers-overview") + @ResponseBody + public PaginationResult getClusterMirrorMakersOverview(@PathVariable Long clusterPhyId, + @Validated @RequestBody ClusterMirrorMakersOverviewDTO dto) { + return mirrorMakerManager.getClusterMirrorMakersOverview(clusterPhyId,dto); + } + + @ApiOperation(value = "集群MM2指标信息") + @PostMapping(value = "clusters/{clusterPhyId}/mirror-makers-metrics") + @ResponseBody + public Result> getClusterMirrorMakersMetrics(@PathVariable Long clusterPhyId, + @Validated @RequestBody MetricsMirrorMakersDTO dto) { + return mirrorMakerMetricService.listMirrorMakerClusterMetricsFromES(clusterPhyId, dto); + } +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java index 8e5e7237..d60314bb 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/KafkaConnectorController.java @@ -6,7 +6,6 @@ import com.xiaojukeji.know.streaming.km.biz.connect.connector.ConnectorManager; import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorActionDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorCreateDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorDeleteDTO; -import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.connector.ConnectorConfigModifyDTO; import com.xiaojukeji.know.streaming.km.common.bean.entity.connect.config.ConnectConfigInfos; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus; @@ -73,14 +72,14 @@ public class KafkaConnectorController { @ApiOperation(value = "修改Connector配置", notes = "") @PutMapping(value ="connectors-config") @ResponseBody - public Result modifyConnectors(@Validated @RequestBody ConnectorConfigModifyDTO dto) { + public Result modifyConnectors(@Validated @RequestBody ConnectorCreateDTO dto) { return connectorManager.updateConnectorConfig(dto.getConnectClusterId(), dto.getConnectorName(), dto.getConfigs(), HttpRequestUtil.getOperator()); } @ApiOperation(value = "校验Connector配置", notes = "") @PutMapping(value ="connectors-config/validate") @ResponseBody - public Result validateConnectors(@Validated @RequestBody ConnectorConfigModifyDTO dto) { + public Result validateConnectors(@Validated @RequestBody ConnectorCreateDTO dto) { Result infoResult = pluginService.validateConfig(dto.getConnectClusterId(), dto.getConfigs()); if (infoResult.failed()) { return Result.buildFromIgnoreData(infoResult); diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerController.java new file mode 100644 index 00000000..fdc1136d --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerController.java @@ -0,0 +1,76 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect.mm2; + +import com.didiglobal.logi.security.util.HttpRequestUtil; +import com.xiaojukeji.know.streaming.km.biz.connect.mm2.MirrorMakerManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2.MirrorMaker2ActionDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2.MirrorMakerCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2.MirrorMaker2DeleteDTO; +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.vo.connect.plugin.ConnectConfigInfosVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.enums.connect.ConnectActionEnum; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author zengqiao + * @date 22/12/12 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "MM2-MM2自身-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_MM2_PREFIX) +public class KafkaMirrorMakerController { + @Autowired + private MirrorMakerManager mirrorMakerManager; + + @ApiOperation(value = "创建MM2", notes = "") + @PostMapping(value = "mirror-makers") + @ResponseBody + public Result createMM2(@Validated @RequestBody MirrorMakerCreateDTO dto) { + return mirrorMakerManager.createMirrorMaker(dto, HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "删除MM2", notes = "") + @DeleteMapping(value ="mirror-makers") + @ResponseBody + public Result deleteMM2(@Validated @RequestBody MirrorMaker2DeleteDTO dto) { + return mirrorMakerManager.deleteMirrorMaker(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "操作MM2", notes = "") + @PutMapping(value ="mirror-makers") + @ResponseBody + public Result operateMM2s(@Validated @RequestBody MirrorMaker2ActionDTO dto) { + if (ConnectActionEnum.RESTART.getValue().equals(dto.getAction())) { + return mirrorMakerManager.restartMirrorMaker(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } else if (ConnectActionEnum.STOP.getValue().equals(dto.getAction())) { + return mirrorMakerManager.stopMirrorMaker(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } else if (ConnectActionEnum.RESUME.getValue().equals(dto.getAction())) { + return mirrorMakerManager.resumeMirrorMaker(dto.getConnectClusterId(), dto.getConnectorName(), HttpRequestUtil.getOperator()); + } + + return Result.buildFailure(ResultStatus.PARAM_ILLEGAL); + } + + @ApiOperation(value = "MM2配置修改", notes = "") + @PutMapping(value ="mirror-makers-config") + @ResponseBody + public Result modifyMM2s(@Validated @RequestBody MirrorMakerCreateDTO dto) { + return mirrorMakerManager.modifyMirrorMakerConfig(dto, HttpRequestUtil.getOperator()); + } + + @ApiOperation(value = "校验MM2配置", notes = "") + @PutMapping(value ="mirror-makers-config/validate") + @ResponseBody + public Result> validateConnectors(@Validated @RequestBody MirrorMakerCreateDTO dto) { + return mirrorMakerManager.validateConnectors(dto); + } +} diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerStateController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerStateController.java new file mode 100644 index 00000000..2cfad408 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/connect/mm2/KafkaMirrorMakerStateController.java @@ -0,0 +1,68 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.connect.mm2; + +import com.didiglobal.logi.security.util.HttpRequestUtil; +import com.xiaojukeji.know.streaming.km.biz.connect.mm2.MirrorMakerManager; +import com.xiaojukeji.know.streaming.km.common.bean.dto.connect.mm2.MirrorMakerCreateDTO; +import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.mm2.MirrorMakerMetrics; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.mm2.MirrorMakerBaseStateVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.connect.task.KCTaskOverviewVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.core.service.connect.mm2.MirrorMakerMetricService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + + +/** + * @author zengqiao + * @date 22/12/12 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "MM2-MM2状态-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_MM2_PREFIX) +public class KafkaMirrorMakerStateController { + @Autowired + private MirrorMakerManager mirrorMakerManager; + + @Autowired + private MirrorMakerMetricService mirrorMakerMetricService; + + @ApiOperation(value = "获取mm2任务的状态", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/state") + @ResponseBody + public Result getMirrorMakerStateVO(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + return mirrorMakerManager.getMirrorMakerState(connectClusterId, connectorName); + } + + @ApiOperation(value = "获取MM2的Task列表", notes = "") + @GetMapping(value = "clusters/{connectClusterId}/connectors/{connectorName}/tasks") + @ResponseBody + public Result>> getConnectorTasks(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + return mirrorMakerManager.getTaskOverview(connectClusterId, connectorName); + } + + @ApiOperation(value = "MM2配置", notes = "") + @GetMapping(value ="clusters/{connectClusterId}/connectors/{connectorName}/config") + @ResponseBody + public Result> getMM2Configs(@PathVariable Long connectClusterId, @PathVariable String connectorName) { + return mirrorMakerManager.getMM2Configs(connectClusterId, connectorName); + } + + @ApiOperation(value = "Connector近期指标") + @PostMapping(value = "clusters/{connectClusterId}/connectors/{mirrorMakerName}/latest-metrics") + @ResponseBody + public Result getMirrorMakerLatestMetrics(@PathVariable Long connectClusterId, + @PathVariable String mirrorMakerName, + @RequestBody List metricsNames) { + return mirrorMakerMetricService.getLatestMetricsFromES(connectClusterId, mirrorMakerName, metricsNames); + } + +} From e5c6d00438e8f612cd97988a2593f7f6109de881 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:56:36 +0800 Subject: [PATCH 115/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=9B=86=E7=BE=A4Group=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E4=BF=A1=E6=81=AF(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../streaming/km/biz/group/GroupManager.java | 2 +- .../dto/cluster/ClusterGroupsOverviewDTO.java | 19 -------- .../km/core/service/group/GroupService.java | 1 + .../service/group/impl/GroupServiceImpl.java | 8 ++++ .../v3/cluster/ClusterGroupsController.java | 48 ++++--------------- 5 files changed, 20 insertions(+), 58 deletions(-) delete mode 100644 km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterGroupsOverviewDTO.java diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/GroupManager.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/GroupManager.java index a3686c03..5a6d3ac6 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/GroupManager.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/group/GroupManager.java @@ -39,5 +39,5 @@ public interface GroupManager { Result resetGroupOffsets(GroupOffsetResetDTO dto, String operator) throws Exception; - List getGroupTopicOverviewVOList (Long clusterPhyId, List groupMemberPOList); + List getGroupTopicOverviewVOList(Long clusterPhyId, List groupMemberPOList); } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterGroupsOverviewDTO.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterGroupsOverviewDTO.java deleted file mode 100644 index 13d13b52..00000000 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/dto/cluster/ClusterGroupsOverviewDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.xiaojukeji.know.streaming.km.common.bean.dto.cluster; - -import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationMulFuzzySearchDTO; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - - -/** - * @author zengqiao - * @date 22/02/24 - */ -@Data -public class ClusterGroupsOverviewDTO extends PaginationMulFuzzySearchDTO { - @ApiModelProperty("查找该Topic") - private String topicName; - - @ApiModelProperty("查找该Group") - private String groupName; -} diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java index 3f56a0b3..47317c80 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/GroupService.java @@ -58,6 +58,7 @@ public interface GroupService { /** * DB-GroupTopic相关接口 */ + List listGroupByCluster(Long clusterPhyId); List listGroupByTopic(Long clusterPhyId, String topicName); PaginationResult pagingGroupMembers(Long clusterPhyId, diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java index c80652ee..15dc2108 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/group/impl/GroupServiceImpl.java @@ -224,6 +224,14 @@ public class GroupServiceImpl extends BaseKafkaVersionControlService implements return GroupStateEnum.getByState(poList.get(0).getState()); } + @Override + public List listGroupByCluster(Long clusterPhyId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(GroupMemberPO::getClusterPhyId, clusterPhyId); + + return groupMemberDAO.selectList(lambdaQueryWrapper); + } + @Override public List listGroupByTopic(Long clusterPhyId, String topicName) { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterGroupsController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterGroupsController.java index b035ea02..7159dca9 100644 --- a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterGroupsController.java +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/cluster/ClusterGroupsController.java @@ -2,21 +2,20 @@ package com.xiaojukeji.know.streaming.km.rest.api.v3.cluster; import com.xiaojukeji.know.streaming.km.biz.group.GroupManager; import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterGroupSummaryDTO; -import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterGroupsOverviewDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricGroupPartitionDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationBaseDTO; -import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.field.PaginationFuzzySearchFieldDTO; 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.topic.TopicPartitionKS; import com.xiaojukeji.know.streaming.km.common.bean.vo.group.GroupOverviewVO; +import com.xiaojukeji.know.streaming.km.common.bean.vo.group.GroupTopicBasicVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.group.GroupTopicOverviewVO; import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; import com.xiaojukeji.know.streaming.km.common.constant.Constant; -import com.xiaojukeji.know.streaming.km.common.utils.Tuple; -import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; +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 io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -37,24 +36,17 @@ public class ClusterGroupsController { @Autowired private GroupManager groupManager; + @Autowired + private GroupService groupService; + @Autowired private GroupMetricService groupMetricService; - @Deprecated - @ApiOperation(value = "集群Groups信息列表", notes = "废弃, 下一个版本删除") - @PostMapping(value = "clusters/{clusterPhyId}/groups-overview") + @ApiOperation(value = "集群Groups信息列表") + @GetMapping(value = "clusters/{clusterPhyId}/groups-basic") @ResponseBody - public PaginationResult getClusterPhyGroupsOverview(@PathVariable Long clusterPhyId, - @RequestBody ClusterGroupsOverviewDTO dto) { - Tuple searchKeyTuple = this.getSearchKeyWords(dto); - return groupManager.pagingGroupMembers( - clusterPhyId, - dto.getTopicName(), - dto.getGroupName(), - searchKeyTuple.getV1(), - searchKeyTuple.getV2(), - dto - ); + public Result> getGroupsBasic(@PathVariable Long clusterPhyId) { + return Result.buildSuc(ConvertUtil.list2List(groupService.listGroupByCluster(clusterPhyId), GroupTopicBasicVO.class)); } @ApiOperation(value = "集群Groups信息列表") @@ -90,24 +82,4 @@ public class ClusterGroupsController { } /**************************************************** private method ****************************************************/ - - @Deprecated - private Tuple getSearchKeyWords(ClusterGroupsOverviewDTO dto) { - if (ValidateUtils.isEmptyList(dto.getFuzzySearchDTOList())) { - return new Tuple<>("", ""); - } - - String searchTopicName = ""; - String searchGroupName = ""; - for (PaginationFuzzySearchFieldDTO searchFieldDTO: dto.getFuzzySearchDTOList()) { - if (searchFieldDTO.getFieldName().equals("topicName")) { - searchTopicName = searchFieldDTO.getFieldValue(); - } - if (searchFieldDTO.getFieldName().equals("groupName")) { - searchGroupName = searchFieldDTO.getFieldValue(); - } - } - - return new Tuple<>(searchTopicName, searchGroupName); - } } From fbcf58e19cb27ff2961bdbcf7954af9b727fdcb2 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 16:57:26 +0800 Subject: [PATCH 116/150] =?UTF-8?q?[Feature]MM2=E7=AE=A1=E7=90=86-Connecto?= =?UTF-8?q?r=E5=85=83=E4=BF=A1=E6=81=AF=E7=AE=A1=E7=90=86=E4=BC=98?= =?UTF-8?q?=E5=8C=96(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/task/connect/metadata/SyncConnectorTask.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java index 4d45e7c2..00e58425 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/connect/metadata/SyncConnectorTask.java @@ -27,7 +27,6 @@ public class SyncConnectorTask extends AbstractAsyncMetadataDispatchTask { @Autowired private ConnectorService connectorService; - @Override public TaskResult processClusterTask(ConnectCluster connectCluster, long triggerTimeUnitMs) { Result> nameListResult = connectorService.listConnectorsFromCluster(connectCluster.getId()); @@ -42,7 +41,7 @@ public class SyncConnectorTask extends AbstractAsyncMetadataDispatchTask { Result ksConnectorResult = connectorService.getAllConnectorInfoFromCluster(connectCluster.getId(), connectorName); if (ksConnectorResult.failed()) { LOGGER.error( - "class=SyncConnectorTask||method=processClusterTask||connectClusterId={}||connectorName={}||result={}", + "method=processClusterTask||connectClusterId={}||connectorName={}||result={}", connectCluster.getId(), connectorName, ksConnectorResult ); @@ -53,6 +52,9 @@ public class SyncConnectorTask extends AbstractAsyncMetadataDispatchTask { connectorList.add(ksConnectorResult.getData()); } + //mm2相关信息的添加 + connectorService.completeMirrorMakerInfo(connectCluster, connectorList); + connectorService.batchReplace(connectCluster.getKafkaClusterPhyId(), connectCluster.getId(), connectorList, new HashSet<>(nameListResult.getData())); return allSuccess? TaskResult.SUCCESS: TaskResult.FAIL; From 4c5ffccc45070a8fb516372298628d6de1a37157 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 17:00:50 +0800 Subject: [PATCH 117/150] =?UTF-8?q?[Optimize]=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E6=95=88=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../broker/impl/BrokerConfigServiceImpl.java | 1 - .../broker/impl/BrokerSpecServiceImpl.java | 4 ---- .../component/AbstractMonitorSinkService.java | 21 +++---------------- .../cache/DataBaseDataLocalCache.java | 1 + 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java index f47a3fa5..aa2bb7ec 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerConfigServiceImpl.java @@ -23,7 +23,6 @@ import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerConfigService; import com.xiaojukeji.know.streaming.km.core.service.oprecord.OpLogWrapService; import com.xiaojukeji.know.streaming.km.core.service.version.BaseKafkaVersionControlService; -import com.xiaojukeji.know.streaming.km.core.service.version.BaseVersionControlService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminZKClient; import com.xiaojukeji.know.streaming.km.persistence.mysql.broker.BrokerConfigDAO; diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java index 5cbe3ce8..7ba20b15 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerSpecServiceImpl.java @@ -1,7 +1,5 @@ package com.xiaojukeji.know.streaming.km.core.service.broker.impl; -import com.didiglobal.logi.log.ILog; -import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.BrokerSpec; import com.xiaojukeji.know.streaming.km.common.bean.po.config.PlatformClusterConfigPO; import com.xiaojukeji.know.streaming.km.common.enums.config.ConfigGroupEnum; @@ -17,8 +15,6 @@ import java.util.Map; @Service public class BrokerSpecServiceImpl implements BrokerSpecService { - private static final ILog LOGGER = LogFactory.getLog(BrokerSpecServiceImpl.class); - @Autowired private PlatformClusterConfigService platformClusterConfigService; diff --git a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java index 8bac5ac6..f35c5ec6 100644 --- a/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java +++ b/km-extends/km-monitor/src/main/java/com/xiaojukeji/know/streaming/km/monitor/component/AbstractMonitorSinkService.java @@ -16,11 +16,11 @@ import java.util.Map; import static com.xiaojukeji.know.streaming.km.monitor.common.MonitorSinkTagEnum.*; public abstract class AbstractMonitorSinkService implements ApplicationListener { - protected static final ILog LOGGER = LogFactory.getLog(AbstractMonitorSinkService.class); + protected static final ILog LOGGER = LogFactory.getLog(AbstractMonitorSinkService.class); - private static final int STEP = 60; + private static final int STEP = 60; - private FutureUtil sinkTP = FutureUtil.init( + private static final FutureUtil sinkTP = FutureUtil.init( "SinkMetricsTP", 5, 5, @@ -156,21 +156,6 @@ public abstract class AbstractMonitorSinkService implements ApplicationListener< return pointList; } - private List replicationMetric2SinkPoint(List replicationMetrics){ - List pointList = new ArrayList<>(); - - for(ReplicationMetrics r : replicationMetrics){ - Map tagsMap = new HashMap<>(); - tagsMap.put(CLUSTER_ID.getName(), r.getClusterPhyId()); - tagsMap.put(BROKER_ID.getName(), r.getBrokerId()); - tagsMap.put(PARTITION_ID.getName(), r.getPartitionId()); - - pointList.addAll(genSinkPoint("Replication", r.getMetrics(), r.getTimestamp(), tagsMap)); - } - - return pointList; - } - private List zookeeperMetric2SinkPoint(List zookeeperMetricsList){ List pointList = new ArrayList<>(); diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java index 32ac7ce8..cb67b14b 100644 --- a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/cache/DataBaseDataLocalCache.java @@ -39,6 +39,7 @@ public class DataBaseDataLocalCache { private static Cache>> partitionsCache; private static Cache>> healthCheckResultCache; + private static Cache haTopicCache; @PostConstruct From e204023b1f221dfac906135a0c14b74cfed56638 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 9 Feb 2023 17:03:28 +0800 Subject: [PATCH 118/150] =?UTF-8?q?[Feature]=E5=A2=9E=E5=8A=A0=E6=94=AF?= =?UTF-8?q?=E6=8C=81Topic=E5=A4=8D=E5=88=B6=E7=9A=84=E9=9B=86=E7=BE=A4?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3(#899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mirror/MirrorClusterController.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorClusterController.java diff --git a/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorClusterController.java b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorClusterController.java new file mode 100644 index 00000000..f4d2f855 --- /dev/null +++ b/km-rest/src/main/java/com/xiaojukeji/know/streaming/km/rest/api/v3/enterprise/mirror/MirrorClusterController.java @@ -0,0 +1,37 @@ +package com.xiaojukeji.know.streaming.km.rest.api.v3.enterprise.mirror; + +import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; +import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyBaseVO; +import com.xiaojukeji.know.streaming.km.common.constant.ApiPrefix; +import com.xiaojukeji.know.streaming.km.common.constant.Constant; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; +import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author zengqiao + * @date 22/12/12 + */ +@Api(tags = Constant.SWAGGER_API_TAG_PREFIX + "Mirror-集群-相关接口(REST)") +@RestController +@RequestMapping(ApiPrefix.API_V3_HA_MIRROR_PREFIX) +public class MirrorClusterController { + + @Autowired + private ClusterPhyService clusterPhyService; + + @ApiOperation(value = "集群列表(支持Mirror)", notes = "") + @GetMapping(value = "physical-clusters/basic") + @ResponseBody + public Result> listClusters() { + List clusterPhyList = clusterPhyService.listAllClusters().stream().filter(item -> item.getKafkaVersion().contains("2.5.0-d-")).collect(Collectors.toList()); + return Result.buildSuc(ConvertUtil.list2List(clusterPhyList, ClusterPhyBaseVO.class)); + } +} From 98a5c7b776f9dc5da3b0d25193e7690738418a2d Mon Sep 17 00:00:00 2001 From: fengqiongfeng Date: Thu, 9 Feb 2023 19:09:39 +0800 Subject: [PATCH 119/150] =?UTF-8?q?[Optimize]=E5=81=A5=E5=BA=B7=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E6=97=A5=E5=BF=97=E4=BC=98=E5=8C=96(#869)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../health/checker/cluster/HealthCheckClusterService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java index 39ee6779..9d31af2d 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/health/checker/cluster/HealthCheckClusterService.java @@ -58,7 +58,7 @@ public class HealthCheckClusterService extends AbstractHealthCheckService { Result clusterMetricsResult = clusterMetricService.getLatestMetricsFromES(param.getClusterPhyId(), Arrays.asList(ClusterMetricVersionItems.CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT)); if (clusterMetricsResult.failed() || !clusterMetricsResult.hasData()) { - log.error("method=checkClusterNoController||param={}||config={}||result={}||errMsg=get metrics failed", + log.error("method=checkClusterNoController||param={}||config={}||result={}||errMsg=get metrics from es failed", param, valueConfig, clusterMetricsResult); return null; } @@ -71,7 +71,11 @@ public class HealthCheckClusterService extends AbstractHealthCheckService { ); Float activeController = clusterMetricsResult.getData().getMetric(ClusterMetricVersionItems.CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT); - + if (activeController == null) { + log.error("method=checkClusterNoController||param={}||config={}||errMsg=get metrics from es failed, activeControllerCount is null", + param, valueConfig); + return null; + } checkResult.setPassed(activeController.intValue() != valueConfig.getValue().intValue() ? 0: 1); From c062586c7e59b53c27e6a5b3655e3a7ce0b80673 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 10 Feb 2023 16:51:32 +0800 Subject: [PATCH 120/150] =?UTF-8?q?[Optimize]=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8&=E5=A4=9A=E4=BD=99=E7=9A=84=E6=89=93=E5=8C=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kafka-manager-springboot-distribution.xml | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100755 km-rest/src/main/resources/distribution/kafka-manager-springboot-distribution.xml diff --git a/km-rest/src/main/resources/distribution/kafka-manager-springboot-distribution.xml b/km-rest/src/main/resources/distribution/kafka-manager-springboot-distribution.xml deleted file mode 100755 index 51c9a632..00000000 --- a/km-rest/src/main/resources/distribution/kafka-manager-springboot-distribution.xml +++ /dev/null @@ -1,63 +0,0 @@ - - assembly - - tar - zip - - - - src/main/resources/bin - bin - - control.sh - start.bat - - 0755 - - - src/main/resources - config - - *.properties - *.xml - *.yml - env/dev/* - env/qa/* - env/uat/* - env/prod/* - - - - target - lib - - - kafka-manager-web*.jar - - - - *sources.jar - - - - src/main/resources - logs - 0755 - - **/* - - - - - \ No newline at end of file From b5683b73c22fb3cf6d5e00d3d5f2e0a441bd1ac8 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Fri, 10 Feb 2023 17:01:33 +0800 Subject: [PATCH 121/150] =?UTF-8?q?[Optimize]=E4=BC=98=E5=8C=96=20MySQL=20?= =?UTF-8?q?&=20ES=20=E6=B5=8B=E8=AF=95=E5=AE=B9=E5=99=A8=E7=9A=84=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96(#906)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要的变更 1、knowstreaming/knowstreaming-manager 容器; 2、knowstreaming/knowstreaming-mysql 容器调整为使用 mysql:5.7 容器; 3、初始化 mysql:5.7 容器后,增加初始化 MySQL 表及数据的动作; 被影响的变更: 1、移动 km-dist/init/sql 下的MySQL初始化脚本至 km-persistence/src/main/resource/sql 下,以便项目测试时加载到所需的初始化 SQL; 2、删除无用的 km-dist/init/template 目录; 3、因为 km-dist/init/sql 和 km-dist/init/template 目录的调整,因此也调整 ReleaseKnowStreaming.xml 内的文件内容; --- km-dist/ReleaseKnowStreaming.xml | 18 +- km-dist/init/template/ks_kafka_broker_metric | 101 --------- km-dist/init/template/ks_kafka_cluster_metric | 186 ----------------- .../template/ks_kafka_connect_cluster_metric | 86 -------- .../ks_kafka_connect_connector_metric | 194 ------------------ km-dist/init/template/ks_kafka_group_metric | 74 ------- .../init/template/ks_kafka_partition_metric | 65 ------ km-dist/init/template/ks_kafka_topic_metric | 116 ----------- .../init/template/ks_kafka_zookeeper_metric | 84 -------- .../km/persistence/utils/LoadSQLUtil.java | 55 +++++ .../src/main/resources}/sql/ddl-ks-km.sql | 0 .../src/main/resources}/sql/ddl-logi-job.sql | 0 .../main/resources}/sql/ddl-logi-security.sql | 0 .../src/main/resources}/sql/dml-ks-km.sql | 0 .../src/main/resources}/sql/dml-logi.sql | 0 .../know/streaming/test/KMTestEnvService.java | 8 + .../test/container/es/ESTestContainer.java | 11 +- .../container/mysql/KSMySQLContainer.java | 40 ++++ .../container/mysql/MySQLTestContainer.java | 35 +++- 19 files changed, 148 insertions(+), 925 deletions(-) delete mode 100644 km-dist/init/template/ks_kafka_broker_metric delete mode 100644 km-dist/init/template/ks_kafka_cluster_metric delete mode 100644 km-dist/init/template/ks_kafka_connect_cluster_metric delete mode 100644 km-dist/init/template/ks_kafka_connect_connector_metric delete mode 100644 km-dist/init/template/ks_kafka_group_metric delete mode 100644 km-dist/init/template/ks_kafka_partition_metric delete mode 100644 km-dist/init/template/ks_kafka_topic_metric delete mode 100644 km-dist/init/template/ks_kafka_zookeeper_metric create mode 100644 km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/utils/LoadSQLUtil.java rename {km-dist/init => km-persistence/src/main/resources}/sql/ddl-ks-km.sql (100%) rename {km-dist/init => km-persistence/src/main/resources}/sql/ddl-logi-job.sql (100%) rename {km-dist/init => km-persistence/src/main/resources}/sql/ddl-logi-security.sql (100%) rename {km-dist/init => km-persistence/src/main/resources}/sql/dml-ks-km.sql (100%) rename {km-dist/init => km-persistence/src/main/resources}/sql/dml-logi.sql (100%) diff --git a/km-dist/ReleaseKnowStreaming.xml b/km-dist/ReleaseKnowStreaming.xml index 046a5cfd..37833d4b 100755 --- a/km-dist/ReleaseKnowStreaming.xml +++ b/km-dist/ReleaseKnowStreaming.xml @@ -26,6 +26,22 @@ 0755 + + ../km-persistence/src/main/resources + init + + sql + sql/* + + + + ../km-persistence/src/main/resources/es + init + + template + template/* + + @@ -34,8 +50,6 @@ init/* init/*/* - - packages/* diff --git a/km-dist/init/template/ks_kafka_broker_metric b/km-dist/init/template/ks_kafka_broker_metric deleted file mode 100644 index 749b1494..00000000 --- a/km-dist/init/template/ks_kafka_broker_metric +++ /dev/null @@ -1,101 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_broker_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "NetworkProcessorAvgIdle" : { - "type" : "float" - }, - "UnderReplicatedPartitions" : { - "type" : "float" - }, - "BytesIn_min_15" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "RequestHandlerAvgIdle" : { - "type" : "float" - }, - "connectionsCount" : { - "type" : "float" - }, - "BytesIn_min_5" : { - "type" : "float" - }, - "HealthScore" : { - "type" : "float" - }, - "BytesOut" : { - "type" : "float" - }, - "BytesOut_min_15" : { - "type" : "float" - }, - "BytesIn" : { - "type" : "float" - }, - "BytesOut_min_5" : { - "type" : "float" - }, - "TotalRequestQueueSize" : { - "type" : "float" - }, - "MessagesIn" : { - "type" : "float" - }, - "TotalProduceRequests" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - }, - "TotalResponseQueueSize" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_cluster_metric b/km-dist/init/template/ks_kafka_cluster_metric deleted file mode 100644 index 942fdce2..00000000 --- a/km-dist/init/template/ks_kafka_cluster_metric +++ /dev/null @@ -1,186 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_cluster_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "Connections" : { - "type" : "double" - }, - "BytesIn_min_15" : { - "type" : "double" - }, - "PartitionURP" : { - "type" : "double" - }, - "HealthScore_Topics" : { - "type" : "double" - }, - "EventQueueSize" : { - "type" : "double" - }, - "ActiveControllerCount" : { - "type" : "double" - }, - "GroupDeads" : { - "type" : "double" - }, - "BytesIn_min_5" : { - "type" : "double" - }, - "HealthCheckTotal_Topics" : { - "type" : "double" - }, - "Partitions" : { - "type" : "double" - }, - "BytesOut" : { - "type" : "double" - }, - "Groups" : { - "type" : "double" - }, - "BytesOut_min_15" : { - "type" : "double" - }, - "TotalRequestQueueSize" : { - "type" : "double" - }, - "HealthCheckPassed_Groups" : { - "type" : "double" - }, - "TotalProduceRequests" : { - "type" : "double" - }, - "HealthCheckPassed" : { - "type" : "double" - }, - "TotalLogSize" : { - "type" : "double" - }, - "GroupEmptys" : { - "type" : "double" - }, - "PartitionNoLeader" : { - "type" : "double" - }, - "HealthScore_Brokers" : { - "type" : "double" - }, - "Messages" : { - "type" : "double" - }, - "Topics" : { - "type" : "double" - }, - "PartitionMinISR_E" : { - "type" : "double" - }, - "HealthCheckTotal" : { - "type" : "double" - }, - "Brokers" : { - "type" : "double" - }, - "Replicas" : { - "type" : "double" - }, - "HealthCheckTotal_Groups" : { - "type" : "double" - }, - "GroupRebalances" : { - "type" : "double" - }, - "MessageIn" : { - "type" : "double" - }, - "HealthScore" : { - "type" : "double" - }, - "HealthCheckPassed_Topics" : { - "type" : "double" - }, - "HealthCheckTotal_Brokers" : { - "type" : "double" - }, - "PartitionMinISR_S" : { - "type" : "double" - }, - "BytesIn" : { - "type" : "double" - }, - "BytesOut_min_5" : { - "type" : "double" - }, - "GroupActives" : { - "type" : "double" - }, - "MessagesIn" : { - "type" : "double" - }, - "GroupReBalances" : { - "type" : "double" - }, - "HealthCheckPassed_Brokers" : { - "type" : "double" - }, - "HealthScore_Groups" : { - "type" : "double" - }, - "TotalResponseQueueSize" : { - "type" : "double" - }, - "Zookeepers" : { - "type" : "double" - }, - "LeaderMessages" : { - "type" : "double" - }, - "HealthScore_Cluster" : { - "type" : "double" - }, - "HealthCheckPassed_Cluster" : { - "type" : "double" - }, - "HealthCheckTotal_Cluster" : { - "type" : "double" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "type" : "date" - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_connect_cluster_metric b/km-dist/init/template/ks_kafka_connect_cluster_metric deleted file mode 100644 index 7fa4c523..00000000 --- a/km-dist/init/template/ks_kafka_connect_cluster_metric +++ /dev/null @@ -1,86 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_connect_cluster_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "connectClusterId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "ConnectorCount" : { - "type" : "float" - }, - "TaskCount" : { - "type" : "float" - }, - "ConnectorStartupAttemptsTotal" : { - "type" : "float" - }, - "ConnectorStartupFailurePercentage" : { - "type" : "float" - }, - "ConnectorStartupFailureTotal" : { - "type" : "float" - }, - "ConnectorStartupSuccessPercentage" : { - "type" : "float" - }, - "ConnectorStartupSuccessTotal" : { - "type" : "float" - }, - "TaskStartupAttemptsTotal" : { - "type" : "float" - }, - "TaskStartupFailurePercentage" : { - "type" : "float" - }, - "TaskStartupFailureTotal" : { - "type" : "float" - }, - "TaskStartupSuccessPercentage" : { - "type" : "float" - }, - "TaskStartupSuccessTotal" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_connect_connector_metric b/km-dist/init/template/ks_kafka_connect_connector_metric deleted file mode 100644 index b26836a0..00000000 --- a/km-dist/init/template/ks_kafka_connect_connector_metric +++ /dev/null @@ -1,194 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_connect_connector_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "connectClusterId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "connectorName" : { - "type" : "keyword" - }, - "connectorNameAndClusterId" : { - "type" : "keyword" - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "HealthState" : { - "type" : "float" - }, - "ConnectorTotalTaskCount" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "ConnectorRunningTaskCount" : { - "type" : "float" - }, - "ConnectorPausedTaskCount" : { - "type" : "float" - }, - "ConnectorFailedTaskCount" : { - "type" : "float" - }, - "ConnectorUnassignedTaskCount" : { - "type" : "float" - }, - "BatchSizeAvg" : { - "type" : "float" - }, - "BatchSizeMax" : { - "type" : "float" - }, - "OffsetCommitAvgTimeMs" : { - "type" : "float" - }, - "OffsetCommitMaxTimeMs" : { - "type" : "float" - }, - "OffsetCommitFailurePercentage" : { - "type" : "float" - }, - "OffsetCommitSuccessPercentage" : { - "type" : "float" - }, - "PollBatchAvgTimeMs" : { - "type" : "float" - }, - "PollBatchMaxTimeMs" : { - "type" : "float" - }, - "SourceRecordActiveCount" : { - "type" : "float" - }, - "SourceRecordActiveCountAvg" : { - "type" : "float" - }, - "SourceRecordActiveCountMax" : { - "type" : "float" - }, - "SourceRecordPollRate" : { - "type" : "float" - }, - "SourceRecordPollTotal" : { - "type" : "float" - }, - "SourceRecordWriteRate" : { - "type" : "float" - }, - "SourceRecordWriteTotal" : { - "type" : "float" - }, - "OffsetCommitCompletionRate" : { - "type" : "float" - }, - "OffsetCommitCompletionTotal" : { - "type" : "float" - }, - "OffsetCommitSkipRate" : { - "type" : "float" - }, - "OffsetCommitSkipTotal" : { - "type" : "float" - }, - "PartitionCount" : { - "type" : "float" - }, - "PutBatchAvgTimeMs" : { - "type" : "float" - }, - "PutBatchMaxTimeMs" : { - "type" : "float" - }, - "SinkRecordActiveCount" : { - "type" : "float" - }, - "SinkRecordActiveCountAvg" : { - "type" : "float" - }, - "SinkRecordActiveCountMax" : { - "type" : "float" - }, - "SinkRecordLagMax" : { - "type" : "float" - }, - "SinkRecordReadRate" : { - "type" : "float" - }, - "SinkRecordReadTotal" : { - "type" : "float" - }, - "SinkRecordSendRate" : { - "type" : "float" - }, - "SinkRecordSendTotal" : { - "type" : "float" - }, - "DeadletterqueueProduceFailures" : { - "type" : "float" - }, - "DeadletterqueueProduceRequests" : { - "type" : "float" - }, - "LastErrorTimestamp" : { - "type" : "float" - }, - "TotalErrorsLogged" : { - "type" : "float" - }, - "TotalRecordErrors" : { - "type" : "float" - }, - "TotalRecordFailures" : { - "type" : "float" - }, - "TotalRecordsSkipped" : { - "type" : "float" - }, - "TotalRetries" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_group_metric b/km-dist/init/template/ks_kafka_group_metric deleted file mode 100644 index 24ff12de..00000000 --- a/km-dist/init/template/ks_kafka_group_metric +++ /dev/null @@ -1,74 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_group_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "6" - } - }, - "mappings" : { - "properties" : { - "group" : { - "type" : "keyword" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "HealthScore" : { - "type" : "float" - }, - "Lag" : { - "type" : "float" - }, - "OffsetConsumed" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - } - } - }, - "groupMetric" : { - "type" : "keyword" - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_partition_metric b/km-dist/init/template/ks_kafka_partition_metric deleted file mode 100644 index 51193c22..00000000 --- a/km-dist/init/template/ks_kafka_partition_metric +++ /dev/null @@ -1,65 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_partition_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "6" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "partitionId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "topic" : { - "type" : "keyword" - }, - "metrics" : { - "properties" : { - "LogStartOffset" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "LogEndOffset" : { - "type" : "float" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_topic_metric b/km-dist/init/template/ks_kafka_topic_metric deleted file mode 100644 index 4a1aa70c..00000000 --- a/km-dist/init/template/ks_kafka_topic_metric +++ /dev/null @@ -1,116 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_topic_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "6" - } - }, - "mappings" : { - "properties" : { - "brokerId" : { - "type" : "long" - }, - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "topic" : { - "type" : "keyword" - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "BytesIn_min_15" : { - "type" : "float" - }, - "Messages" : { - "type" : "float" - }, - "BytesRejected" : { - "type" : "float" - }, - "PartitionURP" : { - "type" : "float" - }, - "HealthCheckTotal" : { - "type" : "float" - }, - "ReplicationCount" : { - "type" : "float" - }, - "ReplicationBytesOut" : { - "type" : "float" - }, - "ReplicationBytesIn" : { - "type" : "float" - }, - "FailedFetchRequests" : { - "type" : "float" - }, - "BytesIn_min_5" : { - "type" : "float" - }, - "HealthScore" : { - "type" : "float" - }, - "LogSize" : { - "type" : "float" - }, - "BytesOut" : { - "type" : "float" - }, - "BytesOut_min_15" : { - "type" : "float" - }, - "FailedProduceRequests" : { - "type" : "float" - }, - "BytesIn" : { - "type" : "float" - }, - "BytesOut_min_5" : { - "type" : "float" - }, - "MessagesIn" : { - "type" : "float" - }, - "TotalProduceRequests" : { - "type" : "float" - }, - "HealthCheckPassed" : { - "type" : "float" - } - } - }, - "brokerAgg" : { - "type" : "keyword" - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "index" : true, - "type" : "date", - "doc_values" : true - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-dist/init/template/ks_kafka_zookeeper_metric b/km-dist/init/template/ks_kafka_zookeeper_metric deleted file mode 100644 index 10c9324b..00000000 --- a/km-dist/init/template/ks_kafka_zookeeper_metric +++ /dev/null @@ -1,84 +0,0 @@ -{ - "order" : 10, - "index_patterns" : [ - "ks_kafka_zookeeper_metric*" - ], - "settings" : { - "index" : { - "number_of_shards" : "2" - } - }, - "mappings" : { - "properties" : { - "routingValue" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "clusterPhyId" : { - "type" : "long" - }, - "metrics" : { - "properties" : { - "AvgRequestLatency" : { - "type" : "double" - }, - "MinRequestLatency" : { - "type" : "double" - }, - "MaxRequestLatency" : { - "type" : "double" - }, - "OutstandingRequests" : { - "type" : "double" - }, - "NodeCount" : { - "type" : "double" - }, - "WatchCount" : { - "type" : "double" - }, - "NumAliveConnections" : { - "type" : "double" - }, - "PacketsReceived" : { - "type" : "double" - }, - "PacketsSent" : { - "type" : "double" - }, - "EphemeralsCount" : { - "type" : "double" - }, - "ApproximateDataSize" : { - "type" : "double" - }, - "OpenFileDescriptorCount" : { - "type" : "double" - }, - "MaxFileDescriptorCount" : { - "type" : "double" - } - } - }, - "key" : { - "type" : "text", - "fields" : { - "keyword" : { - "ignore_above" : 256, - "type" : "keyword" - } - } - }, - "timestamp" : { - "format" : "yyyy-MM-dd HH:mm:ss Z||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS Z||yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss,SSS||yyyy/MM/dd HH:mm:ss||yyyy-MM-dd HH:mm:ss,SSS Z||yyyy/MM/dd HH:mm:ss,SSS Z||epoch_millis", - "type" : "date" - } - } - }, - "aliases" : { } - } \ No newline at end of file diff --git a/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/utils/LoadSQLUtil.java b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/utils/LoadSQLUtil.java new file mode 100644 index 00000000..50d4e1f3 --- /dev/null +++ b/km-persistence/src/main/java/com/xiaojukeji/know/streaming/km/persistence/utils/LoadSQLUtil.java @@ -0,0 +1,55 @@ +package com.xiaojukeji.know.streaming.km.persistence.utils; + +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + + +public class LoadSQLUtil { + private static final ILog LOGGER = LogFactory.getLog(LoadSQLUtil.class); + + public static final String SQL_DDL_KS_KM = "sql/ddl-ks-km.sql"; + public static final String SQL_DDL_LOGI_JOB = "sql/ddl-logi-job.sql"; + public static final String SQL_DDL_LOGI_SECURITY = "sql/ddl-logi-security.sql"; + public static final String SQL_DML_KS_KM = "sql/dml-ks-km.sql"; + public static final String SQL_DML_LOGI = "sql/dml-logi.sql"; + + public static String loadSQL(String sqlFileName) { + InputStream inputStream = LoadSQLUtil.class.getClassLoader().getResourceAsStream(sqlFileName); + if (inputStream == null) { + LOGGER.error("method=loadSQL||fileName={}||msg=read script failed", sqlFileName); + return ""; + } + + try { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line = null; + + StringBuilder sb = new StringBuilder(); + while ((line = bufferedReader.readLine()) != null) { + sb.append(line).append("\n"); + } + + return sb.toString(); + } catch (Exception e) { + LOGGER.error("method=loadSQL||fileName={}||errMsg={}||msg=read script failed", sqlFileName, e.getMessage()); + } finally { + try { + inputStream.close(); + + + } catch (IOException e) { + LOGGER.error("method=loadSQL||fileName={}||errMsg={}||msg=close reading script failed", sqlFileName, e.getMessage()); + } + } + + return ""; + } + + private LoadSQLUtil() { + } +} diff --git a/km-dist/init/sql/ddl-ks-km.sql b/km-persistence/src/main/resources/sql/ddl-ks-km.sql similarity index 100% rename from km-dist/init/sql/ddl-ks-km.sql rename to km-persistence/src/main/resources/sql/ddl-ks-km.sql diff --git a/km-dist/init/sql/ddl-logi-job.sql b/km-persistence/src/main/resources/sql/ddl-logi-job.sql similarity index 100% rename from km-dist/init/sql/ddl-logi-job.sql rename to km-persistence/src/main/resources/sql/ddl-logi-job.sql diff --git a/km-dist/init/sql/ddl-logi-security.sql b/km-persistence/src/main/resources/sql/ddl-logi-security.sql similarity index 100% rename from km-dist/init/sql/ddl-logi-security.sql rename to km-persistence/src/main/resources/sql/ddl-logi-security.sql diff --git a/km-dist/init/sql/dml-ks-km.sql b/km-persistence/src/main/resources/sql/dml-ks-km.sql similarity index 100% rename from km-dist/init/sql/dml-ks-km.sql rename to km-persistence/src/main/resources/sql/dml-ks-km.sql diff --git a/km-dist/init/sql/dml-logi.sql b/km-persistence/src/main/resources/sql/dml-logi.sql similarity index 100% rename from km-dist/init/sql/dml-logi.sql rename to km-persistence/src/main/resources/sql/dml-logi.sql diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java index acae3123..132fb4db 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/KMTestEnvService.java @@ -42,8 +42,16 @@ public abstract class KMTestEnvService { @DynamicPropertySource static void setUp(DynamicPropertyRegistry registry) { registry.add("spring.datasource.know-streaming.jdbc-url", mySQLTestContainer.jdbcUrl()); + registry.add("spring.datasource.know-streaming.username", mySQLTestContainer.jdbcUsername()); + registry.add("spring.datasource.know-streaming.password", mySQLTestContainer.jdbcPassword()); + registry.add("spring.logi-job.jdbc-url", mySQLTestContainer.jdbcUrl()); + registry.add("spring.logi-job.username", mySQLTestContainer.jdbcUsername()); + registry.add("spring.logi-job.password", mySQLTestContainer.jdbcPassword()); + registry.add("spring.logi-security.jdbc-url", mySQLTestContainer.jdbcUrl()); + registry.add("spring.logi-security.username", mySQLTestContainer.jdbcUsername()); + registry.add("spring.logi-security.password", mySQLTestContainer.jdbcPassword()); registry.add("es.client.address", esTestContainer.esUrl()); } diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java index 2a297252..3ba7e99c 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/es/ESTestContainer.java @@ -2,7 +2,6 @@ package com.xiaojukeji.know.streaming.test.container.es; import com.xiaojukeji.know.streaming.test.container.BaseTestContainer; import org.jetbrains.annotations.NotNull; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.lifecycle.Startables; import org.testcontainers.utility.DockerImageName; @@ -19,14 +18,6 @@ public class ESTestContainer extends BaseTestContainer { .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") .withEnv("discovery.type", "single-node"); - // km容器,需要初始化es索引模版 - private static final GenericContainer INIT_CONTAINER = new GenericContainer<>( - "knowstreaming/knowstreaming-manager:latest" - ) - .withEnv("TZ", "Asia/Shanghai") - .withCommand("/bin/bash", "/es_template_create.sh") - .dependsOn(ES_CONTAINER); - @NotNull public Supplier esUrl() { return () -> ES_CONTAINER.getHost() + ":" + ES_CONTAINER.getMappedPort(9200); @@ -34,7 +25,7 @@ public class ESTestContainer extends BaseTestContainer { @Override public void init() { - Startables.deepStart(ES_CONTAINER, INIT_CONTAINER).join(); + Startables.deepStart(ES_CONTAINER).join(); } @Override diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java index a656e6d9..56a32a7c 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/KSMySQLContainer.java @@ -1,11 +1,20 @@ package com.xiaojukeji.know.streaming.test.container.mysql; +import com.didiglobal.logi.log.ILog; +import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.utils.Tuple; import org.jetbrains.annotations.NotNull; import org.testcontainers.containers.ContainerLaunchException; import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.delegate.DatabaseDelegate; +import org.testcontainers.ext.ScriptUtils; +import org.testcontainers.jdbc.JdbcDatabaseDelegate; import org.testcontainers.utility.DockerImageName; +import javax.script.ScriptException; +import java.util.ArrayList; +import java.util.List; import java.util.Set; /** @@ -13,6 +22,8 @@ import java.util.Set; * @see org.testcontainers.containers.MySQLContainer */ public class KSMySQLContainer> extends JdbcDatabaseContainer { + private static final ILog LOGGER = LogFactory.getLog(KSMySQLContainer.class); + public static final String NAME = "mysql"; private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mysql"); @@ -39,6 +50,8 @@ public class KSMySQLContainer> extends JdbcD private static final String MYSQL_ROOT_USER = "root"; + private List> initScriptPathAndContentList = new ArrayList<>(); + /** * @deprecated use {@link MySQLContainer(DockerImageName)} instead */ @@ -169,4 +182,31 @@ public class KSMySQLContainer> extends JdbcD this.password = password; return self(); } + + public SELF addInitScriptPathAndContent(String initScriptPath, String initScriptContent) { + initScriptPathAndContentList.add(new Tuple<>(initScriptPath, initScriptContent)); + return self(); + } + + // KS改动的地方 + @Override + public DatabaseDelegate getDatabaseDelegate() { + return new JdbcDatabaseDelegate(this, ""); + } + + @Override + protected void runInitScriptIfRequired() { + if (initScriptPathAndContentList.isEmpty()) { + return; + } + + for (Tuple elem: initScriptPathAndContentList) { + try { + ScriptUtils.executeDatabaseScript(this.getDatabaseDelegate(), elem.getV1(), elem.getV2()); + } catch (ScriptException var5) { + LOGGER.error("Error while executing init script: {}", elem.getV1(), var5); + throw new ScriptUtils.UncategorizedScriptException("Error while executing init script: " + elem.getV2(), var5); + } + } + } } diff --git a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java index d232940a..e65e0ec8 100644 --- a/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java +++ b/km-rest/src/test/java/com/xiaojukeji/know/streaming/test/container/mysql/MySQLTestContainer.java @@ -1,5 +1,6 @@ package com.xiaojukeji.know.streaming.test.container.mysql; +import com.xiaojukeji.know.streaming.km.persistence.utils.LoadSQLUtil; import com.xiaojukeji.know.streaming.test.container.BaseTestContainer; import org.jetbrains.annotations.NotNull; import org.testcontainers.lifecycle.Startables; @@ -8,7 +9,12 @@ import org.testcontainers.utility.DockerImageName; import java.util.function.Supplier; public class MySQLTestContainer extends BaseTestContainer { - private static final String DB_PROPERTY = "?useUnicode=true" + + private static final String DB_USERNAME = "root"; + private static final String DB_PASSWORD = "1234567890"; + + private static final String DATABASE_NAME = "know_streaming"; + + private static final String DB_PROPERTY = "?useUnicode=true" + "&characterEncoding=utf8" + "&jdbcCompliantTruncation=true" + "&allowMultiQueries=true" + @@ -18,19 +24,34 @@ public class MySQLTestContainer extends BaseTestContainer { "&allowPublicKeyRetrieval=true"; private static final KSMySQLContainer MYSQL_CONTAINER = new KSMySQLContainer<>( - DockerImageName.parse("knowstreaming/knowstreaming-mysql:latest").asCompatibleSubstituteFor("mysql") + DockerImageName.parse("mysql:5.7").asCompatibleSubstituteFor("mysql") ) - .withEnv("MYSQL_ROOT_HOST", "%") .withEnv("TZ", "Asia/Shanghai") - .withDatabaseName("know_streaming") - .withUsername("root") - .withPassword("mysql_pass"); + .withDatabaseName(DATABASE_NAME) + .withUsername(DB_USERNAME) + .withPassword(DB_PASSWORD) + .addInitScriptPathAndContent(LoadSQLUtil.SQL_DDL_KS_KM, String.format("use %s;\n%s", DATABASE_NAME, LoadSQLUtil.loadSQL(LoadSQLUtil.SQL_DDL_KS_KM))) + .addInitScriptPathAndContent(LoadSQLUtil.SQL_DDL_LOGI_JOB, String.format("use %s;\n%s", DATABASE_NAME, LoadSQLUtil.loadSQL(LoadSQLUtil.SQL_DDL_LOGI_JOB))) + .addInitScriptPathAndContent(LoadSQLUtil.SQL_DDL_LOGI_SECURITY, String.format("use %s;\n%s", DATABASE_NAME, LoadSQLUtil.loadSQL(LoadSQLUtil.SQL_DDL_LOGI_SECURITY))) + .addInitScriptPathAndContent(LoadSQLUtil.SQL_DML_KS_KM, String.format("use %s;\n%s", DATABASE_NAME, LoadSQLUtil.loadSQL(LoadSQLUtil.SQL_DML_KS_KM))) + .addInitScriptPathAndContent(LoadSQLUtil.SQL_DML_LOGI, String.format("use %s;\n%s", DATABASE_NAME, LoadSQLUtil.loadSQL(LoadSQLUtil.SQL_DML_LOGI))) + ; + + @NotNull + public Supplier jdbcUsername() { + return () -> DB_USERNAME; + } + + @NotNull + public Supplier jdbcPassword() { + return () -> DB_PASSWORD; + } @NotNull public Supplier jdbcUrl() { return () -> "jdbc:mariadb://" + MYSQL_CONTAINER.getHost() + ":" + MYSQL_CONTAINER.getMappedPort(3306) - + "/know_streaming" + DB_PROPERTY; + + "/" + DATABASE_NAME + DB_PROPERTY; } @Override From f03460f3cd984e0a7981d4ba018bb098c1e749d2 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 13 Feb 2023 11:18:29 +0800 Subject: [PATCH 122/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=20Broker=20S?= =?UTF-8?q?imilar=20Config=20=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98(#872)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/biz/cluster/impl/ClusterBrokersManagerImpl.java | 3 ++- .../know/streaming/km/common/constant/KafkaConstant.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java index 7f98e86f..ab5d6a6d 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterBrokersManagerImpl.java @@ -140,7 +140,8 @@ public class ClusterBrokersManagerImpl implements ClusterBrokersManager { clusterBrokersStateVO.setKafkaControllerAlive(true); } - clusterBrokersStateVO.setConfigSimilar(brokerConfigService.countBrokerConfigDiffsFromDB(clusterPhyId, Arrays.asList("broker.id", "listeners", "name", "value")) <= 0); + clusterBrokersStateVO.setConfigSimilar(brokerConfigService.countBrokerConfigDiffsFromDB(clusterPhyId, KafkaConstant.CONFIG_SIMILAR_IGNORED_CONFIG_KEY_LIST) <= 0 + ); return clusterBrokersStateVO; } diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java index b7d6ffaf..465f6f8a 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/constant/KafkaConstant.java @@ -45,6 +45,8 @@ public class KafkaConstant { public static final String DEFAULT_CONNECT_VERSION = "2.5.0"; + public static final List CONFIG_SIMILAR_IGNORED_CONFIG_KEY_LIST = Arrays.asList("broker.id", "listeners", "name", "value", "advertised.listeners", "node.id"); + public static final Map KAFKA_ALL_CONFIG_DEF_MAP = new ConcurrentHashMap<>(); static { From fa2abadc25322ac3a0cd8a68ee137ef2d5d838ee Mon Sep 17 00:00:00 2001 From: wyb Date: Fri, 10 Feb 2023 16:39:47 +0800 Subject: [PATCH 123/150] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EMirror=20Make?= =?UTF-8?q?r=202.0(MM2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/api/index.ts | 38 +- .../components/CardBar/MirrorMakerCard.tsx | 119 ++ .../CardBar/MirrorMakerDetailCard.tsx | 145 ++ .../src/components/CardBar/index.tsx | 9 +- .../ChartOperateBar/MetricSelect.tsx | 16 +- .../src/components/DraggableCharts/Detail.tsx | 8 +- .../src/components/DraggableCharts/config.tsx | 1 + .../src/components/DraggableCharts/index.tsx | 102 +- .../layout-clusters-fe/src/constants/menu.tsx | 25 +- .../layout-clusters-fe/src/locales/zh.tsx | 4 + .../src/pages/CommonConfig.tsx | 8 + .../src/pages/MirrorMaker2/AddMM2.tsx | 1239 +++++++++++++++++ .../src/pages/MirrorMaker2/AddMM2JSON.tsx | 266 ++++ .../src/pages/MirrorMaker2/Delete.tsx | 99 ++ .../src/pages/MirrorMaker2/Detail.tsx | 185 +++ .../src/pages/MirrorMaker2/config.tsx | 344 +++++ .../src/pages/MirrorMaker2/index.less | 265 ++++ .../src/pages/MirrorMaker2/index.tsx | 240 ++++ .../src/pages/pageRoutes.ts | 15 + 19 files changed, 3096 insertions(+), 32 deletions(-) create mode 100644 km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerCard.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerDetailCard.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Delete.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Detail.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/config.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.less create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.tsx diff --git a/km-console/packages/layout-clusters-fe/src/api/index.ts b/km-console/packages/layout-clusters-fe/src/api/index.ts index 4d185ea9..1c6bc77e 100755 --- a/km-console/packages/layout-clusters-fe/src/api/index.ts +++ b/km-console/packages/layout-clusters-fe/src/api/index.ts @@ -18,6 +18,7 @@ export enum MetricType { Connect = 120, Connectors = 121, Controls = 901, + MM2 = 122, } const api = { @@ -233,9 +234,9 @@ const api = { getConnectors: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/connectors-basic`), getConnectorMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/connectors-metrics`), getConnectorPlugins: (connectClusterId: number) => getApi(`/kafka-connect/clusters/${connectClusterId}/connector-plugins`), - getConnectorPluginConfig: (connectClusterId: number, pluginName: string) => + getConnectorPluginConfig: (connectClusterId: number | string, pluginName: string) => getApi(`/kafka-connect/clusters/${connectClusterId}/connector-plugins/${pluginName}/config`), - getCurPluginConfig: (connectClusterId: number, connectorName: string) => + getCurPluginConfig: (connectClusterId: number | string, connectorName: string) => getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/config`), isConnectorExist: (connectClusterId: number, connectorName: string) => getApi(`/kafka-connect/clusters/${connectClusterId}/connectors/${connectorName}/basic-combine-exist`), @@ -251,6 +252,39 @@ const api = { getConnectClusterBasicExit: (clusterPhyId: string, clusterPhyName: string) => getApi(`/kafka-clusters/${clusterPhyId}/connect-clusters/${clusterPhyName}/basic-combine-exist`), + + // MM2 列表 + getMirrorMakerList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/mirror-makers-overview`), + // MM2 状态卡片 + getMirrorMakerState: (clusterPhyId: string) => getApi(`/kafka-clusters/${clusterPhyId}/mirror-makers-state`), + // MM2 指标卡片 + getMirrorMakerMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/mirror-makers-metrics`), + // MM2 筛选 + getMirrorMakerMetadata: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/mirror-makers-basic`), + // MM2 详情列表 + getMM2DetailTasks: (connectorName: number | string, connectClusterId: number | string) => + getApi(`/kafka-mm2/clusters/${connectClusterId}/connectors/${connectorName}/tasks`), + // MM2 详情状态卡片 + getMM2DetailState: (connectorName: number | string, connectClusterId: number | string) => + getApi(`/kafka-mm2/clusters/${connectClusterId}/connectors/${connectorName}/state`), + // MM2 操作接口 新增、暂停、重启、删除 + mirrorMakerOperates: getApi('/kafka-mm2/mirror-makers'), + // MM2 操作接口 新增、编辑校验 + validateMM2Config: getApi('/kafka-mm2/mirror-makers-config/validate'), + // 修改 Connector 配置 + updateMM2Config: getApi('/kafka-mm2/mirror-makers-config'), + // MM2 详情 + getMirrorMakerMetricPoints: (mirrorMakerName: number | string, connectClusterId: number | string) => + getApi(`/kafka-mm2/clusters/${connectClusterId}/connectors/${mirrorMakerName}/latest-metrics`), + getSourceKafkaClusterBasic: getApi(`/physical-clusters/basic`), + getGroupBasic: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/groups-basic`), + // Topic复制 + getMirrorClusterList: () => getApi(`/ha-mirror/physical-clusters/basic`), + handleTopicMirror: () => getApi(`/ha-mirror/topics`), + getTopicMirrorList: (clusterPhyId: number, topicName: string) => + getApi(`/ha-mirror/clusters/${clusterPhyId}/topics/${topicName}/mirror-info`), + getMirrorMakerConfig: (connectClusterId: number | string, connectorName: string) => + getApi(`/kafka-mm2/clusters/${connectClusterId}/connectors/${connectorName}/config`), }; export default api; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerCard.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerCard.tsx new file mode 100644 index 00000000..f7489e75 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerCard.tsx @@ -0,0 +1,119 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import CardBar, { healthDataProps } from './index'; +import { Tooltip, Utils } from 'knowdesign'; +import api from '@src/api'; +import { HealthStateEnum } from '../HealthState'; +import { InfoCircleOutlined } from '@ant-design/icons'; + +interface MM2State { + workerCount: number; + aliveConnectorCount: number; + aliveTaskCount: number; + healthCheckPassed: number; + healthCheckTotal: number; + healthState: number; + totalConnectorCount: string; + totalTaskCount: number; + totalServerCount: number; + mirrorMakerCount: number; +} + +const getVal = (val: string | number | undefined | null) => { + return val === undefined || val === null || val === '' ? '0' : val; +}; + +const ConnectCard = ({ state }: { state?: boolean }) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [loading, setLoading] = useState(false); + const [cardData, setCardData] = useState([]); + const [healthData, setHealthData] = useState({ + state: HealthStateEnum.UNKNOWN, + passed: 0, + total: 0, + }); + + const getHealthData = () => { + return Utils.post(api.getMetricPointsLatest(Number(clusterId)), [ + 'HealthCheckPassed_MirrorMaker', + 'HealthCheckTotal_MirrorMaker', + 'HealthState_MirrorMaker', + ]).then((data: any) => { + setHealthData({ + state: data?.metrics?.['HealthState_MirrorMaker'], + passed: data?.metrics?.['HealthCheckPassed_MirrorMaker'] || 0, + total: data?.metrics?.['HealthCheckTotal_MirrorMaker'] || 0, + }); + }); + }; + + const getCardInfo = () => { + return Utils.request(api.getMirrorMakerState(clusterId)).then((res: MM2State) => { + const { mirrorMakerCount, aliveConnectorCount, aliveTaskCount, totalConnectorCount, totalTaskCount, workerCount } = res || {}; + const cardMap = [ + { + title: 'MM2s', + value: getVal(mirrorMakerCount), + customStyle: { + // 自定义cardbar样式 + marginLeft: 0, + }, + }, + { + title: 'Workers', + value: getVal(workerCount), + }, + { + title() { + return ( +
+ Connectors + + + +
+ ); + }, + value() { + return ( + + {getVal(aliveConnectorCount)}/{getVal(totalConnectorCount)} + + ); + }, + }, + { + title() { + return ( +
+ Tasks + + + +
+ ); + }, + value() { + return ( + + {getVal(aliveTaskCount)}/{getVal(totalTaskCount)} + + ); + }, + }, + ]; + setCardData(cardMap); + }); + }; + useEffect(() => { + setLoading(true); + Promise.all([getHealthData(), getCardInfo()]).finally(() => { + setLoading(false); + }); + }, [clusterId, state]); + return ; +}; + +export default ConnectCard; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerDetailCard.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerDetailCard.tsx new file mode 100644 index 00000000..743e2e12 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/MirrorMakerDetailCard.tsx @@ -0,0 +1,145 @@ +/* eslint-disable react/display-name */ +import React, { useState, useEffect } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; +import CardBar from '@src/components/CardBar'; +import { healthDataProps } from '.'; +import { Tooltip, Utils } from 'knowdesign'; +import Api from '@src/api'; +import { hashDataParse } from '@src/constants/common'; +import { HealthStateEnum } from '../HealthState'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { stateEnum } from '@src/pages/Connect/config'; +const getVal = (val: string | number | undefined | null) => { + return val === undefined || val === null || val === '' ? '0' : val; +}; + +const ConnectDetailCard = (props: { record: any; tabSelectType: string }) => { + const { record, tabSelectType } = props; + const urlParams = useParams<{ clusterId: string; brokerId: string }>(); + const urlLocation = useLocation(); + const [loading, setLoading] = useState(false); + const [cardData, setCardData] = useState([]); + const [healthData, setHealthData] = useState({ + state: HealthStateEnum.UNKNOWN, + passed: 0, + total: 0, + }); + + const getHealthData = (tabSelectTypeName: string) => { + return Utils.post(Api.getMirrorMakerMetricPoints(tabSelectTypeName, record?.connectClusterId), [ + 'HealthState', + 'HealthCheckPassed', + 'HealthCheckTotal', + ]).then((data: any) => { + setHealthData({ + state: data?.metrics?.['HealthState'], + passed: data?.metrics?.['HealthCheckPassed'] || 0, + total: data?.metrics?.['HealthCheckTotal'] || 0, + }); + }); + }; + + const getCardInfo = (tabSelectTypeName: string) => { + return Utils.request(Api.getConnectDetailState(tabSelectTypeName, record?.connectClusterId)).then((res: any) => { + const { type, aliveTaskCount, state, totalTaskCount, totalWorkerCount } = res || {}; + const cordRightMap = [ + { + title: 'Status', + // value: Utils.firstCharUppercase(state) || '-', + value: () => { + return ( + <> + { + + {Utils.firstCharUppercase(state) || '-'} + + } + + ); + }, + }, + + { + title() { + return ( +
+ Tasks + + + +
+ ); + }, + value() { + return ( + + {getVal(aliveTaskCount)}/{getVal(totalTaskCount)} + + ); + }, + }, + { + title: 'Workers', + value: getVal(totalWorkerCount), + }, + ]; + setCardData(cordRightMap); + }); + }; + + const noDataCardInfo = () => { + const cordRightMap = [ + { + title: 'Status', + // value: Utils.firstCharUppercase(state) || '-', + value() { + return -; + }, + }, + + { + title() { + return ( +
+ Tasks + + + +
+ ); + }, + value() { + return -/-; + }, + }, + { + title: 'Workers', + value() { + return -; + }, + }, + ]; + setCardData(cordRightMap); + }; + + useEffect(() => { + setLoading(true); + + const filterCardInfo = + tabSelectType === 'MirrorCheckpoint' && record.checkpointConnector + ? getCardInfo(record.checkpointConnector) + : tabSelectType === 'MirrorHeatbeat' && record.heartbeatConnector + ? getCardInfo(record.heartbeatConnector) + : tabSelectType === 'MirrorSource' && record.connectorName + ? getCardInfo(record.connectorName) + : noDataCardInfo(); + Promise.all([getHealthData(record.connectorName), filterCardInfo]).finally(() => { + setLoading(false); + }); + }, [record, tabSelectType]); + return ( + + ); +}; + +export default ConnectDetailCard; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx index f35de692..ea683e07 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx @@ -18,7 +18,7 @@ export interface CardBarProps { cardColumns?: any[]; healthData?: healthDataProps; showCardBg?: boolean; - scene: 'topics' | 'brokers' | 'topic' | 'broker' | 'group' | 'zookeeper' | 'connect' | 'connector'; + scene: 'topics' | 'brokers' | 'topic' | 'broker' | 'group' | 'zookeeper' | 'connect' | 'connector' | 'mm2'; record?: any; loading?: boolean; needProgress?: boolean; @@ -67,6 +67,11 @@ const sceneCodeMap = { fieldName: 'connectorName', alias: 'Connector', }, + mm2: { + code: 7, + fieldName: 'connectorName', + alias: 'MM2', + }, }; const CardColumnsItem: any = (cardItem: any) => { const { cardColumnsItemData, showCardBg } = cardItem; @@ -108,7 +113,7 @@ const CardBar = (props: CardBarProps) => { const sceneObj = sceneCodeMap[scene]; const path = record ? api.getResourceHealthDetail( - scene === 'connector' ? Number(record?.connectClusterId) : Number(routeParams.clusterId), + scene === 'connector' || scene === 'mm2' ? Number(record?.connectClusterId) : Number(routeParams.clusterId), sceneObj.code, record[sceneObj.fieldName] ) diff --git a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx index 628275cd..3ce20f3c 100644 --- a/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/ChartOperateBar/MetricSelect.tsx @@ -89,7 +89,15 @@ export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) => const columns = [ { - title: `${pathname.endsWith('/broker') ? 'Broker' : pathname.endsWith('/topic') ? 'Topic' : 'Cluster'} Metrics`, + title: `${ + pathname.endsWith('/broker') + ? 'Broker' + : pathname.endsWith('/topic') + ? 'Topic' + : pathname.endsWith('/replication') + ? 'MM2' + : 'Cluster' + } Metrics`, dataIndex: 'category', key: 'category', }, @@ -112,7 +120,7 @@ export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) => desc, unit: metricDefine?.unit, }; - if (metricDefine.category) { + if (metricDefine?.category) { if (!categoryData[metricDefine.category]) { categoryData[metricDefine.category] = [returnData]; } else { @@ -129,11 +137,11 @@ export const MetricSelect = forwardRef((metricSelect: MetricSelectProps, ref) => }; const formateSelectedKeys = () => { - const newKeys = metricSelect.selectedRows; + const newKeys = metricSelect?.selectedRows; const result: SelectedMetrics = {}; const selectedCategories: string[] = []; - newKeys.forEach((name: string) => { + newKeys?.forEach((name: string) => { const metricDefine = global.getMetricDefine(metricSelect?.metricType, name); if (metricDefine) { if (!result[metricDefine.category]) { diff --git a/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/Detail.tsx b/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/Detail.tsx index 8b04205a..c635d0e0 100644 --- a/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/Detail.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/Detail.tsx @@ -167,6 +167,9 @@ const ChartDetail = (props: ChartDetailProps) => { const getMetricChartData = ([startTime, endTime]: readonly [number, number]) => { const getQueryUrl = () => { switch (metricType) { + case MetricType.MM2: { + return api.getMirrorMakerMetrics(clusterId); + } case MetricType.Connect: { return api.getConnectClusterMetrics(clusterId); } @@ -180,13 +183,16 @@ const ChartDetail = (props: ChartDetailProps) => { [MetricType.Topic]: 'topics', [MetricType.Connect]: 'connectClusterIdList', [MetricType.Connectors]: 'connectorNameList', + [MetricType.MM2]: 'connectorNameList', }; + return Utils.post(getQueryUrl(), { startTime, endTime, metricsNames: [metricName], topNu: null, - [queryMap[metricType as keyof typeof queryMap]]: queryLines, + [queryMap[metricType as keyof typeof queryMap]]: + metricType === MetricType.MM2 ? queryLines.map((item) => (typeof item === 'string' ? Utils.parseJSON(item) : item)) : queryLines, }); }; diff --git a/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/config.tsx b/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/config.tsx index 3a60eca4..87da8dab 100644 --- a/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/config.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/config.tsx @@ -5,6 +5,7 @@ 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.MM2]: (clusterId: string) => api.getMirrorMakerMetrics(clusterId), }; export const getMetricDashboardReq = (clusterId: string, type: MetricType.Broker | MetricType.Topic | MetricType.Zookeeper) => diff --git a/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/index.tsx b/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/index.tsx index 83422932..52e82935 100644 --- a/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/DraggableCharts/index.tsx @@ -150,18 +150,35 @@ const DraggableCharts = (props: PropsType): JSX.Element => { // 获取节点范围列表 const getScopeList = async () => { - const res: any = await Utils.request(api.getDashboardMetadata(clusterId, dashboardType)); - const list = res.map((item: any) => { - return dashboardType === MetricType.Broker - ? { - label: item.host, - value: item.brokerId, - } - : { - label: item.topicName, - value: item.topicName, - }; - }); + const res: any = await Utils.request( + dashboardType !== MetricType.MM2 ? api.getDashboardMetadata(clusterId, dashboardType) : api.getMirrorMakerMetadata(clusterId) + ); + const mockRes = [{ connectClusterId: 1, connectClusterName: 'connectClusterName', connectorName: 'connectorName' }]; + const list = + res.length > 0 + ? res.map((item: any) => { + return dashboardType === MetricType.Broker + ? { + label: item.host, + value: item.brokerId, + } + : dashboardType === MetricType.MM2 + ? { + label: item.connectorName, + value: JSON.stringify({ connectClusterId: item.connectClusterId, connectorName: item.connectorName }), + } + : { + label: item.topicName, + value: item.topicName, + }; + }) + : mockRes.map((item) => { + return { + label: item.connectorName, + value: JSON.stringify(item), + }; + }); + setScopeList(list); }; @@ -172,19 +189,23 @@ const DraggableCharts = (props: PropsType): JSX.Element => { const [startTime, endTime] = curHeaderOptions.rangeTime; const curTimestamp = Date.now(); curFetchingTimestamp.current = curTimestamp; - const reqBody = Object.assign( { startTime, endTime, metricsNames: metricList || [], }, - dashboardType === MetricType.Broker || dashboardType === MetricType.Topic + dashboardType === MetricType.Broker || dashboardType === MetricType.Topic || dashboardType === MetricType.MM2 ? { topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null, - [dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop - ? null - : curHeaderOptions.scopeData.data, + [dashboardType === MetricType.Broker ? 'brokerIds' : dashboardType === MetricType.MM2 ? 'connectorNameList' : 'topics']: + curHeaderOptions?.scopeData?.isTop + ? null + : dashboardType === MetricType.MM2 + ? curHeaderOptions.scopeData.data?.map((item: any) => { + return JSON.parse(item); + }) + : curHeaderOptions.scopeData.data, } : {} ); @@ -207,10 +228,31 @@ const DraggableCharts = (props: PropsType): JSX.Element => { dashboardType, curHeaderOptions.rangeTime ) as FormattedMetricData[]; + // todo 将指标筛选选中但是没有返回的指标插入chartData中 + const nullformattedMetricData: any = []; + + metricList?.forEach((item) => { + if (formattedMetricData && formattedMetricData.some((key) => item === key.metricName)) { + nullformattedMetricData.push(null); + } else { + const chartData: any = { + metricName: item, + metricType: dashboardType, + metricUnit: global.getMetricDefine(dashboardType, item)?.unit || '', + metricLines: [], + showLegend: false, + targetUnit: undefined, + }; + nullformattedMetricData.push(chartData); + } + }); // 指标排序 formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName)); - - setMetricChartData(formattedMetricData); + const filterNullformattedMetricData = nullformattedMetricData.filter((item: any) => item !== null); + filterNullformattedMetricData.sort( + (a: any, b: any) => metricRankList.current.indexOf(a?.metricName) - metricRankList.current.indexOf(b?.metricName) + ); + setMetricChartData([...formattedMetricData, ...filterNullformattedMetricData]); } setLoading(false); }, @@ -255,12 +297,15 @@ const DraggableCharts = (props: PropsType): JSX.Element => { useEffect(() => { if (metricList?.length && curHeaderOptions) { getMetricChartData(); + } else { + setMetricChartData([]); + setLoading(false); } }, [curHeaderOptions, metricList]); useEffect(() => { // 初始化页面,获取 scope 和 metric 信息 - (dashboardType === MetricType.Broker || dashboardType === MetricType.Topic) && getScopeList(); + (dashboardType === MetricType.Broker || dashboardType === MetricType.Topic || dashboardType === MetricType.MM2) && getScopeList(); }, []); return ( @@ -270,11 +315,24 @@ const DraggableCharts = (props: PropsType): JSX.Element => { hideNodeScope={dashboardType === MetricType.Zookeeper} openMetricFilter={() => metricFilterRef.current?.open()} nodeSelect={{ - name: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper', + name: + dashboardType === MetricType.Broker + ? 'Broker' + : dashboardType === MetricType.Topic + ? 'Topic' + : dashboardType === MetricType.MM2 + ? 'MM2' + : 'Zookeeper', customContent: ( diff --git a/km-console/packages/layout-clusters-fe/src/constants/menu.tsx b/km-console/packages/layout-clusters-fe/src/constants/menu.tsx index d0c0b592..47d84df4 100755 --- a/km-console/packages/layout-clusters-fe/src/constants/menu.tsx +++ b/km-console/packages/layout-clusters-fe/src/constants/menu.tsx @@ -79,7 +79,6 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({ return (
{intl.formatMessage({ id: 'menu.cluster.connect' })} -
); }, @@ -103,6 +102,30 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({ }, ].filter((m) => m), }, + { + name: (intl: any) => { + return ( +
+ {intl.formatMessage({ id: 'menu.cluster.replication' })} +
+
+ ); + }, + path: 'replication', + icon: 'icon-Operation', + children: [ + { + name: (intl: any) => {intl.formatMessage({ id: 'menu.cluster.replication.dashboard' })}, + path: '', + icon: 'icon-luoji', + }, + { + name: (intl: any) => {intl.formatMessage({ id: 'menu.cluster.replication.mirror-maker' })}, + path: 'mirror-maker', + icon: '#icon-luoji', + }, + ].filter((m) => m), + }, { name: 'consumer-group', path: 'consumers', diff --git a/km-console/packages/layout-clusters-fe/src/locales/zh.tsx b/km-console/packages/layout-clusters-fe/src/locales/zh.tsx index 5537c9d0..493a4bd9 100755 --- a/km-console/packages/layout-clusters-fe/src/locales/zh.tsx +++ b/km-console/packages/layout-clusters-fe/src/locales/zh.tsx @@ -52,6 +52,10 @@ export default { [`menu.${systemKey}.connect.connectors`]: 'Connectors', [`menu.${systemKey}.connect.workers`]: 'Workers', + [`menu.${systemKey}.replication`]: 'Replication', + [`menu.${systemKey}.replication.dashboard`]: 'Overview', + [`menu.${systemKey}.replication.mirror-maker`]: 'Mirror Makers', + [`menu.${systemKey}.acls`]: 'ACLs', [`menu.${systemKey}.jobs`]: 'Job', diff --git a/km-console/packages/layout-clusters-fe/src/pages/CommonConfig.tsx b/km-console/packages/layout-clusters-fe/src/pages/CommonConfig.tsx index 8e37edf3..4d01da97 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/CommonConfig.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/CommonConfig.tsx @@ -26,11 +26,19 @@ export enum ClustersPermissionMap { TOPIC_ADD = 'Topic-新增Topic', TOPIC_MOVE_REPLICA = 'Topic-迁移副本', TOPIC_CHANGE_REPLICA = 'Topic-扩缩副本', + TOPIC_REPLICATOR = 'Topic-新增Topic复制', + TOPIC_CANCEL_REPLICATOR = 'Topic-详情-取消Topic复制', // Consumers CONSUMERS_RESET_OFFSET = 'Consumers-重置Offset', // Test TEST_CONSUMER = 'Test-Consumer', TEST_PRODUCER = 'Test-Producer', + // MM2 + MM2_ADD = 'MM2-新增', + MM2_CHANGE_CONFIG = 'MM2-编辑', + MM2_DELETE = 'MM2-删除', + MM2_RESTART = 'MM2-重启', + MM2_STOP_RESUME = 'MM2-暂停&恢复', } export interface PermissionNode { diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2.tsx new file mode 100644 index 00000000..df7d13e2 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2.tsx @@ -0,0 +1,1239 @@ +import React, { createContext, createElement, forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import { Alert, Button, Drawer, Form, Input, InputNumber, Radio, Select, Steps, Switch, Table, Transfer, Utils } from 'knowdesign'; +import { FormInstance } from 'knowdesign/es/basic/form/Form'; +import message from '@src/components/Message'; +import api from '@src/api'; +import { useParams } from 'react-router-dom'; +import { IconFont } from '@knowdesign/icons'; +import { regClusterName } from '@src/constants/reg'; + +const { Step } = Steps; + +export interface ConnectCluster { + id: number; + name: string; + groupName: string; + state: number; + version: string; + jmxProperties: string; + clusterUrl: string; + memberLeaderUrl: string; +} + +export interface ConnectorPlugin { + type: 'source' | 'sink'; + version: string; + className: string; + helpDocLink: string; +} + +interface ConnectorPluginConfigDefinition { + name: string; + type: string; + required: boolean; + defaultValue: string | null; + importance: string; + documentation: string; + group: string; + orderInGroup: number; + width: string; + displayName: string; + dependents: string[]; +} + +interface ConnectorPluginConfigValue { + errors: string[]; + name: string; + recommendedValues: any[]; + value: any; + visible: boolean; +} + +export interface ConnectorPluginConfig { + name: string; + errorCount: number; + groups: string[]; + configs: { + definition: ConnectorPluginConfigDefinition; + value: ConnectorPluginConfigValue; + }[]; +} + +interface FormConnectorConfigs { + pluginConfig: { [key: string]: ConnectorPluginConfigDefinition[] }; + connectorConfig?: { [key: string]: any }; +} + +interface SubFormProps { + visible: boolean; + setSubmitLoading: (loading: boolean) => void; +} + +export interface OperateInfo { + type: 'create' | 'edit'; + errors: { + [key: string]: string[]; + }; + detail?: { + connectClusterId: number; + connectorName: string; + connectorClassName: string; + connectorType: 'source' | 'sink'; + }; + setSourceKafkaClusterId?: any; + setBootstrapServers?: any; + setSourceDetailConfigs?: any; + setCheckoutPointDetailConfigs?: any; + setHeartbeatDetailConfigs?: any; +} + +const existConfigItems = { + sourceConfigs: [ + 'sync.topic.configs.enabled', + 'sync.topic.configs.interval.seconds', + 'sync.topic.acls.enabled', + 'sync.topic.acls.interval.seconds', + 'refresh.topics.enabled', + 'refresh.topics.interval.seconds', + 'refresh.groups.enabled', + 'refresh.groups.interval.seconds', + 'replication.policy.separator', + 'replication.policy.class', + 'topics', + ], + checkpointConfig: ['emit.checkpoints.enabled', 'emit.checkpoints.interval.seconds', 'checkpoints.topic.replication.factor'], + heartbeatConfig: ['heartbeats.topic.replication.factor', 'emit.heartbeats.interval.seconds'], +}; + +const StepsFormContent = createContext< + OperateInfo & { + forms: { current: { [key: string]: FormInstance } }; + } +>({ + type: 'create', + errors: {}, + forms: { current: {} }, +}); + +function useStepForm(key: string | number) { + const { forms } = useContext(StepsFormContent); + const [form] = Form.useForm(); + let formInstace = form; + + if (forms.current[key]) { + formInstace = forms.current[key] as FormInstance; + } else { + forms.current[key] = formInstace; + } + + return [formInstace]; +} + +// 步骤一 +const StepFormFirst = (props: SubFormProps) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [form] = useStepForm(0); + const [topicData, setTopicData] = useState([]); + const { type, detail, setSourceKafkaClusterId, setBootstrapServers, setSourceDetailConfigs, setCheckoutPointDetailConfigs } = + useContext(StepsFormContent); + const isEdit = type === 'edit'; + const [seniorConfig, setSeniorConfig] = useState(false); + const [topicTargetKeys, setTopicTargetKeys] = useState([]); + const [connectClusters, setConnectClusters] = useState<{ label: string; value: number }[]>([]); + const [sourcekafkaClusters, setSourcekafkaClusters] = useState<{ label: string; value: number }[]>([]); + const [headSourcekafkaClusters, setHeadSourcekafkaClusters] = useState([]); + const [formItemValues, setFormItemValues] = useState([]); + const [formItemValue, setFormItemValue] = useState({}); + const [sourcekafkaClustersId, setSourcekafkaClustersId] = useState(null); + + const topicChange = (val: any) => { + setTopicTargetKeys(val); + }; + + // 获取Connect基本信息列表 + const getConnectClustersList = () => { + Utils.request(api.getConnectClusters(clusterId)).then((res: any) => { + const arr = res.map(({ name, id }: any) => ({ + label: name || '-', + value: id, + })); + setConnectClusters(arr); + }); + }; + + // 获取 Source Kafka 集群列表 + const getSourceKafkaClustersList = () => { + Utils.request(api.getSourceKafkaClusterBasic).then((res: any) => { + setHeadSourcekafkaClusters(res || []); + const arr = res + .filter((o: any) => o.id !== +clusterId) + .map(({ name, id }: any) => ({ + label: name || '-', + value: id, + })); + setSourcekafkaClusters(arr); + }); + }; + + // 获取Topic列表 + const getTopicList = () => { + // ! 需整理 + Utils.request(api.getTopicMetaList(Number(clusterId))).then((res: any) => { + const dataDe = res || []; + const dataHandle = dataDe.map((item: any) => { + return { + ...item, + key: item.topicName, + label: item.topicName, + value: item.topicName, + title: item.topicName, + }; + }); + setTopicData(dataHandle); + }); + }; + + const getMM2Config = (connectClusterId: string | number) => { + Promise.all( + [ + Utils.request(api.getConnectorPluginConfig(connectClusterId, 'org.apache.kafka.connect.mirror.MirrorSourceConnector')), + Utils.request(api.getConnectorPluginConfig(connectClusterId, 'org.apache.kafka.connect.mirror.MirrorCheckpointConnector')), + isEdit ? Utils.request(api.getMirrorMakerConfig(connectClusterId, detail.connectorName)) : undefined, + ].filter((r) => r) + ) + .then((res: any) => { + const detailConfigs: any[] = isEdit && res.length > 1 ? res?.[2] : []; + let sourceConfigs: any; + let checkpointConfigs: any; + + detailConfigs?.forEach((config) => { + if (config['connector.class'] === 'org.apache.kafka.connect.mirror.MirrorCheckpointConnector') { + checkpointConfigs = config; + setCheckoutPointDetailConfigs(config); + } else if (config['connector.class'] === 'org.apache.kafka.connect.mirror.MirrorSourceConnector') { + sourceConfigs = config; + setSourceDetailConfigs(config); + } + }); + const formItemValue: any = {}; + const formItemValues: any = []; + res?.[0].configs.forEach(({ definition }: any) => { + if (existConfigItems.sourceConfigs.includes(definition.name)) { + if (isEdit && sourceConfigs[definition.name]) { + formItemValue[definition.name] = sourceConfigs[definition.name]; + if (definition.name === 'topics') { + sourceConfigs[definition.name] !== '.*' + ? formItemValues.push({ + name: 'topics', + value: sourceConfigs[definition.name].split(',') || null, + }) + : formItemValues.push({ + name: 'priority', + value: 'allTopic', + }); + } else { + formItemValues.push({ + name: definition.name, + value: sourceConfigs[definition.name] || null, + }); + } + } else { + formItemValue[definition.name] = definition.defaultValue; + if (definition.name === 'topics') { + definition.defaultValue !== '.*' + ? formItemValues.push({ + name: 'topics', + value: definition.defaultValue.split(',') || null, + }) + : formItemValues.push({ + name: 'priority', + value: 'allTopic', + }); + } else { + formItemValues.push({ + name: definition.name, + value: definition.defaultValue || null, + }); + } + } + } + }); + res?.[1].configs.forEach(({ definition }: any) => { + if (existConfigItems.checkpointConfig.includes(definition.name)) { + if (isEdit && checkpointConfigs[definition.name]) { + formItemValue[definition.name] = checkpointConfigs[definition.name]; + formItemValues.push({ + name: definition.name, + value: checkpointConfigs[definition.name] || null, + }); + } else { + formItemValue[definition.name] = definition.defaultValue; + formItemValues.push({ + name: definition.name, + value: definition.defaultValue || null, + }); + } + // formItemValue[definition.name] = + // definition.type === 'BOOLEAN' ? Boolean(definition.defaultValue) : definition.defaultValue || null; + // formItemValues.push({ + // name: definition.name, + // value: definition.type === 'BOOLEAN' ? Boolean(definition.defaultValue) : definition.defaultValue || null, + // }); + } + }); + setFormItemValue(formItemValue); + setFormItemValues(formItemValues); + }) + .finally(() => props.setSubmitLoading(false)); + }; + + useEffect(() => { + getTopicList(); + getConnectClustersList(); + getSourceKafkaClustersList(); + }, []); + + useEffect(() => { + form.resetFields(existConfigItems.sourceConfigs); + form.setFields(formItemValues); + }, [formItemValues]); + + useEffect(() => { + const bootstrapServers = + headSourcekafkaClusters.length && headSourcekafkaClusters.find((item) => item.id === sourcekafkaClustersId)?.bootstrapServers; + setBootstrapServers(bootstrapServers); + }, [sourcekafkaClustersId]); + + // useEffect(() => { + // connectorConfig && + // form.setFieldsValue({ + // topics: + // typeof connectorConfig['topics'] === 'string' ? connectorConfig['topics'].split(',').map((i: string) => i.trim()) : undefined, + // }); + // }, [topicData, connectorConfig]); + + useEffect(() => { + // 需要处理Topic和config配置,还需要考虑编辑时的回显问题 + isEdit && getMM2Config(detail.connectClusterId); + const config = isEdit + ? detail + : { + 'sync.topic.configs.enabled': false, + 'sync.topic.configs.interval.seconds': 0, + 'sync.topic.acls.enabled': false, + 'sync.topic.acls.interval.seconds': 600, + 'refresh.topics.enabled': false, + 'refresh.topics.interval.seconds': 600, + 'refresh.groups.enabled': false, + 'refresh.groups.interval.seconds': 600, + 'emit.checkpoints.enabled': false, + 'emit.checkpoints.interval.seconds': 60, + 'checkpoints.topic.replication.factor': false, + 'replication.policy.separator': '.', + priority: 'allTopic', + }; + form.setFieldsValue(config); + setFormItemValue((state: any) => { + return { ...state, ...config }; + }); + }, [isEdit, detail]); + + const onConnectClusterChange = (value: string) => { + value && getMM2Config(value); + }; + + return ( +
+
+ 64) { + return Promise.reject('MM2任务名称长度限制在1~64字符'); + } + if (!new RegExp(regClusterName).test(value)) { + return Promise.reject( + 'MM2 名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~' + ); + } + return Promise.resolve(); + // return Utils.request(api.isConnectorExist(prevForm.getFieldValue('connectClusterId'), value)).then( + // (res: any) => { + // const data = res || {}; + // return data?.exist ? Promise.reject('MM2 名称重复') : Promise.resolve(); + // }, + // () => Promise.reject('连接超时! 请重试或检查服务') + // ); + }, + }, + ]} + > + + + + + + {!seniorConfig && ( +
setSeniorConfig(true)}> + 高级配置 + +
+ )} +
+
+
+ + + setFormItemValue((state: any) => { + return { ...state, 'sync.topic.configs.enabled': e }; + }) + } + /> + + {formItemValue['sync.topic.configs.enabled'] ? ( + + + + ) : null} +
+
+ + + setFormItemValue((state: any) => { + return { ...state, 'sync.topic.acls.enabled': e }; + }) + } + /> + + {formItemValue['sync.topic.acls.enabled'] ? ( + + + + ) : null} +
+
+ + + setFormItemValue((state: any) => { + return { ...state, 'refresh.topics.enabled': e }; + }) + } + /> + + {formItemValue['refresh.topics.enabled'] ? ( + + + + ) : null} +
+
+ + + setFormItemValue((state: any) => { + return { ...state, 'refresh.groups.enabled': e }; + }) + } + /> + + {formItemValue['refresh.groups.enabled'] ? ( + + + + ) : null} +
+
+ + + setFormItemValue((state: any) => { + return { ...state, 'emit.checkpoints.enabled': e }; + }) + } + /> + + {formItemValue['emit.checkpoints.enabled'] ? ( +
+ + + + + + +
+ ) : null} +
+
+ + + +
+ {seniorConfig && ( +
setSeniorConfig(false)}> + 收起 + +
+ )} + +
+ ); +}; + +// 步骤二 +const StepFormSecond = (props: SubFormProps) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [form] = useStepForm(1); + const [firstForm] = useStepForm(0); + const connectClusterId = firstForm.getFieldValue('connectClusterId'); + const [activeKey, setActiveKey] = useState([]); + const { type, detail, errors, setHeartbeatDetailConfigs } = useContext(StepsFormContent); + const isEdit = type === 'edit'; + const [groupOffset, setGroupOffset] = useState(false); // 控制是否同步开关 + const [heartbeat, setHeartbeat] = useState(true); // 控制是否同步开关 + const [groupLoading, setGroupLoading] = useState(false); + const [groupBasicData, setGroupBasicData] = useState([]); + + const [formItemValues, setFormItemValues] = useState([]); + const [formItemValue, setFormItemValue] = useState({}); + const columns = [ + { + title: 'ConsumerGroup', + dataIndex: 'groupName', + key: 'groupName', + width: 200, + lineClampOne: true, + needTooltip: true, + }, + { + title: 'Topic', + dataIndex: 'topicName', + key: 'topicName', + width: 200, + lineClampOne: true, + needTooltip: true, + }, + { + title: '状态', + dataIndex: 'state', + key: 'state', + width: 60, + lineClampOne: true, + needTooltip: true, + }, + ]; + + const getGroupBasicData = () => { + setGroupLoading(true); + Utils.request(api.getGroupBasic(clusterId)) + .then((res: any) => { + setGroupBasicData(res || []); + }) + .finally(() => { + setGroupLoading(false); + }); + }; + + const getMM2Config = async (connectClusterId: string | number) => { + const formItemValue: any = {}; + const formItemValues: any = []; + const result: any = await Utils.request( + api.getConnectorPluginConfig(connectClusterId, 'org.apache.kafka.connect.mirror.MirrorHeartbeatConnector') + ); + const editResult: any = isEdit ? await Utils.request(api.getMirrorMakerConfig(connectClusterId, detail.connectorName)) : undefined; + let heartbeatConfigs: any; + editResult?.forEach((config: any) => { + if (config['connector.class'] === 'org.apache.kafka.connect.mirror.MirrorCheckpointConnector') { + heartbeatConfigs = config; + setHeartbeatDetailConfigs(config); + } + }); + result?.configs.forEach(({ definition }: any) => { + if (existConfigItems.heartbeatConfig.includes(definition.name)) { + if (isEdit && heartbeatConfigs[definition.name]) { + formItemValue[definition.name] = heartbeatConfigs[definition.name]; + formItemValues.push({ + name: definition.name, + value: heartbeatConfigs[definition.name] || null, + }); + } else { + formItemValue[definition.name] = definition.defaultValue; + formItemValues.push({ + name: definition.name, + value: definition.defaultValue || null, + }); + } + // formItemValue[definition.name] = definition.type === 'BOOLEAN' ? Boolean(definition.defaultValue) : definition.defaultValue || null; + // formItemValues.push({ + // name: definition.name, + // value: definition.type === 'BOOLEAN' ? Boolean(definition.defaultValue) : definition.defaultValue || null, + // }); + } + }); + if (formItemValue['heartbeats.topic.replication.factor'] || formItemValue['emit.heartbeats.interval.seconds']) { + formItemValue['heartbeat'] = true; + formItemValues.push({ + name: 'heartbeat', + value: true, + }); + } + setFormItemValue(formItemValue); + setFormItemValues(formItemValues); + }; + + useEffect(() => { + form.resetFields(existConfigItems.sourceConfigs); + form.setFields(formItemValues); + }, [formItemValues]); + + useEffect(() => { + connectClusterId + ? getMM2Config(connectClusterId) + : form.setFieldsValue({ + groupOffset: false, + heartbeat: true, + 'offset-syncs.topic.replication.factor': 3, + 'sync.group.offsets.interval.seconds': 60, + 'heartbeats.topic.replication.factor': 3, + 'emit.heartbeats.interval.seconds': 1, + }); + }, [connectClusterId]); + + useEffect(() => { + groupOffset && getGroupBasicData(); + }, [groupOffset]); + + return ( +
+
+ + setGroupOffset(e)} size="small" /> + + {groupOffset && ( +
+ + + + + + +

+ + )} + + + setFormItemValue((state: any) => { + return { ...state, heartbeat: e }; + }) + } + size="small" + /> + + {formItemValue['heartbeat'] ? ( +
+ + + + + + +
+ ) : null} + {/* {heartbeat && ( +
+ + + + + + +
+ )} */} + + + ); +}; + +const steps = [ + { + title: '步骤一', + content: StepFormFirst, + }, + { + title: '步骤二', + content: StepFormSecond, + }, +]; + +export default forwardRef( + ( + props: { + refresh: () => void; + }, + ref + ) => { + const [visible, setVisible] = useState(false); + const [jsonRef, setJsonRef] = useState({}); + const [currentStep, setCurrentStep] = useState(0); + const [stepInitState, setStepInitState] = useState([1]); + const [submitLoading, setSubmitLoading] = useState(false); + const [operateInfo, setOperateInfo] = useState({ + type: undefined, + errors: {}, + }); + const stepsFormRef = useRef<{ + [key: string]: FormInstance; + }>({}); + const [sourceKafkaClusterId, setSourceKafkaClusterId] = useState(''); + const [bootstrapServers, setBootstrapServers] = useState(null); + const [sourceDetailConfigs, setSourceDetailConfigs] = useState({}); + const [checkoutPointDetailConfigs, setCheckoutPointDetailConfigs] = useState({}); + const [heartbeatDetailConfigs, setHeartbeatDetailConfigs] = useState({}); + const onOpen = (type: OperateInfo['type'], jsonRef: any, detail?: OperateInfo['detail']) => { + if (type === 'create') { + setStepInitState([1]); + } else { + setStepInitState([1, 2, 3, 4]); + } + setOperateInfo({ + type, + detail, + errors: {}, + }); + setJsonRef(jsonRef); + setVisible(true); + }; + + const onClose = () => { + Object.values(stepsFormRef.current).forEach((form) => { + form.resetFields(); + }); + stepsFormRef.current = {}; + setVisible(false); + setCurrentStep(0); + setStepInitState([]); + }; + + const turnTo = (jumpStep: number) => { + if (submitLoading) { + message.warning('加载中,请稍后重试'); + return; + } + if (jumpStep > currentStep) { + const prevInit = stepInitState[jumpStep - 1]; + if (!prevInit) { + message.warning('请按照顺序填写'); + } else { + stepsFormRef.current[currentStep].validateFields().then(() => { + const prevStep = jumpStep - 1; + if (currentStep < prevStep) { + stepsFormRef.current[prevStep] + .validateFields() + .then(() => { + setStepInitState((prev) => { + const cur = [...prev]; + cur[jumpStep] = 1; + return cur; + }); + setCurrentStep(jumpStep); + }) + .catch(() => { + setCurrentStep(prevStep); + }); + } else { + setStepInitState((prev) => { + const cur = [...prev]; + cur[jumpStep] = 1; + return cur; + }); + setCurrentStep(jumpStep); + } + }); + } + } else { + setCurrentStep(jumpStep); + } + }; + + // 校验所有表单 + const validateForms = ( + callback: (info: { + success?: + | { + connectClusterId: number; + connectorName: string; + configs: { + [key: string]: any; + }; + sourceKafkaClusterId: number; + heartbeatConnectorConfigs: { + [key: string]: any; + }; + checkpointConnectorConfigs: { + [key: string]: any; + }; + } + | any; + error?: any; + }) => void + ) => { + const promises: Promise[] = []; + Object.values(stepsFormRef.current).forEach((form, i) => { + const promise = form + .validateFields() + .then((res) => { + return res; + }) + .catch(() => { + return Promise.reject(i); + }); + promises.push(promise); + }); + + Promise.all(promises).then( + (res) => { + const result = { + ...res[0], + ...res[1], + // ...res[4], + }; + const { detail } = operateInfo as any; + const checkpointConnectorConfigs = result['emit.checkpoints.enabled'] && { + ...checkoutPointDetailConfigs, + 'connector.class': 'org.apache.kafka.connect.mirror.MirrorCheckpointConnector', + 'emit.checkpoints.enabled': result['emit.checkpoints.enabled'], + 'emit.checkpoints.interval.seconds': result['emit.checkpoints.interval.seconds'], + 'checkpoints.topic.replication.factor': result['checkpoints.topic.replication.factor'], + 'source.cluster.alias': sourceKafkaClusterId, + name: detail?.checkpointConnector || result.name, + 'source.cluster.bootstrap.servers': bootstrapServers || checkoutPointDetailConfigs?.['source.cluster.bootstrap.servers'], + }; + const heartbeatConnectorConfigs = result['heartbeat'] && { + ...heartbeatDetailConfigs, + 'connector.class': 'org.apache.kafka.connect.mirror.MirrorHeartbeatConnector', + 'heartbeats.topic.replication.factor': result['heartbeats.topic.replication.factor'], + 'emit.heartbeats.interval.seconds': result['emit.heartbeats.interval.seconds'], + 'source.cluster.alias': sourceKafkaClusterId, + name: detail?.heartbeatConnector || result.name, + 'source.cluster.bootstrap.servers': bootstrapServers || heartbeatDetailConfigs?.['source.cluster.bootstrap.servers'], + }; + const configs = { + ...sourceDetailConfigs, + 'connector.class': 'org.apache.kafka.connect.mirror.MirrorSourceConnector', + 'sync.topic.configs.enabled': result['sync.topic.configs.enabled'], + 'sync.topic.configs.interval.seconds': result['sync.topic.configs.interval.seconds'], + + 'sync.topic.acls.enabled': result['sync.topic.acls.enabled'], + 'sync.topic.acls.interval.seconds': result['sync.topic.acls.interval.seconds'], + + 'refresh.topics.enabled': result['refresh.topics.enabled'], + 'refresh.topics.interval.seconds': result['refresh.topics.interval.seconds'], + + 'refresh.groups.enabled': result['refresh.groups.enabled'], + 'refresh.groups.interval.seconds': result['refresh.groups.interval.seconds'], + 'replication.policy.class': result['replication.policy.class'], + 'replication.policy.separator': result['replication.policy.separator'], + topics: result['priority'] === 'givenTopic' ? result['topics'].join() : '.*', + 'source.cluster.alias': sourceKafkaClusterId, + name: result.name, + 'source.cluster.bootstrap.servers': bootstrapServers || sourceDetailConfigs?.['source.cluster.bootstrap.servers'], + }; + // topics 配置格式化 + // res[1].topics && (result.topics = (res[1].topics as string[]).join(', ')); + // // transforms 配置格式化 + // res[2].transforms && + // (res[2].transforms as string) + // .split('\n') + // .filter((l) => l) + // .forEach((l) => { + // const [k, ...v] = l.split('='); + // result[k] = v.join('='); + // }); + callback({ + success: { + connectClusterId: res[0].connectClusterId, + connectorName: result['name'], + sourceKafkaClusterId: res[0].sourceKafkaClusterId, + configs, + heartbeatConnectorConfigs: heartbeatConnectorConfigs || undefined, + checkpointConnectorConfigs: checkpointConnectorConfigs || undefined, + }, + }); + }, + (error) => { + callback({ + error, + }); + } + ); + }; + + const toJsonMode = () => { + validateForms((info) => { + if (info.error) { + message.warning('校验失败,请检查填写内容'); + setCurrentStep(info.error); + } else { + (jsonRef as any)?.onOpen(operateInfo.type, info.success); + onClose(); + } + }); + }; + + const onSubmit = () => { + validateForms((info) => { + if (info.error) { + message.warning('校验失败,请检查填写内容'); + setCurrentStep(info.error); + } else { + setSubmitLoading(true); + Object.entries(info.success).forEach(([key, val]) => { + if (val === null) { + delete info.success[key]; + } + }); + Utils.put(api.validateMM2Config, info.success).then( + (res: ConnectorPluginConfig) => { + if (res) { + if (res?.errorCount > 0) { + const errors: OperateInfo['errors'] = {}; + res?.configs + ?.filter((config) => config.value.errors.length !== 0) + .forEach(({ value }) => { + if (value.name.includes('transforms.')) { + errors['transforms'] = (errors['transforms'] || []).concat(value.errors); + } else { + errors[value.name] = value.errors; + } + }); + setOperateInfo((cur) => ({ + ...cur, + errors, + })); + + // 步骤跳转 + // const items = getExistFormItems(stepsFormRef.current[0].getFieldValue('connectorType')); + // const keys = Object.keys(errors).filter((key) => items.includes(key)); + // let jumpStep = 4; + // keys.forEach((key) => { + // Object.values(existFormItems).some((items, i) => { + // if (items.includes(key)) { + // jumpStep > i + 1 && (jumpStep = i + 1); + // return true; + // } + // return false; + // }); + // }); + // setCurrentStep(jumpStep); + setSubmitLoading(false); + message.warning('字段校验失败,请检查'); + } else { + if (operateInfo.type === 'create') { + Utils.post(api.mirrorMakerOperates, info.success) + .then(() => { + message.success('新建成功'); + onClose(); + props?.refresh(); + }) + .finally(() => setSubmitLoading(false)); + } else { + Utils.put(api.updateMM2Config, info.success) + .then(() => { + message.success('编辑成功'); + props?.refresh(); + onClose(); + }) + .finally(() => setSubmitLoading(false)); + } + } + } else { + setSubmitLoading(false); + message.error('接口校验出错,请重新提交'); + } + }, + () => setSubmitLoading(false) + ); + } + }); + }; + + useImperativeHandle(ref, () => ({ + onOpen, + onClose, + })); + + return ( + + {operateInfo.type && visible && ( + <> + turnTo(cur)}> + {steps.map(({ title }) => ( + + ))} + +
+ + {steps.map((step, i) => { + return createElement(step.content, { + visible: i === currentStep, + setSubmitLoading, + }); + })} + + {currentStep === steps.length - 1 && ( + + 如果你想自定义更多配置,可以点击 + + 继续补充 + + } + /> + )} +
+ {currentStep > 0 && ( + + )} + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} +
+
+ + )} +
+ ); + } +); diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx new file mode 100644 index 00000000..35c6a2d6 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx @@ -0,0 +1,266 @@ +import api from '@src/api'; +import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem'; +import customMessage from '@src/components/Message'; +import { Button, Divider, Drawer, Form, message, Space, Utils } from 'knowdesign'; +import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { ConnectCluster, ConnectorPlugin, ConnectorPluginConfig, OperateInfo } from './AddMM2'; + +const PLACEHOLDER = `配置格式如下 + +{ + "connectClusterId": 1, // ConnectID + "connectorName": "", // MM2 名称 + "sourceKafkaClusterId": 1, // SourceKafka集群 ID + "configs": { // Source 相关配置 + "name": "", // MM2 名称 + "source.cluster.alias": "", // SourceKafka集群 ID + ... + }, + "heartbeatConnectorConfigs": { // Heartbeat 相关配置 + "name": "", // Heartbeat 对应的Connector名称 + "source.cluster.alias": "", // SourceKafka集群 ID + ... + }, + "checkpointConnectorConfigs": { // Checkpoint 相关配置 + "name": "", // Checkpoint 对应的Connector名称 + "source.cluster.alias": "", // SourceKafka集群 ID + ... + } +}`; + +export default forwardRef((props: any, ref) => { + // const { clusterId } = useParams<{ + // clusterId: string; + // }>(); + const [visible, setVisible] = useState(false); + const [form] = Form.useForm(); + const [type, setType] = useState('create'); + // const [connectClusters, setConnectClusters] = useState<{ label: string; value: number }[]>([]); + const [defaultConfigs, setDefaultConfigs] = useState<{ [key: string]: any }>({}); + const [submitLoading, setSubmitLoading] = useState(false); + + // const getConnectClusters = () => { + // return Utils.request(api.getConnectClusters(clusterId)).then((res: ConnectCluster[]) => { + // setConnectClusters( + // res.map(({ name, id }) => ({ + // label: name || '-', + // value: id, + // })) + // ); + // }); + // }; + + const onOpen = (type: 'create' | 'edit', defaultConfigs?: { [key: string]: any }) => { + if (defaultConfigs) { + setDefaultConfigs({ ...defaultConfigs }); + form.setFieldsValue({ + configs: JSON.stringify(defaultConfigs, null, 2), + }); + } + setType(type); + setVisible(true); + }; + + const onSubmit = () => { + setSubmitLoading(true); + form.validateFields().then( + (data) => { + const postData = JSON.parse(data.configs); + + Object.entries(postData.configs).forEach(([key, val]) => { + if (val === null) { + delete postData.configs[key]; + } + }); + Utils.put(api.validateMM2Config, postData).then( + (res: ConnectorPluginConfig) => { + if (res) { + if (res?.errorCount > 0) { + const errors: OperateInfo['errors'] = {}; + res?.configs + ?.filter((config) => config.value.errors.length !== 0) + .forEach(({ value }) => { + if (value.name.includes('transforms.')) { + errors['transforms'] = (errors['transforms'] || []).concat(value.errors); + } else { + errors[value.name] = value.errors; + } + }); + form.setFields([ + { + name: 'configs', + errors: Object.entries(errors).map(([name, errorArr]) => `${name}: ${errorArr.join('; ')}\n`), + }, + ]); + setSubmitLoading(false); + } else { + if (type === 'create') { + Utils.post(api.mirrorMakerOperates, postData) + .then(() => { + customMessage.success('新建成功'); + onClose(); + props?.refresh(); + }) + .finally(() => setSubmitLoading(false)); + } else { + Utils.put(api.updateMM2Config, postData) + .then(() => { + customMessage.success('编辑成功'); + props?.refresh(); + onClose(); + }) + .finally(() => setSubmitLoading(false)); + } + } + } else { + setSubmitLoading(false); + message.error('接口校验出错,请重新提交'); + } + }, + () => setSubmitLoading(false) + ); + }, + () => setSubmitLoading(false) + ); + }; + + const onClose = () => { + setVisible(false); + form.resetFields(); + }; + + // useEffect(() => { + // getConnectClusters(); + // }, []); + + useImperativeHandle(ref, () => ({ + onOpen, + onClose, + })); + + return ( + + + + + + + + } + > +
+ { + // return data?.exist + // ? Promise.reject('name 与已有 Connector 重复') + // : plugins.every((plugin) => plugin.className !== v.configs['connector.class']) + // ? Promise.reject('该 connectCluster 下不存在 connector.class 项配置的插件') + // : Promise.resolve(); + // }, + // () => { + // return Promise.reject('接口校验出错,请重试'); + // } + // ); + // } else { + // return Promise.resolve(); + // } + } catch (e) { + return Promise.reject('输入内容必须为 JSON'); + } + }, + }, + ]} + > + {visible && ( +
+ { + form.setFieldsValue({ configs }); + }} + /> +
+ )} +
+ +
+ ); +}); diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Delete.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Delete.tsx new file mode 100644 index 00000000..3ef93e6e --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Delete.tsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { Button, Form, Input, Modal, Utils } from 'knowdesign'; +import notification from '@src/components/Notification'; +import { IconFont } from '@knowdesign/icons'; +import Api from '@src/api/index'; + +// eslint-disable-next-line react/display-name +const DeleteConnector = (props: { record: any; onConfirm?: () => void }) => { + const { record, onConfirm } = props; + const [form] = Form.useForm(); + const [delDialogVisible, setDelDialogVisble] = useState(false); + const handleDelOk = () => { + form.validateFields().then((e) => { + const formVal = form.getFieldsValue(); + formVal.connectClusterId = Number(record.connectClusterId); + Utils.delete(Api.mirrorMakerOperates, { data: formVal }).then((res: any) => { + if (res === null) { + notification.success({ + message: '删除成功', + }); + setDelDialogVisble(false); + onConfirm && onConfirm(); + } else { + notification.error({ + message: '删除失败', + }); + } + }); + }); + }; + return ( + <> + + { + setDelDialogVisble(false); + }} + okText="删除" + okButtonProps={{ + danger: true, + size: 'small', + style: { + paddingLeft: '16px', + paddingRight: '16px', + }, + }} + cancelButtonProps={{ + size: 'small', + style: { + paddingLeft: '16px', + paddingRight: '16px', + }, + }} + > +
+ {record.connectorName} + ({ + validator(_, value) { + if (!value) { + return Promise.reject(new Error('请输入MM2 Name名称')); + } else if (value !== record.connectorName) { + return Promise.reject(new Error('请输入正确的MM2 Name名称')); + } + return Promise.resolve(); + }, + }), + ]} + > + + + +
+ + ); +}; + +export default DeleteConnector; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Detail.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Detail.tsx new file mode 100644 index 00000000..a5b12902 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/Detail.tsx @@ -0,0 +1,185 @@ +import React, { useState, useEffect } from 'react'; +import { Drawer, Utils, AppContainer, ProTable, Tabs, Empty, Spin } from 'knowdesign'; +import API from '@src/api'; +import MirrorMakerDetailCard from '@src/components/CardBar/MirrorMakerDetailCard'; +import { defaultPagination, getMM2DetailColumns } from './config'; +import notification from '@src/components/Notification'; +import './index.less'; +const { TabPane } = Tabs; +const prefix = 'mm2-detail'; +const { request } = Utils; + +const DetailTable = ({ loading, retryOption, data }: { loading: boolean; retryOption: any; data: any[] }) => { + const [pagination, setPagination] = useState(defaultPagination); + const onTableChange = (pagination: any, filters: any, sorter: any) => { + setPagination(pagination); + }; + return ( + + {data.length ? ( + + ) : ( + + )} + + ); +}; + +const MM2Detail = (props: any) => { + const { visible, setVisible, record } = props; + const [global] = AppContainer.useGlobalValue(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + + const [tabSelectType, setTabSelectType] = useState('MirrorSource'); + const onClose = () => { + setVisible(false); + setTabSelectType('MirrorSource'); + // setPagination(defaultPagination); + // clean hash + }; + const callback = (key: any) => { + setTabSelectType(key); + }; + + const genData: any = { + MirrorSource: async () => { + if (global?.clusterInfo?.id === undefined) return; + setData([]); + setLoading(true); + if (record.connectorName) { + request(API.getConnectDetailTasks(record.connectorName, record.connectClusterId)) + .then((res: any) => { + setData(res || []); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + } else { + setLoading(false); + } + }, + MirrorCheckpoint: async () => { + if (global?.clusterInfo?.id === undefined) return; + setData([]); + setLoading(true); + if (record.checkpointConnector) { + request(API.getConnectDetailTasks(record.checkpointConnector, record.connectClusterId)) + .then((res: any) => { + setData(res || []); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + } else { + setLoading(false); + } + }, + MirrorHeatbeat: async () => { + if (global?.clusterInfo?.id === undefined) return; + setData([]); + setLoading(true); + if (record.heartbeatConnector) { + request(API.getConnectDetailTasks(record.heartbeatConnector, record.connectClusterId)) + .then((res: any) => { + setData(res || []); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + } else { + setLoading(false); + } + }, + }; + + const retryOption = (taskId: any) => { + const params = { + action: 'restart', + connectClusterId: record?.connectClusterId, + connectorName: record?.connectorName, + taskId, + }; + // 需要区分 tabSelectType + request(API.optionTasks(), { method: 'PUT', data: params }).then((res: any) => { + if (res === null) { + notification.success({ + message: `任务重试成功`, + }); + genData[tabSelectType](); + } else { + notification.error({ + message: `任务重试失败`, + }); + } + }); + }; + + useEffect(() => { + visible && record && genData[tabSelectType](); + }, [visible, tabSelectType]); + + return ( + + {record.connectorName ?? '-'} + + } + width={1080} + placement="right" + onClose={onClose} + visible={visible} + className={`${prefix}-drawer`} + destroyOnClose + maskClosable={false} + > + + + + + {/* {global.isShowControl && global.isShowControl(ControlStatusMap.BROKER_DETAIL_CONFIG) ? ( + + ) : ( + + )} */} + + + + + + + + + {/* */} + + ); +}; + +export default MM2Detail; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/config.tsx new file mode 100644 index 00000000..b0e21d3b --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/config.tsx @@ -0,0 +1,344 @@ +import SmallChart from '@src/components/SmallChart'; +import { IconFont } from '@knowdesign/icons'; +import { Button, Tag, Tooltip, Utils, Popconfirm, AppContainer } from 'knowdesign'; +import React from 'react'; +import Delete from './Delete'; +import { ClustersPermissionMap } from '../CommonConfig'; +export const defaultPagination = { + current: 1, + pageSize: 10, + position: 'bottomRight', + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50', '100', '200', '500'], +}; + +export const optionType: { [name: string]: string } = { + ['stop']: '暂停', + ['restart']: '重启', + ['resume']: '继续', +}; + +export const stateEnum: any = { + ['UNASSIGNED']: { + // 未分配 + name: 'Unassigned', + color: '#556EE6', + bgColor: '#EBEEFA', + }, + ['RUNNING']: { + // 运行 + name: 'Running', + color: '#00C0A2', + bgColor: 'rgba(0,192,162,0.10)', + }, + ['PAUSED']: { + // 暂停 + name: 'Paused', + color: '#495057', + bgColor: '#ECECF6', + }, + ['FAILED']: { + // 失败 + name: 'Failed', + color: '#F58342', + bgColor: '#fef3e5', + }, + ['DESTROYED']: { + // 销毁 + name: 'Destroyed', + color: '#FF7066', + bgColor: '#fdefee', + }, + ['RESTARTING']: { + // 重新启动 + name: 'Restarting', + color: '#3991FF', + bgColor: '#e9f5ff', + }, +}; + +const calcCurValue = (record: any, metricName: string) => { + // const item = (record.metricPoints || []).find((item: any) => item.metricName === metricName); + // return item?.value || ''; + // TODO 替换record + const orgVal = record?.latestMetrics?.metrics?.[metricName]; + if (orgVal !== undefined) { + if (metricName === 'TotalRecordErrors') { + return Math.round(orgVal).toLocaleString(); + } else { + return Number(Utils.formatAssignSize(orgVal, 'KB', orgVal > 1000 ? 2 : 3)).toLocaleString(); + // return Utils.formatAssignSize(orgVal, 'KB'); + } + } + return '-'; + // return orgVal !== undefined ? (metricName !== 'HealthScore' ? formatAssignSize(orgVal, 'KB') : orgVal) : '-'; +}; + +const renderLine = (record: any, metricName: string) => { + const points = record.metricLines?.find((item: any) => item.metricName === metricName)?.metricPoints || []; + return points.length ? ( +
+ ({ time: item.timeStamp, value: item.value })), + }} + /> + {calcCurValue(record, metricName)} +
+ ) : ( + {calcCurValue(record, metricName)} + ); +}; + +export const getMM2Columns = (arg?: any) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [global] = AppContainer.useGlobalValue(); + const columns: any = [ + { + title: 'MM2 Name', + dataIndex: 'connectorName', + key: 'connectorName', + width: 160, + fixed: 'left', + lineClampOne: true, + render: (t: string, r: any) => { + return t ? ( + <> + + { + arg.getDetailInfo(r); + }} + > + {t} + + + + ) : ( + '-' + ); + }, + }, + { + title: 'Connect集群', + dataIndex: 'connectClusterName', + key: 'connectClusterName', + width: 200, + lineClampOne: true, + needTooltip: true, + }, + { + title: 'State', + dataIndex: 'state', + key: 'state', + width: 120, + render: (t: string, r: any) => { + return t ? ( + + {stateEnum[t]?.name} + + ) : ( + '-' + ); + }, + }, + { + // title: '集群(源-->目标)', + title: ( + + 集群(源{' '} + + + {' '} + 目标) + + ), + dataIndex: 'destKafkaClusterName', + key: 'destKafkaClusterName', + width: 200, + render: (t: string, r: any) => { + return r.sourceKafkaClusterName && r.destKafkaClusterName ? ( + + {r.sourceKafkaClusterName} + + {t} + + ) : ( + '-' + ); + }, + }, + { + title: 'Tasks', + dataIndex: 'taskCount', + key: 'taskCount', + width: 100, + }, + { + title: '复制流量速率', + dataIndex: 'byteRate', + key: 'byteRate', + sorter: true, + width: 170, + render: (value: any, record: any) => renderLine(record, 'ByteRate'), + }, + { + title: '消息复制速率', + dataIndex: 'recordRate', + key: 'recordRate', + sorter: true, + width: 170, + render: (value: any, record: any) => renderLine(record, 'RecordRate'), + }, + { + title: '最大延迟', + dataIndex: 'replicationLatencyMsMax', + key: 'replicationLatencyMsMax', + sorter: true, + width: 170, + render: (value: any, record: any) => renderLine(record, 'ReplicationLatencyMsMax'), + }, + ]; + if (global.hasPermission) { + columns.push({ + title: '操作', + dataIndex: 'options', + key: 'options', + width: 200, + filterTitle: true, + fixed: 'right', + // eslint-disable-next-line react/display-name + render: (_t: any, r: any) => { + return ( +
+ {global.hasPermission(ClustersPermissionMap.MM2_STOP_RESUME) && (r.state === 'RUNNING' || r.state === 'PAUSED') && ( + arg?.optionConnect(r, r.state === 'RUNNING' ? 'stop' : 'resume')} + // onCancel={cancel} + okText="是" + cancelText="否" + overlayClassName="connect-popconfirm" + > + + + )} + {global.hasPermission(ClustersPermissionMap.MM2_RESTART) ? ( + arg?.optionConnect(r, 'restart')} + // onCancel={cancel} + okText="是" + cancelText="否" + overlayClassName="connect-popconfirm" + > + + + ) : ( + <> + )} + {global.hasPermission(ClustersPermissionMap.MM2_CHANGE_CONFIG) ? ( + r.sourceKafkaClusterId ? ( + + ) : ( + + + + ) + ) : ( + <> + )} + {global.hasPermission(ClustersPermissionMap.MM2_DELETE) ? : <>} +
+ ); + }, + }); + } + return columns; +}; + +// Detail +export const getMM2DetailColumns = (arg?: any) => { + const columns = [ + { + title: 'Task ID', + dataIndex: 'taskId', + key: 'taskId', + width: 240, + render: (t: any, r: any) => { + return ( + + {t} + { + + {Utils.firstCharUppercase(r?.state as string)} + + } + + ); + }, + }, + { + title: 'Worker', + dataIndex: 'workerId', + key: 'workerId', + width: 240, + }, + { + title: '错误原因', + dataIndex: 'trace', + key: 'trace', + width: 400, + needTooltip: true, + lineClampOne: true, + }, + { + title: '操作', + dataIndex: 'role', + key: 'role', + width: 100, + render: (_t: any, r: any) => { + return ( +
+ arg?.retryOption(r.taskId)} + // onCancel={cancel} + okText="是" + cancelText="否" + overlayClassName="connect-popconfirm" + > + 重试 + +
+ ); + }, + }, + ]; + return columns; +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.less b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.less new file mode 100644 index 00000000..82664902 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.less @@ -0,0 +1,265 @@ +// mm2列表 图表 +.metric-data-wrap { + // display: flex; + // align-items: center; + width: 100%; + .cur-val { + display: block; + text-align: right; + } + .dcloud-spin-nested-loading { + flex: 1; + } +} + +// 新增按钮 +.add-connect { + .dcloud-btn-primary:hover, + .dcloud-btn-primary:focus { + // 可以控制新增按钮的hover和focus的样式 + // background: #556ee6; + // border-color: #556ee6; + } + &-btn { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-right: none; + } + &-dropdown-menu { + .dcloud-dropdown-menu { + border-radius: 8px; + &-item { + font-size: 13px; + } + } + } + &-json { + padding: 8px 12px !important; + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-left: none; + } +} + +// connect详情 +.mm2-detail-drawer { + .card-bar-container { + background: rgba(86, 110, 230, 0.04) !important; + + .card-bar-colunms { + background-color: rgba(86, 110, 230, 0); + } + } + &-title { + margin: 20px 0 8px; + font-family: @font-family-bold; + font-size: 16px; + font-weight: 500; + } +} + +// 删除 +.del-connect-modal { + .tip-info { + display: flex; + color: #592d00; + padding: 6px 14px; + font-size: 13px; + background: #fffae0; + border-radius: 4px; + .anticon { + color: #ffc300; + margin-right: 4px; + margin-top: 3px; + } + .test-right-away { + color: #556ee6; + cursor: pointer; + } + .dcloud-alert-content { + flex: none; + } + } +} + +// 重启、继续/暂停 气泡卡片 +.connect-popconfirm { + .dcloud-popover-inner-content { + padding: 6px 16px; + } + .dcloud-popover-inner { + border-radius: 8px; + } + .dcloud-popover-message, + .dcloud-btn { + font-size: 12px !important; + } +} + +.operate-connector-drawer { + .connector-plugin-desc { + font-size: 13px; + .connector-plugin-title { + font-family: @font-family-bold; + } + } + .dcloud-collapse.add-connector-collapse { + .add-connector-collapse-panel, + .add-connector-collapse-panel:last-child { + margin-bottom: 8px; + overflow: hidden; + background: #f8f9fa; + border: 0px; + border-radius: 8px; + .dcloud-collapse-header { + padding: 8px 12px; + font-size: 14px; + color: #495057; + .dcloud-collapse-arrow { + margin-right: 8px !important; + font-size: 10px; + } + } + &:hover .dcloud-collapse-extra { + opacity: 1; + } + &:not(.dcloud-collapse-item-active) { + .dcloud-collapse-header:hover { + background: #f1f3ff; + } + } + .dcloud-collapse-content-box { + display: flex; + flex-flow: row wrap; + justify-content: space-between; + padding: 20px 14px 0 14px; + .dcloud-form-item { + flex: 1 0 50%; + .dcloud-input-number { + width: 100%; + } + &:nth-child(2n) { + padding-left: 6px; + } + &:nth-child(2n + 1) { + padding-right: 6px; + } + .dcloud-form-item-control-input { + height: 100%; + } + } + } + } + } + + .add-container-plugin-select { + height: 27px; + margin: 4px 0; + position: relative; + .dcloud-form-item { + position: absolute; + top: 0; + left: 0; + .dcloud-form-item-control-input { + min-height: 0; + } + } + } + + .dcloud-alert { + margin: 16px 0 24px 0; + padding: 0 12px; + border: unset; + border-radius: 8px; + background: #fffae0; + .dcloud-alert-message { + font-size: 13px; + color: #592d00; + .dcloud-btn { + padding: 0 4px; + font-size: 13px; + } + } + } +} + +.operate-connector-drawer-use-json { + .CodeMirror.cm-s-default { + height: calc(100vh - 146px); + } + .dcloud-form-item { + margin-bottom: 0 !important; + } +} + +.mirror-maker-steps { + width: 340px !important; + margin: 0 auto !important; +} + +.add-mm2-config-title { + color: #556ee6; + margin-bottom: 24px; + display: inline-block; + cursor: pointer; + &-text { + margin-right: 7px; + } + &-icon { + transform: rotate(180deg); + } +} + +.custom-form-item-27 { + padding: 0 16px; + .dcloud-form-item { + margin-bottom: 6px !important; + } + .dcloud-form-item-label > label { + height: 27px; + } + .group-offset-table { + margin-bottom: 40px; + margin-top: 12px; + } +} + +.custom-form-item-36 { + .dcloud-form-item-control-input { + min-height: 36px; + } +} + +.clear-topic-minHeight { + .dcloud-form-item-control-input { + min-height: 0; + } +} + +.add-mm2-flex-layout { + &-item { + display: flex; + justify-content: space-between; + } + .senior-config-left { + width: 228px !important; + margin-right: 10px; + // .dcloud-form-item-label { + // width: 190px !important; + // text-align: right !important; + // } + } + .dcloud-form-item { + width: 345px; + flex-direction: row !important; + height: 27px; + margin-bottom: 2px !important; + &-label { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 0 !important; + margin-right: 10px; + } + } +} diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.tsx new file mode 100644 index 00000000..42f42694 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/index.tsx @@ -0,0 +1,240 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { ProTable, Dropdown, Button, Utils, AppContainer, SearchInput, Menu } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import API from '../../api'; +import { getMM2Columns, defaultPagination, optionType } from './config'; +import { tableHeaderPrefix } from '@src/constants/common'; +import MirrorMakerCard from '@src/components/CardBar/MirrorMakerCard'; +import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; +import AddMM2, { OperateInfo } from './AddMM2'; +import MM2Detail from './Detail'; +import notification from '@src/components/Notification'; +import './index.less'; +import AddConnectorUseJSON from './AddMM2JSON'; +import { ClustersPermissionMap } from '../CommonConfig'; +const { request } = Utils; + +const rateMap: any = { + byteRate: ['ByteRate'], + recordRate: ['RecordRate'], + replicationLatencyMsMax: ['ReplicationLatencyMsMax'], +}; + +const MirrorMaker2: 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(defaultPagination); + const [sortInfo, setSortInfo] = useState({}); + const [detailRecord, setDetailRecord] = useState(''); + const [healthType, setHealthType] = useState(true); + const addConnectorRef = useRef(null); + const addConnectorJsonRef = useRef(null); + + const getRecent1DayTimeStamp = () => [Date.now() - 24 * 60 * 60 * 1000, Date.now()]; + // 请求接口获取数据 + const genData = async ({ pageNo, pageSize, filters, sorter }: any) => { + const [startStamp, endStamp] = getRecent1DayTimeStamp(); + if (global?.clusterInfo?.id === undefined) return; + setLoading(true); + const params = { + metricLines: { + aggType: 'avg', + endTime: endStamp, + metricsNames: ['ByteRate', 'RecordRate', 'ReplicationLatencyMsMax'], + // metricsNames: ['SourceRecordPollRate', 'SourceRecordWriteRate', 'SinkRecordReadRate', 'SinkRecordSendRate', 'TotalRecordErrors'], + startTime: startStamp, + topNu: 0, + }, + searchKeywords: searchKeywords.slice(0, 128), + pageNo, + pageSize, + latestMetricNames: ['ByteRate', 'RecordRate', 'ReplicationLatencyMsMax'], + // latestMetricNames: ['SourceRecordPollRate', 'SourceRecordWriteRate', 'SinkRecordReadRate', 'SinkRecordSendRate', 'TotalRecordErrors'], + sortType: sorter?.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : 'desc', + sortMetricNameList: rateMap[sorter?.field] || [], + }; + + request(API.getMirrorMakerList(global?.clusterInfo?.id), { method: 'POST', data: params }) + // request(API.getConnectorsList(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, + key: item.connectClusterName + item.connectorName, + }; + }) || []; + setData(newData); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any) => { + setSortInfo(sorter); + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter }); + }; + + const menu = ( + + + addConnectorJsonRef.current?.onOpen('create')}>JSON 新增MM2 + + + ); + + const getDetailInfo = (record: any) => { + setDetailRecord(record); + setDetailVisible(true); + }; + + // 编辑 + const editConnector = (detail: OperateInfo['detail']) => { + addConnectorRef.current?.onOpen('edit', addConnectorJsonRef.current, detail); + }; + + // 重启、暂停/继续 操作 + const optionConnect = (record: any, action: string) => { + setLoading(true); + const params = { + action, + connectClusterId: record?.connectClusterId, + connectorName: record?.connectorName, + }; + + request(API.mirrorMakerOperates, { method: 'PUT', data: params }) + .then((res: any) => { + if (res === null) { + notification.success({ + message: `任务已${optionType[params.action]}`, + description: `任务状态更新会有至多1min延迟`, + }); + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo }); + setHealthType(!healthType); + } else { + notification.error({ + message: `${optionType[params.action]}任务失败`, + }); + } + }) + .catch((err) => { + setLoading(false); + }); + }; + + // 删除任务 + const deleteTesk = () => { + genData({ pageNo: 1, pageSize: pagination.pageSize }); + }; + + useEffect(() => { + genData({ + pageNo: 1, + pageSize: pagination.pageSize, + sorter: sortInfo, + }); + }, [searchKeywords]); + + return ( + <> +
+ +
+ {/* + <> + + + */} +
+ +
+
+
+
+
genData({ pageNo: pagination.current, pageSize: pagination.pageSize })} + > + +
+
+
+ + {global.hasPermission && global.hasPermission(ClustersPermissionMap.MM2_ADD) ? ( + + + + + + + ) : ( + <> + )} +
+
+ +
+ + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo })} + /> + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo })} + /> + + ); +}; + +export default MirrorMaker2; diff --git a/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts b/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts index a781c948..cc69935d 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts +++ b/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts @@ -29,6 +29,9 @@ import ConnectDashboard from './ConnectDashboard'; import Connectors from './Connect'; import Workers from './Connect/Workers'; +import MirrorMaker2 from './MirrorMaker2'; +import MirrorMakerDashboard from './MirrorMakerDashBoard'; + const pageRoutes = [ { path: '/', @@ -152,6 +155,18 @@ const pageRoutes = [ component: Workers, noSider: false, }, + { + path: 'replication', + exact: true, + component: MirrorMakerDashboard, + noSider: false, + }, + { + path: 'replication/mirror-maker', + exact: true, + component: MirrorMaker2, + noSider: false, + }, { path: 'security/acls', exact: true, From a0371ab88b26de2803ee1ee3cc98e29cc2712178 Mon Sep 17 00:00:00 2001 From: wyb Date: Fri, 10 Feb 2023 16:56:13 +0800 Subject: [PATCH 124/150] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9ETopic=20?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/TopicJob/TopicMirror.tsx | 289 ++++++++++++++++++ .../src/pages/TopicDetail/Replicator.tsx | 173 +++++++++++ .../src/pages/TopicDetail/index.tsx | 4 + .../src/pages/TopicList/index.tsx | 53 +++- 4 files changed, 507 insertions(+), 12 deletions(-) create mode 100644 km-console/packages/layout-clusters-fe/src/components/TopicJob/TopicMirror.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Replicator.tsx diff --git a/km-console/packages/layout-clusters-fe/src/components/TopicJob/TopicMirror.tsx b/km-console/packages/layout-clusters-fe/src/components/TopicJob/TopicMirror.tsx new file mode 100644 index 00000000..2845dcae --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/TopicJob/TopicMirror.tsx @@ -0,0 +1,289 @@ +// 批量Topic复制 +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { Button, Drawer, Form, Select, Utils, AppContainer, Space, Divider, Transfer, Checkbox, Tooltip } from 'knowdesign'; +import message from '@src/components/Message'; +import { IconFont } from '@knowdesign/icons'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import './index.less'; +import Api from '@src/api/index'; + +const { Option } = Select; +const CheckboxGroup = Checkbox.Group; + +interface DefaultConfig { + drawerVisible: boolean; + onClose: () => void; + genData?: () => any; +} + +export default (props: DefaultConfig) => { + const { drawerVisible, onClose, genData } = props; + const routeParams = useParams<{ clusterId: string }>(); + const [visible, setVisible] = useState(drawerVisible); + const [topicList, setTopicList] = useState([]); + const [clusterList, setClusterList] = useState([]); + const [selectTopicList, setSelectTopicList] = useState([]); + const [form] = Form.useForm(); + const topicsPerCluster = React.useRef({} as any); + + const mirrorScopeOptions = [ + { + label: '数据', + value: 'syncData', + }, + { + label: 'Topic配置', + value: 'syncConfig', + }, + { + label: 'kafkauser+Acls', + value: 'kafkauserAcls', + disabled: true, + }, + { + label: 'Group Offset', + value: 'groupOffset', + disabled: true, + }, + ]; + + const getTopicList = () => { + Utils.request(Api.getTopicMetaData(+routeParams.clusterId)).then((res: any) => { + const dataDe = res || []; + const dataHandle = dataDe.map((item: any) => { + return { + ...item, + key: item.topicName, + title: item.topicName, + }; + }); + setTopicList(dataHandle); + }); + }; + + const getTopicsPerCluster = (clusterId: number) => { + Utils.request(Api.getTopicMetaData(clusterId)).then((res: any) => { + const dataDe = res || []; + const dataHandle = dataDe.map((item: any) => item.topicName); + topicsPerCluster.current = { ...topicsPerCluster.current, [clusterId]: dataHandle }; + setTimeout(() => { + form.validateFields(['topicNames']); + }, 1000); + }); + }; + + const getClusterList = () => { + Utils.request(Api.getMirrorClusterList()).then((res: any) => { + const dataDe = res || []; + const dataHandle = dataDe.map((item: any) => { + return { + label: item.name, + value: item.id, + }; + }); + setClusterList(dataHandle); + }); + }; + + const checkTopic = (_: any, value: any[]) => { + const clusters = form.getFieldValue('destClusterPhyIds'); + if (!value || !value.length) { + return Promise.reject('请选择需要复制的Topic'); + } else { + if (clusters && clusters.length) { + // 验证Topic是否存在 + const existTopics = {} as any; + clusters.forEach((cluster: number) => { + if (cluster && topicsPerCluster.current[cluster]) { + existTopics[cluster] = []; + value.forEach((topic) => { + if (topicsPerCluster.current[cluster].indexOf(topic) > -1) { + existTopics[cluster].push(topic); + } + }); + if (!existTopics[cluster].length) delete existTopics[cluster]; + } else { + getTopicsPerCluster(cluster); + } + }); + if (Object.keys(existTopics).length) { + let errorInfo = ''; + Object.keys(existTopics).forEach((key) => { + const clusterName = clusterList.find((item) => item.value == key)?.label; + errorInfo = errorInfo.concat(`${existTopics[key].join('、')}在集群【${clusterName}】中已存在,`); + }); + errorInfo = errorInfo.concat('请重新选择'); + return Promise.reject(errorInfo); + } else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } + } + }; + + const topicChange = (val: string[]) => { + setSelectTopicList(val); + }; + + const clusterChange = (val: number[]) => { + if (val && val.length) { + val.forEach((item) => { + if (item && !topicsPerCluster.current[item]) { + getTopicsPerCluster(item); + } else { + form.validateFields(['topicNames']); + } + }); + } else { + form.validateFields(['topicNames']); + } + }; + + const onDrawerClose = () => { + form.resetFields(); + setSelectTopicList([]); + topicsPerCluster.current = {}; + setVisible(false); + onClose(); + }; + + useEffect(() => { + if (!drawerVisible) return; + setVisible(true); + getTopicList(); + getClusterList(); + }, [drawerVisible]); + + const addTopicMirror = () => { + form.validateFields().then((e) => { + const formData = form.getFieldsValue(); + const handledData = [] as any; + formData.destClusterPhyIds.forEach((cluster: number) => { + formData.topicNames.forEach((topic: string) => { + handledData.push({ + destClusterPhyId: cluster, + sourceClusterPhyId: +routeParams.clusterId, + syncData: formData.mirrorScope.indexOf('syncData') > -1, + syncConfig: formData.mirrorScope.indexOf('syncConfig') > -1, + topicName: topic, + }); + }); + }); + Utils.post(Api.handleTopicMirror(), handledData).then(() => { + message.success('成功复制Topic'); + onDrawerClose(); + genData(); + }); + }); + }; + + return ( + + + + + + } + > +
+
+ + option.topicName.indexOf(inputValue) > -1} + targetKeys={selectTopicList} + onChange={topicChange} + render={(item) => item.title} + titles={['待选Topic', '已选Topic']} + customHeader + showSelectedCount + locale={{ itemUnit: '', itemsUnit: '' }} + suffix={} + /> + + + 选择目标集群 + + + + + } + rules={[{ required: true, message: '请选择目标集群' }]} + > + + + { + if (!value || !value.length) { + return Promise.reject('请选择Topic复制范围'); + } else if (value.indexOf('syncData') === -1) { + return Promise.reject('Topic复制范围必须选择[数据]'); + } else { + return Promise.resolve(); + } + }, + }, + ]} + initialValue={['syncData']} + > + + {mirrorScopeOptions.map((option) => { + return option.disabled ? ( + + + {option.label} + + + ) : ( + + {option.label} + + ); + })} + + + +
+
+ ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Replicator.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Replicator.tsx new file mode 100644 index 00000000..89e27479 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/Replicator.tsx @@ -0,0 +1,173 @@ +import React, { useState, useEffect } from 'react'; +import { AppContainer, ProTable, Utils, Tag, Modal, Tooltip } from 'knowdesign'; +import Api from '@src/api'; +import { useParams } from 'react-router-dom'; +import { getDataUnit } from '@src/constants/chartConfig'; +import message from '@src/components/Message'; +import { ClustersPermissionMap } from '../CommonConfig'; +import { ControlStatusMap } from '../CommonRoute'; +const { request } = Utils; + +const getColmns = (arg: any) => { + const formattedBytes = (v: number) => { + const [unit, size] = getDataUnit['Memory'](v); + return `${(v / size).toFixed(2)}${unit}/s`; + }; + const tagEle = ( + + 当前集群 + + ); + const baseColumns: any = [ + { + title: '源集群', + dataIndex: 'sourceClusterName', + key: 'sourceClusterName', + render: (t: string, record: any) => ( + <> + {t || '-'} + {record.sourceClusterId == arg.clusterId && tagEle} + + ), + }, + { + title: '目标集群', + dataIndex: 'destClusterName', + key: 'destClusterName', + render: (t: string, record: any) => ( + <> + {t || '-'} + {record.destClusterId == arg.clusterId && tagEle} + + ), + }, + { + title: '消息写入速率', + dataIndex: 'bytesIn', + key: 'bytesIn', + width: 150, + render: (t: number) => (t !== null && t !== undefined ? formattedBytes(t) : '-'), + }, + { + title: '消息复制速率', + dataIndex: 'replicationBytesIn', + key: 'replicationBytesIn', + width: 150, + render: (t: number) => (t !== null && t !== undefined ? formattedBytes(t) : '-'), + }, + { + title: '延迟(个消息)', + dataIndex: 'lag', + key: 'lag', + width: 150, + }, + { + title: '操作', + dataIndex: 'option', + key: 'option', + width: 100, + render: (_t: any, r: any) => { + return arg.global.hasPermission(ClustersPermissionMap.TOPIC_CANCEL_REPLICATOR) ? ( + arg.cancelSync(r)}>取消同步 + ) : ( + '-' + ); + }, + }, + ]; + + return baseColumns; +}; + +const Replicator = (props: any) => { + const { hashData } = props; + const urlParams = useParams(); // 获取地址栏参数 + const [global] = AppContainer.useGlobalValue(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + position: 'bottomRight', + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50', '100', '200', '500'], + }); + + const genData = () => { + if (urlParams?.clusterId === undefined || hashData?.topicName === undefined) return; + setLoading(true); + request(Api.getTopicMirrorList(urlParams?.clusterId, hashData?.topicName)) + .then((res: any = []) => { + setData(res); + }) + .finally(() => setLoading(false)); + }; + + const cancelSync = (item: any) => { + Modal.confirm({ + title: `确认取消此Topic同步吗?`, + okType: 'primary', + centered: true, + okButtonProps: { + size: 'small', + danger: true, + }, + cancelButtonProps: { + size: 'small', + }, + maskClosable: false, + onOk(close) { + close(); + const data = [ + { + destClusterPhyId: item.destClusterId, + sourceClusterPhyId: item.sourceClusterId, + topicName: item.topicName, + }, + ]; + Utils.delete(Api.handleTopicMirror(), { data }).then(() => { + message.success('成功取消Topic同步'); + genData(); + }); + }, + }); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any, extra: any) => { + setPagination(pagination); + }; + + useEffect(() => { + props.positionType === 'Replicator' && genData(); + }, []); + + return ( +
+ +
+ ); +}; + +export default Replicator; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx index bc3f6ed4..ea009bd4 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx @@ -10,6 +10,7 @@ import ConsumerGroups from './ConsumerGroups'; import ACLs from './ACLs'; import Configuration from './Configuration'; import Consumers from './ConsumerGroups'; +import Replicator from './Replicator'; // import Consumers from '@src/pages/Consumers'; import './index.less'; import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck'; @@ -206,6 +207,9 @@ const TopicDetail = (props: any) => { )} + + {positionType === 'Replicator' && } + ); diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx index 8e35af9a..4c3f8857 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/display-name */ import React, { useState, useEffect } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import { AppContainer, Input, ProTable, Select, Switch, Tooltip, Utils, Dropdown, Menu, Button, Divider } from 'knowdesign'; +import { AppContainer, Input, ProTable, Select, Switch, Tooltip, Utils, Dropdown, Menu, Button, Divider, Tag } from 'knowdesign'; import { IconFont } from '@knowdesign/icons'; import Create from './Create'; import './index.less'; @@ -15,10 +15,12 @@ import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; import ReplicaChange from '@src/components/TopicJob/ReplicaChange'; import SmallChart from '@src/components/SmallChart'; import ReplicaMove from '@src/components/TopicJob/ReplicaMove'; +import TopicMirror from '@src/components/TopicJob/TopicMirror'; import { formatAssignSize } from '../Jobs/config'; import { DownOutlined } from '@ant-design/icons'; import { tableHeaderPrefix } from '@src/constants/common'; import { HealthStateMap } from './config'; +import { ControlStatusMap } from '../CommonRoute'; const { Option } = Select; @@ -39,6 +41,7 @@ const AutoPage = (props: any) => { const [type, setType] = useState(''); const [changeVisible, setChangeVisible] = useState(false); const [moveVisible, setMoveVisible] = useState(false); + const [mirrorVisible, setMirrorVisible] = useState(false); const [selectValue, setSelectValue] = useState('批量操作'); const [sortObj, setSortObj] = useState<{ @@ -131,17 +134,34 @@ const AutoPage = (props: any) => { className: 'clean-padding-left', lineClampOne: true, // eslint-disable-next-line react/display-name - render: (t: string, r: any) => { + render: (t: string, record: any) => { return ( - - { - window.location.hash = `topicName=${t}`; - }} - > - {t} - - + <> + + { + window.location.hash = `topicName=${t}`; + }} + > + {t} + + + {record.inMirror && ( +
+ + 复制中... + +
+ )} + ); }, }, @@ -256,6 +276,7 @@ const AutoPage = (props: any) => { const onclose = () => { setChangeVisible(false); setMoveVisible(false); + setMirrorVisible(false); setSelectValue('批量操作'); }; @@ -271,6 +292,11 @@ const AutoPage = (props: any) => { setMoveVisible(true)}>迁移副本 )} + {global.hasPermission(ClustersPermissionMap.TOPIC_REPLICATOR) && ( + + setMirrorVisible(true)}>Topic复制 + + )} ); @@ -296,6 +322,8 @@ const AutoPage = (props: any) => { {/* 批量迁移 */} + {/* Topic复制 */} +
getTopicsList()}> @@ -334,7 +362,8 @@ const AutoPage = (props: any) => { }} /> {(global.hasPermission(ClustersPermissionMap.TOPIC_CHANGE_REPLICA) || - global.hasPermission(ClustersPermissionMap.TOPIC_MOVE_REPLICA)) && ( + global.hasPermission(ClustersPermissionMap.TOPIC_MOVE_REPLICA) || + global.hasPermission(ClustersPermissionMap.TOPIC_REPLICATOR)) && (
-
onExpand(metricName, metricType)}> - -
- + {metricLines?.length > 0 && ( +
onExpand(metricName, metricType)}> + +
+ )} + {metricLines?.length > 0 ? ( + + ) : ( + + )} ); })} diff --git a/km-console/packages/layout-clusters-fe/src/components/TopicJob/index.less b/km-console/packages/layout-clusters-fe/src/components/TopicJob/index.less index 794a252e..31c5914c 100644 --- a/km-console/packages/layout-clusters-fe/src/components/TopicJob/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/TopicJob/index.less @@ -89,4 +89,10 @@ background: #F8F9FA; } } +} + +.checkbox-content-margin { + .dcloud-checkbox-wrapper { + margin-right: 20px; + } } \ No newline at end of file diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx index 8dd47926..f1b4481e 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx @@ -103,17 +103,53 @@ const DraggableCharts = (): JSX.Element => { MetricType.Connect, curHeaderOptions.rangeTime ) as FormattedMetricData[]; + // todo 将指标筛选选中但是没有返回的指标插入chartData中 + const newConnectClusterData: any = []; + metricList[MetricType.Connect]?.forEach((item) => { + if (connectClusterData && connectClusterData.some((key) => item === key.metricName)) { + newConnectClusterData.push(null); + } else { + const chartData: any = { + metricName: item, + metricType: MetricType.Connect, + metricUnit: global.getMetricDefine(MetricType.Connect, item)?.unit || '', + metricLines: [], + showLegend: false, + targetUnit: undefined, + }; + newConnectClusterData.push(chartData); + } + }); const connectorData = formatChartData( res[1], global.getMetricDefine || {}, MetricType.Connectors, curHeaderOptions.rangeTime ) as FormattedMetricData[]; + // todo 将指标筛选选中但是没有返回的指标插入chartData中 + const newConnectorData: any = []; + + metricList[MetricType.Connectors]?.forEach((item) => { + if (connectorData && connectorData.some((key) => item === key.metricName)) { + newConnectorData.push(null); + } else { + const chartData: any = { + metricName: item, + metricType: MetricType.Connectors, + metricUnit: global.getMetricDefine(MetricType.Connectors, item)?.unit || '', + metricLines: [], + showLegend: false, + targetUnit: undefined, + }; + newConnectorData.push(chartData); + } + }); // 指标排序 const formattedMetricData = [...connectClusterData, ...connectorData]; + const nullDataMetricData = [...newConnectClusterData, ...newConnectorData].filter((item) => item !== null); formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName)); - - setMetricChartData(formattedMetricData); + nullDataMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName)); + setMetricChartData([...formattedMetricData, ...nullDataMetricData]); } else { setMetricChartData([]); } @@ -186,6 +222,9 @@ const DraggableCharts = (): JSX.Element => { if (Object.values(metricList).some((list) => list.length) && curHeaderOptions) { setLoading(true); getMetricChartData(); + } else { + setMetricChartData([]); + setLoading(false); } }, [metricList]); From b6e4f50849dc8eb3de11cea3e604a7b98252e720 Mon Sep 17 00:00:00 2001 From: wyb Date: Fri, 10 Feb 2023 17:20:44 +0800 Subject: [PATCH 126/150] =?UTF-8?q?fix:=20=E5=81=A5=E5=BA=B7=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=AF=A6=E6=83=85=E4=BC=98=E5=8C=96=20&=20Connector?= =?UTF-8?q?=20=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96=20&=20=E6=97=A0MM2?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=8C=87=E6=A0=87=E5=85=9C=E5=BA=95=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/Connect/HasConnector.tsx | 2 +- .../src/pages/Connect/config.tsx | 35 +++++++------ .../MirrorMakerDashBoard/HasConnector.tsx | 51 ++++++++++++++++++ .../src/pages/MirrorMakerDashBoard/index.tsx | 34 ++++++++++++ .../pages/MutliClusterPage/AccessCluster.tsx | 7 +-- .../MutliClusterPage/AccessClusterConfig.tsx | 23 ++++++++ .../src/pages/MutliClusterPage/HomePage.tsx | 2 + .../src/pages/MutliClusterPage/index.less | 8 +++ .../pages/SingleClusterDetail/CheckDetail.tsx | 1 + .../src/pages/SingleClusterDetail/config.tsx | 52 +++++++++++++++++++ 10 files changed, 194 insertions(+), 21 deletions(-) create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/HasConnector.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/index.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessClusterConfig.tsx diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx index 18136475..d2deaeea 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/HasConnector.tsx @@ -36,7 +36,7 @@ export default (props: Props) => { const [disabled, setDisabled] = useState(true); useLayoutEffect(() => { - Utils.request(api.getConnectors(clusterId)) + Utils.request(api.getConnectClusters(clusterId)) .then((res: any[]) => { res?.length && setDisabled(false); }) diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx index 82bfa59a..67691d74 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/config.tsx @@ -94,28 +94,12 @@ const renderLine = (record: any, metricName: string) => { export const getConnectorsColumns = (arg?: any) => { const columns = [ - { - title: 'Connect集群', - dataIndex: 'connectClusterName', - key: 'connectClusterName', - width: 200, - fixed: 'left', - lineClampOne: true, - needTooltip: true, - // render: (t: string, r: any) => { - // return ( - // - // {t} - // {r?.status ? Live : Down} - // - // ); - // }, - }, { title: 'Connector Name', dataIndex: 'connectorName', key: 'connectorName', width: 160, + fixed: 'left', lineClampOne: true, render: (t: string, r: any) => { return t ? ( @@ -135,6 +119,23 @@ export const getConnectorsColumns = (arg?: any) => { ); }, }, + { + title: 'Connect集群', + dataIndex: 'connectClusterName', + key: 'connectClusterName', + width: 200, + lineClampOne: true, + needTooltip: true, + // render: (t: string, r: any) => { + // return ( + // + // {t} + // {r?.status ? Live : Down} + // + // ); + // }, + }, + { title: 'State', dataIndex: 'state', diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/HasConnector.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/HasConnector.tsx new file mode 100644 index 00000000..a8fffc76 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/HasConnector.tsx @@ -0,0 +1,51 @@ +import React, { useLayoutEffect, useState } from 'react'; +import api from '@src/api'; +import { Spin, Utils } from 'knowdesign'; +import { useParams } from 'react-router-dom'; +import NodataImg from '@src/assets/no-data.png'; + +interface Props { + children: any; +} + +const NoConnector = () => { + return ( +
+ + 暂无数据,请先创建 MM2 任务 +
+ ); +}; + +export default (props: Props) => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [loading, setLoading] = useState(true); + const [disabled, setDisabled] = useState(true); + + useLayoutEffect(() => { + Utils.request(api.getMirrorMakerMetadata(clusterId)) + .then((res: any[]) => { + res?.length && setDisabled(false); + }) + .finally(() => setLoading(false)); + }, []); + + return disabled ? ( + {loading ?
: } + ) : ( + props.children + ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/index.tsx new file mode 100644 index 00000000..b82e7dac --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMakerDashBoard/index.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { MetricType } from '@src/api'; +import MirrorMakerCard from '@src/components/CardBar/MirrorMakerCard'; +import DraggableCharts from '@src/components/DraggableCharts'; +import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; +import { AppContainer } from 'knowdesign'; +import HasConnector from './HasConnector'; + +const MirrorMakerDashboard = (): JSX.Element => { + const [global] = AppContainer.useGlobalValue(); + return ( + <> +
+ +
+ + <> + + + + + + {/* */} + + ); +}; + +export default MirrorMakerDashboard; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx index ec5fab11..8f06b99b 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx @@ -522,6 +522,7 @@ const ConnectorForm = (props: { const params = { ...values, id: initFieldsValue?.id, + jmxProperties: values.jmxProperties ? `{ "jmxProperties": "${values.jmxProperties}" }` : undefined, }; Utils.put(api.batchConnectClusters, [params]) .then((res) => { @@ -542,7 +543,7 @@ const ConnectorForm = (props: { setSelectedTabKey(undefined); try { const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {}; - form.setFieldsValue({ ...initFieldsValue, jmxPort: jmxPortInfo.jmxPort }); + form.setFieldsValue({ ...initFieldsValue, jmxProperties: jmxPortInfo.jmxProperties }); } catch { form.setFieldsValue({ ...initFieldsValue }); } @@ -551,7 +552,7 @@ const ConnectorForm = (props: { useLayoutEffect(() => { try { const jmxPortInfo = JSON.parse(initFieldsValue.jmxProperties) || {}; - form.setFieldsValue({ ...initFieldsValue, jmxPort: jmxPortInfo.jmxPort }); + form.setFieldsValue({ ...initFieldsValue, jmxProperties: jmxPortInfo.jmxProperties }); } catch { form.setFieldsValue({ ...initFieldsValue }); } @@ -626,7 +627,7 @@ const ConnectorForm = (props: { ))} - +
diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessClusterConfig.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessClusterConfig.tsx new file mode 100644 index 00000000..4c73a8ae --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessClusterConfig.tsx @@ -0,0 +1,23 @@ +import React, { useState } from 'react'; +import { message } from 'knowdesign'; +import { HeartTwoTone } from '@ant-design/icons'; + +const AccessClusterConfig = () => { + const [count, setCount] = useState(1); + + const setErgeModal = () => { + if (count >= 50) { + message.success({ + content: 'Erge', + icon: , + }); + setCount(1); + } else { + setCount(count + 1); + } + }; + + return
; +}; + +export default AccessClusterConfig; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx index 25e5cdfe..4f59ae90 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx @@ -6,6 +6,7 @@ import TourGuide, { MultiPageSteps } from '@src/components/TourGuide'; import { healthSorceList, sliderValueMap, sortFieldList, sortTypes, statusFilters } from './config'; import ClusterList from './List'; import AccessClusters from './AccessCluster'; +import AccessCluster from './AccessClusterConfig'; import CustomCheckGroup from './CustomCheckGroup'; import { ClustersPermissionMap } from '../CommonConfig'; import './index.less'; @@ -359,6 +360,7 @@ const MultiClusterPage = () => { />
+
diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less index fc5dbfa3..f0dab4ba 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less @@ -343,6 +343,14 @@ } } } + .multi-cluster-erge { + position: absolute; + width: 15px; + height: 15px; + top: 100px; + left: 20px; + background-color: transparent; + } } &-dashboard { & > .dcloud-spin-nested-loading > .dcloud-spin-container::after { diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/CheckDetail.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/CheckDetail.tsx index 29fc708a..83f78db4 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/CheckDetail.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/CheckDetail.tsx @@ -19,6 +19,7 @@ const CheckDetail = forwardRef((props: any, ref): JSX.Element => { const getHealthDetail = () => { setLoading(true); return Utils.request(API.getResourceListHealthDetail(+clusterId)).then((res: any) => { + res.sort((a: any, b: any) => a.dimension - b.dimension); setData(res); setLoading(false); }); diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx index 01af934a..e7d4f2ba 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx @@ -40,6 +40,10 @@ export const dimensionMap = { label: 'Connector', href: '/connect/connectors', }, + 7: { + label: 'MirrorMaker', + href: '/replication', + }, } as any; const toLowerCase = (name = '') => { @@ -95,6 +99,18 @@ const CONFIG_ITEM_DETAIL_DESC = { ConnectorUnassignedTaskCount: (valueGroup: any) => { return `未被分配的任务数量 小于 ${valueGroup?.value}`; }, + MirrorMakerFailedTaskCount: (valueGroup: any) => { + return `失败状态的任务数量 小于 ${valueGroup?.value}`; + }, + MirrorMakerUnassignedTaskCount: (valueGroup: any) => { + return `未被分配的任务数量 小于 ${valueGroup?.value}`; + }, + ReplicationLatencyMsMax: (valueGroup: any) => { + return `消息复制最大延迟时间 小于 ${valueGroup?.value}`; + }, + 'TotalRecord-errors': (valueGroup: any) => { + return `消息处理错误的次数 增量小于 ${valueGroup?.value}`; + }, }; export const getConfigItemDetailDesc = (item: keyof typeof CONFIG_ITEM_DETAIL_DESC, valueGroup: any) => { @@ -409,6 +425,42 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string)
); } + case 'MirrorMakerFailedTaskCount': { + return ( +
+ {'>'} + {getFormItem({ configItem, attrs: { min: 0, max: 99998 } })} + 则不通过 +
+ ); + } + case 'MirrorMakerUnassignedTaskCount': { + return ( +
+ {'>'} + {getFormItem({ configItem, attrs: { min: 0, max: 99998 } })} + 则不通过 +
+ ); + } + case 'ReplicationLatencyMsMax': { + return ( +
+ {'>'} + {getFormItem({ configItem, attrs: { min: 0, max: 99998 } })} + 则不通过 +
+ ); + } + case 'TotalRecord-errors': { + return ( +
+ {'>'} + {getFormItem({ configItem, attrs: { min: 0, max: 99998 } })} + 则不通过 +
+ ); + } default: { return <>; } From fad9ddb9a112b7a0ed46d0f4961d920e483154f4 Mon Sep 17 00:00:00 2001 From: wyb Date: Fri, 10 Feb 2023 17:21:42 +0800 Subject: [PATCH 127/150] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=A1=B5=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/layout-clusters-fe/src/pages/Login/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/Login/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/Login/index.tsx index 913284a5..99802231 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Login/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Login/index.tsx @@ -13,8 +13,8 @@ const carouselList = [
Github: - 5.6K - + Star的项目 Know Streaming + 5.8K + + Star的的实时流处理平台
从开源至今社区内已经超过 2000+ 用户使用,从新创公司到巨头,尤其是得到各行业一线企业开发者的信赖。 From f715cf7a8d615c55897027cfe94c56bc8c5698fd Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 13 Feb 2023 11:57:51 +0800 Subject: [PATCH 128/150] =?UTF-8?q?=E8=A1=A5=E5=85=85=203.3.0=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=98=E6=9B=B4=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Releases_Notes.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Releases_Notes.md b/Releases_Notes.md index 656790f2..14d186b1 100644 --- a/Releases_Notes.md +++ b/Releases_Notes.md @@ -1,4 +1,52 @@ +## v3.3.0 + +**协议调整** +- 项目由 AGPL-3.0 协议调整为 Apache-2.0 协议; + + +**问题修复** +- 修复 Connect 的 JMX-Port 配置未生效问题; +- 修复 不存在 Connector 时,OverView 页面的数据一直处于加载中的问题; +- 修复 Group 分区信息,分页时展示不全的问题; +- 修复采集副本指标时,参数传递错误的问题; +- 修复用户信息修改后,用户列表会抛出空指针异常的问题; +- 修复 Topic 详情页面,查看消息时,选择分区不生效问题; +- 修复对 ZK 客户端进行配置后不生效的问题; +- 修复 connect 模块,指标中缺少健康巡检项通过数的问题; +- 修复 connect 模块,指标获取方法存在映射错误的问题; +- 修复 connect 模块,max 纬度指标获取错误的问题; +- 修复 Topic 指标大盘 TopN 指标显示信息错误的问题; +- 修复 Broker Similar Config 显示错误的问题; + + +**产品优化** +- ZK Overview 页面补充默认展示的指标; +- 统一初始化 ES 索引模版的脚本为 init_es_template.sh,同时新增缺失的 connect 索引模版初始化脚本,去除多余的 replica 和 zookeper 索引模版初始化脚本; +- 指标大盘页面,优化指标筛选操作后,无指标数据的指标卡片由不显示改为显示,并增加无数据的兜底; +- 删除从 ES 读写 replica 指标的相关代码; +- 优化 Topic 健康巡检的日志,明确错误的原因; +- 优化无 ZK 模块时,巡检详情忽略对 ZK 的展示; +- 优化本地缓存大小为可配置; +- Task 模块中的返回中,补充任务的分组信息; +- FAQ 补充 Ldap 的配置说明; +- FAQ 补充接入 Kerberos 认证的 Kafka 集群的配置说明; + + +**功能新增** +- 新增基于滴滴 Kafka 的 Topic 复制功能(需使用滴滴 Kafka 才可具备该能力); +- Topic 指标大盘,新增 Topic 复制相关的指标; +- 新增基于 TestContainers 的单测; + + +**Kafka MM2 Beta版 (v3.3.0版本新增发布)** +- MM2 任务的增删改查; +- MM2 任务的指标大盘; +- MM2 任务的健康状态; + +--- + + ## v3.2.0 **问题修复** From e4651ef74963aab39fd7d7593fb6a3b1f1f82061 Mon Sep 17 00:00:00 2001 From: edengyuan_v Date: Tue, 14 Feb 2023 17:35:50 +0800 Subject: [PATCH 129/150] =?UTF-8?q?[Optimize]=E6=96=B0=E5=A2=9ETopic?= =?UTF-8?q?=E6=97=B6=E6=B8=85=E7=90=86=E7=AD=96=E7=95=A5=E5=8C=BA=E5=88=86?= =?UTF-8?q?=E5=8D=95=E9=80=89=E5=A4=9A=E9=80=89(#770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/CommonRoute.tsx | 2 ++ .../src/pages/TopicList/Create.tsx | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/CommonRoute.tsx b/km-console/packages/layout-clusters-fe/src/pages/CommonRoute.tsx index 3fc32f35..f5d7f497 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/CommonRoute.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/CommonRoute.tsx @@ -22,6 +22,8 @@ export enum ControlStatusMap { TESTING_PRODUCER_HEADER = 'FETestingProducerHeader', TESTING_PRODUCER_COMPRESSION_TYPE_ZSTD = 'FETestingProducerCompressionTypeZSTD', TESTING_CONSUMER_HEADER = 'FETestingConsumerHeader', + // Topic + CREATE_TOPIC_CLEANUP_POLICY = 'FECreateTopicCleanupPolicy', } const CommonRoute: React.FC = (props: any) => { diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx index 6f1cb667..bf80fd74 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx @@ -1,11 +1,13 @@ import React, { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { Alert, Button, Checkbox, Divider, Drawer, Form, Input, InputNumber, Modal, Select, Utils } from 'knowdesign'; +import { Alert, Button, Checkbox, Divider, Drawer, Form, Input, InputNumber, Modal, Select, Utils, Radio, AppContainer } from 'knowdesign'; import notification from '@src/components/Notification'; import { PlusOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; import Api from '@src/api/index'; +import { ControlStatusMap } from '../CommonRoute'; const CheckboxGroup = Checkbox.Group; +const RadioGroup = Radio.Group; interface DefaultConfig { name: string; @@ -67,6 +69,8 @@ export default (props: any) => { const routeParams = useParams<{ clusterId: string; }>(); + const [global] = AppContainer.useGlobalValue(); + const multiCleanupPolicy = global.isShowControl && global.isShowControl(ControlStatusMap.CREATE_TOPIC_CLEANUP_POLICY); const confirm = () => { form.validateFields().then((e) => { const formVal = JSON.parse(JSON.stringify(form.getFieldsValue())); @@ -106,7 +110,7 @@ export default (props: any) => { let res: any; try { res = - item.name === 'cleanup.policy' + item.name === 'cleanup.policy' && multiCleanupPolicy ? item.defaultValue .replace(/\[|\]|\s+/g, '') .split(',') @@ -267,12 +271,21 @@ export default (props: any) => {
- - - delete - - compact - + {multiCleanupPolicy ? ( + + + delete + + compact + + ) : ( + + + delete + + compact + + )}
更多配置
From eed9571ffafa265c9b2ae11f631889310a700e41 Mon Sep 17 00:00:00 2001 From: slhu Date: Wed, 15 Feb 2023 14:30:36 +0800 Subject: [PATCH 130/150] =?UTF-8?q?[Bugfix]=E8=A7=A3=E5=86=B3=E5=9C=A8?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=91=BD=E4=BB=A4=E6=89=A7=E8=A1=8C=E5=90=8E?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=8C=87=E6=A0=87=E7=9A=84=E5=80=BC=E6=97=B6?= =?UTF-8?q?=E5=8F=91=E7=94=9F=E7=9A=84=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E9=94=99=E8=AF=AF=E4=B8=8E=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E4=B8=8A=E6=8A=A5=E6=97=B6=E6=8A=A5=E7=A9=BA?= =?UTF-8?q?=E6=8C=87=E9=92=88=E7=9A=84=E9=97=AE=E9=A2=98(#912)=201.zk=5Fmi?= =?UTF-8?q?n=5Flatency=E3=80=81zk=5Fmax=5Flatency=E6=8C=87=E6=A0=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E5=8F=98=E6=9B=B4=E4=B8=BA?= =?UTF-8?q?float=202.=E4=BD=BF=E7=94=A8ConvertUtil.string2Float()=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E8=BF=9B=E8=A1=8Cstring=E5=88=B0float=E5=88=B0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/zookeeper/fourletterword/MonitorCmdData.java | 4 ++-- .../entity/zookeeper/fourletterword/ServerCmdData.java | 4 ++-- .../fourletterword/parser/MonitorCmdDataParser.java | 7 ++++--- .../fourletterword/parser/ServerCmdDataParser.java | 7 ++++--- .../zookeeper/impl/ZookeeperMetricServiceImpl.java | 8 ++++---- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/MonitorCmdData.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/MonitorCmdData.java index 2fb3c9da..7e2a10f4 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/MonitorCmdData.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/MonitorCmdData.java @@ -23,8 +23,8 @@ import lombok.Data; public class MonitorCmdData extends BaseFourLetterWordCmdData { private String zkVersion; private Float zkAvgLatency; - private Long zkMaxLatency; - private Long zkMinLatency; + private Float zkMaxLatency; + private Float zkMinLatency; private Long zkPacketsReceived; private Long zkPacketsSent; private Long zkNumAliveConnections; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/ServerCmdData.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/ServerCmdData.java index 883231d6..0bd9e0a4 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/ServerCmdData.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/ServerCmdData.java @@ -18,8 +18,8 @@ import lombok.Data; public class ServerCmdData extends BaseFourLetterWordCmdData { private String zkVersion; private Float zkAvgLatency; - private Long zkMaxLatency; - private Long zkMinLatency; + private Float zkMaxLatency; + private Float zkMinLatency; private Long zkPacketsReceived; private Long zkPacketsSent; private Long zkNumAliveConnections; diff --git a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java index ff23afab..8c3e6958 100644 --- a/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java +++ b/km-common/src/main/java/com/xiaojukeji/know/streaming/km/common/bean/entity/zookeeper/fourletterword/parser/MonitorCmdDataParser.java @@ -3,6 +3,7 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.fourletter import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; import com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.fourletterword.MonitorCmdData; +import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.zookeeper.FourLetterWordUtil; import lombok.Data; @@ -57,13 +58,13 @@ public class MonitorCmdDataParser implements FourLetterWordDataParser Date: Wed, 15 Feb 2023 17:39:54 +0800 Subject: [PATCH 131/150] =?UTF-8?q?[Bugfix]=E5=88=A0=E9=99=A4idx=5Fcluster?= =?UTF-8?q?=5Fphy=5Fid=20=E7=B4=A2=E5=BC=95=E5=B9=B6=E6=96=B0=E5=A2=9Eidx?= =?UTF-8?q?=5Fcluster=5Fupdate=5Ftime=E7=B4=A2=E5=BC=95(#918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install_guide/版本升级手册.md | 5 +++++ km-persistence/src/main/resources/sql/ddl-ks-km.sql | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/install_guide/版本升级手册.md b/docs/install_guide/版本升级手册.md index 847f5876..3d98cd57 100644 --- a/docs/install_guide/版本升级手册.md +++ b/docs/install_guide/版本升级手册.md @@ -56,6 +56,11 @@ CREATE TABLE `ks_ha_active_standby_relation` ( UNIQUE KEY `uniq_cluster_res` (`res_type`,`active_cluster_phy_id`,`standby_cluster_phy_id`,`res_name`), UNIQUE KEY `uniq_res_type_standby_cluster_res_name` (`res_type`,`standby_cluster_phy_id`,`res_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='HA主备关系表'; + + +-- 删除idx_cluster_phy_id 索引并新增idx_cluster_update_time索引 +ALTER TABLE `ks_km_kafka_change_record` DROP INDEX `idx_cluster_phy_id` , +ADD INDEX `idx_cluster_update_time` (`cluster_phy_id` ASC, `update_time` ASC); ``` ### 升级至 `3.2.0` 版本 diff --git a/km-persistence/src/main/resources/sql/ddl-ks-km.sql b/km-persistence/src/main/resources/sql/ddl-ks-km.sql index c19de0a3..e3b73d30 100644 --- a/km-persistence/src/main/resources/sql/ddl-ks-km.sql +++ b/km-persistence/src/main/resources/sql/ddl-ks-km.sql @@ -196,7 +196,7 @@ CREATE TABLE `ks_km_kafka_change_record` ( `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `unique_field` (`unique_field`), - KEY `idx_cluster_phy_id` (`cluster_phy_id`) + KEY `idx_cluster_update_time` (`cluster_phy_id` ASC, `update_time` ASC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Kafka变更记录表'; @@ -480,4 +480,4 @@ CREATE TABLE `ks_ha_active_standby_relation` ( PRIMARY KEY (`id`), UNIQUE KEY `uniq_cluster_res` (`res_type`,`active_cluster_phy_id`,`standby_cluster_phy_id`,`res_name`), UNIQUE KEY `uniq_res_type_standby_cluster_res_name` (`res_type`,`standby_cluster_phy_id`,`res_name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='HA主备关系表'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='HA主备关系表'; \ No newline at end of file From 9bfe3fd1db2038d661e276502b0b7f140800bc33 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 15 Feb 2023 17:53:46 +0800 Subject: [PATCH 132/150] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=B8=BAAGPL?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 1094 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 661 insertions(+), 433 deletions(-) diff --git a/LICENSE b/LICENSE index 3a42fbf6..2def0e88 100644 --- a/LICENSE +++ b/LICENSE @@ -1,433 +1,661 @@ - Apache License - - Version 2.0, January 2004 - - http://www.apache.org/licenses/ - - - - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - - - -1. Definitions. - - - - - "License" shall mean the terms and conditions for use, reproduction, - - and distribution as defined by Sections 1 through 9 of this document. - - - - - "Licensor" shall mean the copyright owner or entity authorized by - - the copyright owner that is granting the License. - - - - - "Legal Entity" shall mean the union of the acting entity and all - - other entities that control, are controlled by, or are under common - - control with that entity. For the purposes of this definition, - - "control" means (i) the power, direct or indirect, to cause the - - direction or management of such entity, whether by contract or - - otherwise, or (ii) ownership of fifty percent (50%) or more of the - - outstanding shares, or (iii) beneficial ownership of such entity. - - - - - "You" (or "Your") shall mean an individual or Legal Entity - - exercising permissions granted by this License. - - - - - "Source" form shall mean the preferred form for making modifications, - - including but not limited to software source code, documentation - - source, and configuration files. - - - - - "Object" form shall mean any form resulting from mechanical - - transformation or translation of a Source form, including but - - not limited to compiled object code, generated documentation, - - and conversions to other media types. - - - - - "Work" shall mean the work of authorship, whether in Source or - - Object form, made available under the License, as indicated by a - - copyright notice that is included in or attached to the work - - (an example is provided in the Appendix below). - - - - - "Derivative Works" shall mean any work, whether in Source or Object - - form, that is based on (or derived from) the Work and for which the - - editorial revisions, annotations, elaborations, or other modifications - - represent, as a whole, an original work of authorship. For the purposes - - of this License, Derivative Works shall not include works that remain - - separable from, or merely link (or bind by name) to the interfaces of, - - the Work and Derivative Works thereof. - - - - - "Contribution" shall mean any work of authorship, including - - the original version of the Work and any modifications or additions - - to that Work or Derivative Works thereof, that is intentionally - - submitted to Licensor for inclusion in the Work by the copyright owner - - or by an individual or Legal Entity authorized to submit on behalf of - - the copyright owner. For the purposes of this definition, "submitted" - - means any form of electronic, verbal, or written communication sent - - to the Licensor or its representatives, including but not limited to - - communication on electronic mailing lists, source code control systems, - - and issue tracking systems that are managed by, or on behalf of, the - - Licensor for the purpose of discussing and improving the Work, but - - excluding communication that is conspicuously marked or otherwise - - designated in writing by the copyright owner as "Not a Contribution." - - - - - "Contributor" shall mean Licensor and any individual or Legal Entity - - on behalf of whom a Contribution has been received by Licensor and - - subsequently incorporated within the Work. - - - - -2. Grant of Copyright License. Subject to the terms and conditions of - - this License, each Contributor hereby grants to You a perpetual, - - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - - copyright license to reproduce, prepare Derivative Works of, - - publicly display, publicly perform, sublicense, and distribute the - - Work and such Derivative Works in Source or Object form. - - - - -3. Grant of Patent License. Subject to the terms and conditions of - - this License, each Contributor hereby grants to You a perpetual, - - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - - (except as stated in this section) patent license to make, have made, - - use, offer to sell, sell, import, and otherwise transfer the Work, - - where such license applies only to those patent claims licensable - - by such Contributor that are necessarily infringed by their - - Contribution(s) alone or by combination of their Contribution(s) - - with the Work to which such Contribution(s) was submitted. If You - - institute patent litigation against any entity (including a - - cross-claim or counterclaim in a lawsuit) alleging that the Work - - or a Contribution incorporated within the Work constitutes direct - - or contributory patent infringement, then any patent licenses - - granted to You under this License for that Work shall terminate - - as of the date such litigation is filed. - - - - -4. Redistribution. You may reproduce and distribute copies of the - - Work or Derivative Works thereof in any medium, with or without - - modifications, and in Source or Object form, provided that You - - meet the following conditions: - - - - - (a) You must give any other recipients of the Work or - - Derivative Works a copy of this License; and - - - - - (b) You must cause any modified files to carry prominent notices - - stating that You changed the files; and - - - - - (c) You must retain, in the Source form of any Derivative Works - - that You distribute, all copyright, patent, trademark, and - - attribution notices from the Source form of the Work, - - excluding those notices that do not pertain to any part of - - the Derivative Works; and - - - - - (d) If the Work includes a "NOTICE" text file as part of its - - distribution, then any Derivative Works that You distribute must - - include a readable copy of the attribution notices contained - - within such NOTICE file, excluding those notices that do not - - pertain to any part of the Derivative Works, in at least one - - of the following places: within a NOTICE text file distributed - - as part of the Derivative Works; within the Source form or - - documentation, if provided along with the Derivative Works; or, - - within a display generated by the Derivative Works, if and - - wherever such third-party notices normally appear. The contents - - of the NOTICE file are for informational purposes only and - - do not modify the License. You may add Your own attribution - - notices within Derivative Works that You distribute, alongside - - or as an addendum to the NOTICE text from the Work, provided - - that such additional attribution notices cannot be construed - - as modifying the License. - - - - - You may add Your own copyright statement to Your modifications and - - may provide additional or different license terms and conditions - - for use, reproduction, or distribution of Your modifications, or - - for any such Derivative Works as a whole, provided Your use, - - reproduction, and distribution of the Work otherwise complies with - - the conditions stated in this License. - - - - -5. Submission of Contributions. Unless You explicitly state otherwise, - - any Contribution intentionally submitted for inclusion in the Work - - by You to the Licensor shall be under the terms and conditions of - - this License, without any additional terms or conditions. - - Notwithstanding the above, nothing herein shall supersede or modify - - the terms of any separate license agreement you may have executed - - with Licensor regarding such Contributions. - - - - -6. Trademarks. This License does not grant permission to use the trade - - names, trademarks, service marks, or product names of the Licensor, - - except as required for reasonable and customary use in describing the - - origin of the Work and reproducing the content of the NOTICE file. - - - - -7. Disclaimer of Warranty. Unless required by applicable law or - - agreed to in writing, Licensor provides the Work (and each - - Contributor provides its Contributions) on an "AS IS" BASIS, - - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - - implied, including, without limitation, any warranties or conditions - - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - - PARTICULAR PURPOSE. You are solely responsible for determining the - - appropriateness of using or redistributing the Work and assume any - - risks associated with Your exercise of permissions under this License. - - - - -8. Limitation of Liability. In no event and under no legal theory, - - whether in tort (including negligence), contract, or otherwise, - - unless required by applicable law (such as deliberate and grossly - - negligent acts) or agreed to in writing, shall any Contributor be - - liable to You for damages, including any direct, indirect, special, - - incidental, or consequential damages of any character arising as a - - result of this License or out of the use or inability to use the - - Work (including but not limited to damages for loss of goodwill, - - work stoppage, computer failure or malfunction, or any and all - - other commercial damages or losses), even if such Contributor - - has been advised of the possibility of such damages. - - - - -9. Accepting Warranty or Additional Liability. While redistributing - - the Work or Derivative Works thereof, You may choose to offer, - - and charge a fee for, acceptance of support, warranty, indemnity, - - or other liability obligations and/or rights consistent with this - - License. However, in accepting such obligations, You may act only - - on Your own behalf and on Your sole responsibility, not on behalf - - of any other Contributor, and only if You agree to indemnify, - - defend, and hold each Contributor harmless for any liability - - incurred by, or claims asserted against, such Contributor by reason - - of your accepting any such warranty or additional liability. - - - - -END OF TERMS AND CONDITIONS - - - - -APPENDIX: How to apply the Apache License to your work. - - - - - To apply the Apache License to your work, attach the following - - boilerplate notice, with the fields enclosed by brackets "{}" - - replaced with your own identifying information. (Don't include - - the brackets!) The text should be enclosed in the appropriate - - comment syntax for the file format. We also recommend that a - - file or class name and description of purpose be included on the - - same "printed page" as the copyright notice for easier - - identification within third-party archives. - - - - -Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. - - - - -Licensed under the Apache License, Version 2.0 (the "License"); - -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - - - - - http://www.apache.org/licenses/LICENSE-2.0 - - - - -Unless required by applicable law or agreed to in writing, software - -distributed under the License is distributed on an "AS IS" BASIS, - -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -See the License for the specific language governing permissions and - -limitations under the License. \ No newline at end of file + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file From 687eea80c8860a6ce9a37f91b96c47d1009660e7 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Thu, 16 Feb 2023 14:51:43 +0800 Subject: [PATCH 133/150] =?UTF-8?q?=E8=A1=A5=E5=85=853.3.0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=98=E6=9B=B4=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Releases_Notes.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Releases_Notes.md b/Releases_Notes.md index 14d186b1..fa71a3a3 100644 --- a/Releases_Notes.md +++ b/Releases_Notes.md @@ -1,10 +1,6 @@ ## v3.3.0 -**协议调整** -- 项目由 AGPL-3.0 协议调整为 Apache-2.0 协议; - - **问题修复** - 修复 Connect 的 JMX-Port 配置未生效问题; - 修复 不存在 Connector 时,OverView 页面的数据一直处于加载中的问题; @@ -18,7 +14,8 @@ - 修复 connect 模块,max 纬度指标获取错误的问题; - 修复 Topic 指标大盘 TopN 指标显示信息错误的问题; - 修复 Broker Similar Config 显示错误的问题; - +- 修复解析 ZK 四字命令时,数据类型设置错误导致空指针的问题; +- 修复新增 Topic 时,清理策略选项版本控制错误的问题; **产品优化** - ZK Overview 页面补充默认展示的指标; @@ -31,6 +28,7 @@ - Task 模块中的返回中,补充任务的分组信息; - FAQ 补充 Ldap 的配置说明; - FAQ 补充接入 Kerberos 认证的 Kafka 集群的配置说明; +- ks_km_kafka_change_record 表增加时间纬度的索引,优化查询性能; **功能新增** From 91c60ce72cbd655637663c968c1e7d58f827b9c0 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 20 Feb 2023 16:01:29 +0800 Subject: [PATCH 134/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E6=96=B0?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=9A=84=E9=9B=86=E7=BE=A4=EF=BC=8CControlle?= =?UTF-8?q?r-Host=E4=B8=8D=E6=98=BE=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98(#9?= =?UTF-8?q?27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题原因: 1、新接入的集群,DB中暂未存储Broker信息,因此在存储Controller至DB时,查询DB中的Broker会查询为空。 解决方式: 1、存储Controller至DB前,主动获取一次Broker的信息。 --- .../handler/ControllerNodeChangeHandler.java | 5 ++- .../KafkaControllerService.java | 2 +- .../impl/KafkaControllerServiceImpl.java | 15 +++------ .../kafka/metadata/SyncControllerTask.java | 33 +++++++++++++++++-- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/zk/handler/ControllerNodeChangeHandler.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/zk/handler/ControllerNodeChangeHandler.java index b671c4a3..6498d118 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/zk/handler/ControllerNodeChangeHandler.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/flusher/zk/handler/ControllerNodeChangeHandler.java @@ -2,6 +2,7 @@ package com.xiaojukeji.know.streaming.km.core.flusher.zk.handler; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.KafkaController; import com.xiaojukeji.know.streaming.km.common.bean.po.changerecord.KafkaChangeRecordPO; import com.xiaojukeji.know.streaming.km.common.constant.Constant; @@ -100,7 +101,9 @@ public class ControllerNodeChangeHandler extends AbstractZKHandler implements ZN if (kafkaController == null) { kafkaControllerService.setNoKafkaController(clusterPhyId, triggerTime); } else { - kafkaControllerService.insertAndIgnoreDuplicateException(kafkaController); + Broker broker = kafkaZKDAO.getBrokerMetadata(clusterPhyId, kafkaController.getBrokerId()); + + kafkaControllerService.insertAndIgnoreDuplicateException(kafkaController, broker != null? broker.getHost(): "", broker != null? broker.getRack(): ""); } } catch (Exception e) { log.error("method=updateDBData||clusterPhyId={}||errMsg=exception", clusterPhyId, e); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/KafkaControllerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/KafkaControllerService.java index 4ae53f8b..310967e5 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/KafkaControllerService.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/KafkaControllerService.java @@ -12,7 +12,7 @@ import java.util.Map; public interface KafkaControllerService { Result getControllerFromKafka(ClusterPhy clusterPhy); - int insertAndIgnoreDuplicateException(KafkaController kafkaController); + int insertAndIgnoreDuplicateException(KafkaController kafkaController, String controllerHost, String controllerRack); int setNoKafkaController(Long clusterPhyId, Long triggerTime); diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java index 6047d844..511693c8 100644 --- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java +++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/kafkacontroller/impl/KafkaControllerServiceImpl.java @@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; -import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.KafkaController; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; @@ -15,7 +14,6 @@ import com.xiaojukeji.know.streaming.km.common.constant.Constant; import com.xiaojukeji.know.streaming.km.common.constant.KafkaConstant; import com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterRunStateEnum; import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils; -import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; import com.xiaojukeji.know.streaming.km.core.service.kafkacontroller.KafkaControllerService; import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaAdminClient; import com.xiaojukeji.know.streaming.km.persistence.mysql.kafkacontroller.KafkaControllerDAO; @@ -32,9 +30,6 @@ import java.util.*; public class KafkaControllerServiceImpl implements KafkaControllerService { private static final ILog log = LogFactory.getLog(KafkaControllerServiceImpl.class); - @Autowired - private BrokerService brokerService; - @Autowired private KafkaAdminClient kafkaAdminClient; @@ -54,16 +49,14 @@ public class KafkaControllerServiceImpl implements KafkaControllerService { } @Override - public int insertAndIgnoreDuplicateException(KafkaController kafkaController) { + public int insertAndIgnoreDuplicateException(KafkaController kafkaController, String controllerHost, String controllerRack) { try { - Broker broker = brokerService.getBrokerFromCacheFirst(kafkaController.getClusterPhyId(), kafkaController.getBrokerId()); - KafkaControllerPO kafkaControllerPO = new KafkaControllerPO(); kafkaControllerPO.setClusterPhyId(kafkaController.getClusterPhyId()); kafkaControllerPO.setBrokerId(kafkaController.getBrokerId()); kafkaControllerPO.setTimestamp(kafkaController.getTimestamp()); - kafkaControllerPO.setBrokerHost(broker != null? broker.getHost(): ""); - kafkaControllerPO.setBrokerRack(broker != null? broker.getRack(): ""); + kafkaControllerPO.setBrokerHost(controllerHost != null? controllerHost: ""); + kafkaControllerPO.setBrokerRack(controllerRack != null? controllerRack: ""); kafkaControllerDAO.insert(kafkaControllerPO); } catch (DuplicateKeyException dke) { // ignore @@ -92,7 +85,7 @@ public class KafkaControllerServiceImpl implements KafkaControllerService { // 归一化到秒, 并且将去1秒,避免gc导致时间不对 noKafkaController.setTimestamp(triggerTime); - return this.insertAndIgnoreDuplicateException(noKafkaController); + return this.insertAndIgnoreDuplicateException(noKafkaController, "", ""); } @Override diff --git a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java index ea0a7696..45b4ad03 100644 --- a/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java +++ b/km-task/src/main/java/com/xiaojukeji/know/streaming/km/task/kafka/metadata/SyncControllerTask.java @@ -5,12 +5,16 @@ import com.didiglobal.logi.job.common.TaskResult; import com.didiglobal.logi.job.core.consensual.ConsensualEnum; import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.LogFactory; +import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.kafkacontroller.KafkaController; import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; +import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService; import com.xiaojukeji.know.streaming.km.core.service.kafkacontroller.KafkaControllerService; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + @Task(name = "SyncControllerTask", description = "Controller信息同步到DB", @@ -19,7 +23,10 @@ import org.springframework.beans.factory.annotation.Autowired; consensual = ConsensualEnum.BROADCAST, timeout = 2 * 60) public class SyncControllerTask extends AbstractAsyncMetadataDispatchTask { - private static final ILog log = LogFactory.getLog(SyncControllerTask.class); + private static final ILog LOGGER = LogFactory.getLog(SyncControllerTask.class); + + @Autowired + private BrokerService brokerService; @Autowired private KafkaControllerService kafkaControllerService; @@ -33,10 +40,30 @@ public class SyncControllerTask extends AbstractAsyncMetadataDispatchTask { if (controllerResult.getData() == null) { kafkaControllerService.setNoKafkaController(clusterPhy.getId(), System.currentTimeMillis() / 1000L * 1000L); - } else { - kafkaControllerService.insertAndIgnoreDuplicateException(controllerResult.getData()); + + return TaskResult.SUCCESS; } + + Broker controllerBroker = null; + + Result> brokerListResult = brokerService.listBrokersFromKafka(clusterPhy); + if (brokerListResult.failed()) { + LOGGER.error("method=processClusterTask||clusterPhyId={}||result={}||errMsg=list brokers failed", clusterPhy.getId(), brokerListResult); + } else { + for (Broker broker: brokerListResult.getData()) { + if (broker.getBrokerId().equals(controllerResult.getData().getBrokerId())) { + controllerBroker = broker; + } + } + } + + kafkaControllerService.insertAndIgnoreDuplicateException( + controllerResult.getData(), + controllerBroker != null? controllerBroker.getHost(): "", + controllerBroker != null? controllerBroker.getRack(): "" + ); + return TaskResult.SUCCESS; } } From 7dc8f2dc480d1d4d192d0c8d5201c6d8a2fe1900 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Mon, 20 Feb 2023 16:14:21 +0800 Subject: [PATCH 135/150] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8DConnector?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=92=8CMM2=E5=88=97=E8=A1=A8=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?(#928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../km/biz/cluster/impl/ClusterConnectorsManagerImpl.java | 6 +++--- .../km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java index 46d34378..e982c588 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/cluster/impl/ClusterConnectorsManagerImpl.java @@ -136,13 +136,13 @@ public class ClusterConnectorsManagerImpl implements ClusterConnectorsManager { private PaginationResult pagingConnectorInLocal(List connectorVOList, ClusterConnectorsOverviewDTO dto) { //模糊匹配 - connectorVOList = PaginationUtil.pageByFuzzyFilter(connectorVOList, dto.getSearchKeywords(), Arrays.asList("connectClusterName")); + connectorVOList = PaginationUtil.pageByFuzzyFilter(connectorVOList, dto.getSearchKeywords(), Arrays.asList("connectorName")); //排序 if (!dto.getLatestMetricNames().isEmpty()) { - PaginationMetricsUtil.sortMetrics(connectorVOList, "latestMetrics", dto.getSortMetricNameList(), "connectClusterName", dto.getSortType()); + PaginationMetricsUtil.sortMetrics(connectorVOList, "latestMetrics", dto.getSortMetricNameList(), "connectorName", dto.getSortType()); } else { - PaginationUtil.pageBySort(connectorVOList, dto.getSortField(), dto.getSortType(), "connectClusterName", dto.getSortType()); + PaginationUtil.pageBySort(connectorVOList, dto.getSortField(), dto.getSortType(), "connectorName", dto.getSortType()); } //分页 diff --git a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java index 7d7351f2..62354118 100644 --- a/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java +++ b/km-biz/src/main/java/com/xiaojukeji/know/streaming/km/biz/connect/mm2/impl/MirrorMakerManagerImpl.java @@ -561,13 +561,13 @@ public class MirrorMakerManagerImpl implements MirrorMakerManager { } PaginationResult pagingMirrorMakerInLocal(List mirrorMakerOverviewVOList, ClusterMirrorMakersOverviewDTO dto) { - List mirrorMakerVOList = PaginationUtil.pageByFuzzyFilter(mirrorMakerOverviewVOList, dto.getSearchKeywords(), Arrays.asList("connectClusterName")); + List mirrorMakerVOList = PaginationUtil.pageByFuzzyFilter(mirrorMakerOverviewVOList, dto.getSearchKeywords(), Arrays.asList("connectorName")); //排序 if (!dto.getLatestMetricNames().isEmpty()) { - PaginationMetricsUtil.sortMetrics(mirrorMakerVOList, "latestMetrics", dto.getSortMetricNameList(), "connectClusterName", dto.getSortType()); + PaginationMetricsUtil.sortMetrics(mirrorMakerVOList, "latestMetrics", dto.getSortMetricNameList(), "connectorName", dto.getSortType()); } else { - PaginationUtil.pageBySort(mirrorMakerVOList, dto.getSortField(), dto.getSortType(), "connectClusterName", dto.getSortType()); + PaginationUtil.pageBySort(mirrorMakerVOList, dto.getSortField(), dto.getSortType(), "connectorName", dto.getSortType()); } //分页 From fbe6945d3bd0189b71173f9f1b76588a67370d29 Mon Sep 17 00:00:00 2001 From: erge Date: Mon, 20 Feb 2023 15:26:26 +0800 Subject: [PATCH 136/150] =?UTF-8?q?[Bugfix]zookeeper=E9=A1=B5=E9=9D=A2lead?= =?UTF-8?q?er=E8=8A=82=E7=82=B9=E6=98=BE=E7=A4=BA=E5=BC=82=E5=B8=B8(#873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CardBar/ZookeeperCard.tsx | 19 ++++++++++++-- .../src/components/CardBar/index.less | 25 ++++++++++--------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx index 8f6c65ed..cc837349 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import CardBar, { healthDataProps } from './index'; -import { Utils } from 'knowdesign'; +import { Tooltip, Utils } from 'knowdesign'; import api from '@src/api'; import { HealthStateEnum } from '../HealthState'; @@ -81,7 +81,22 @@ const ZookeeperCard = () => { { title: 'Leader', value() { - return {leaderNode || '-'}; + return ( + + + {leaderNode || '-'} + + + ); }, }, { diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index 472cafa6..e5302338 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -64,21 +64,21 @@ margin-left: 12px; padding: 12px 20px; .card-bar-colunms-header { + font-size: 14px; + color: #74788d; + letter-spacing: 0; + text-align: justify; + line-height: 20px; + .anticon-question-circle { + padding: 3px 3px 2px 3px; + margin-left: -3px; font-size: 14px; - color: #74788d; - letter-spacing: 0; - text-align: justify; - line-height: 20px; - .anticon-question-circle { - padding: 3px 3px 2px 3px; - margin-left: -3px; - font-size: 14px; - border-radius: 50%; - &:hover { - background: rgba(33, 37, 41, 0.04); - } + border-radius: 50%; + &:hover { + background: rgba(33, 37, 41, 0.04); } } + } .card-bar-colunms-body { font-size: 40px; color: #212529; @@ -89,6 +89,7 @@ margin-top: 5px; .num { font-family: DIDIFD-Medium; + overflow: hidden; } .sub-title { font-family: @font-family; From 5c26e8947babb2f67c1a1da78d9b2eb7cb7e74b0 Mon Sep 17 00:00:00 2001 From: erge Date: Mon, 20 Feb 2023 16:36:45 +0800 Subject: [PATCH 137/150] =?UTF-8?q?[Optimize]=20JSON=E6=96=B0=E5=A2=9EMM2?= =?UTF-8?q?=20Drawer=20Title=E6=96=87=E6=A1=88=E5=8F=98=E6=9B=B4(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx index 35c6a2d6..eb58aa2c 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MirrorMaker2/AddMM2JSON.tsx @@ -141,7 +141,7 @@ export default forwardRef((props: any, ref) => { return ( Date: Mon, 20 Feb 2023 16:31:35 +0800 Subject: [PATCH 138/150] =?UTF-8?q?[Optimize]=20=E6=96=B0=E5=A2=9E/?= =?UTF-8?q?=E7=BC=96=E8=BE=91MM2=20Topic=20=E7=94=B1=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E9=9B=86=E7=BE=A4=E8=8E=B7=E5=8F=96=E6=94=B9=E4=B8=BA=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E7=9A=84sourceKafka=E9=9B=86=E7=BE=A4=E8=8E=B7?= =?UTF-8?q?=E5=8F=96&=20=E6=96=B0=E5=A2=9E/=E7=BC=96=E8=BE=91MM2=E5=85=A5?= =?UTF-8?q?=E5=8F=82=E4=BC=98=E5=8C=96(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout-clusters-fe/src/constants/reg.ts | 2 +- .../src/pages/Connect/AddConnector.tsx | 2 +- .../src/pages/ConnectDashboard/index.tsx | 13 +- .../src/pages/MirrorMaker2/AddMM2.tsx | 134 +++++++++++------- .../pages/MutliClusterPage/AccessCluster.tsx | 2 +- 5 files changed, 93 insertions(+), 60 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/constants/reg.ts b/km-console/packages/layout-clusters-fe/src/constants/reg.ts index fe3b51ab..7463882a 100644 --- a/km-console/packages/layout-clusters-fe/src/constants/reg.ts +++ b/km-console/packages/layout-clusters-fe/src/constants/reg.ts @@ -2,7 +2,7 @@ export const regNonnegativeInteger = /^\d+$/g; // 非负正整数 export const regOddNumber = /^\d*[13579]$/; //奇数 -export const regClusterName = /^[\u4E00-\u9FA5A-Za-z0-9\_\-\!\"\#\$\%&'()\*\+,./\:\;\<=\>?\@\[\\\]^\`\{\|\}~]*$/im; // 大、小写字母、数字、-、_ new RegExp('\[a-z0-9_-]$', 'g') +export const regClusterName = /^[\u4E00-\u9FA5A-Za-z0-9\_\-\!\#\$\%&'()\*\+,./\:\;\<=\>?\@\[\\\]^\`\{\|\}~]*$/im; // 大、小写字母、数字、-、_ new RegExp('\[a-z0-9_-]$', 'g') export const regUsername = /^[_a-zA-Z-]*$/; // 大、小写字母、数字、-、_ new RegExp('\[a-z0-9_-]$', 'g') export const regIpAddress = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; diff --git a/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnector.tsx b/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnector.tsx index 87a3da9f..8724a029 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnector.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/Connect/AddConnector.tsx @@ -485,7 +485,7 @@ const StepFormSecond = (props: SubFormProps) => { } if (!new RegExp(regClusterName).test(value)) { return Promise.reject( - 'Connector 名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~' + "Connector 名称支持中英文、数字、特殊字符 ! # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~" ); } return Utils.request(api.isConnectorExist(prevForm.getFieldValue('connectClusterId'), value)).then( diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx index f1b4481e..0803aa54 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/ConnectDashboard/index.tsx @@ -37,6 +37,7 @@ const DraggableCharts = (): JSX.Element => { connectClusters: [], connectors: [], }); + const [screenType, setScreenType] = useState('all'); const curFetchingTimestamp = useRef(0); const metricRankList = useRef([]); const metricFilterRef = useRef(null); @@ -149,7 +150,14 @@ const DraggableCharts = (): JSX.Element => { const nullDataMetricData = [...newConnectClusterData, ...newConnectorData].filter((item) => item !== null); formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName)); nullDataMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName)); - setMetricChartData([...formattedMetricData, ...nullDataMetricData]); + const filterMetricData = [...formattedMetricData, ...nullDataMetricData]; + setMetricChartData( + screenType === 'Connect' + ? filterMetricData.filter((item) => item.metricType === MetricType.Connect) + : screenType === 'Connector' + ? filterMetricData.filter((item) => item.metricType === MetricType.Connectors) + : filterMetricData + ); } else { setMetricChartData([]); } @@ -216,7 +224,7 @@ const DraggableCharts = (): JSX.Element => { if (Object.values(metricList).some((list) => list.length) && curHeaderOptions) { getMetricChartData(); } - }, [curHeaderOptions]); + }, [curHeaderOptions, screenType]); useEffect(() => { if (Object.values(metricList).some((list) => list.length) && curHeaderOptions) { @@ -242,6 +250,7 @@ const DraggableCharts = (): JSX.Element => { name: 'Connect', customContent: , }} + setScreenType={setScreenType} /> { }>(); const [form] = useStepForm(0); const [topicData, setTopicData] = useState([]); + const [topicDataLoading, setTopicDataLoading] = useState(false); + const [givenSourceKafkaId, setGivenSourceKafkaId] = useState(clusterId); const { type, detail, setSourceKafkaClusterId, setBootstrapServers, setSourceDetailConfigs, setCheckoutPointDetailConfigs } = useContext(StepsFormContent); const isEdit = type === 'edit'; @@ -178,21 +180,26 @@ const StepFormFirst = (props: SubFormProps) => { }; // 获取Topic列表 - const getTopicList = () => { + const getTopicList = (givenSourceKafkaId: any) => { // ! 需整理 - Utils.request(api.getTopicMetaList(Number(clusterId))).then((res: any) => { - const dataDe = res || []; - const dataHandle = dataDe.map((item: any) => { - return { - ...item, - key: item.topicName, - label: item.topicName, - value: item.topicName, - title: item.topicName, - }; + setTopicDataLoading(true); + Utils.request(api.getTopicMetaList(Number(givenSourceKafkaId))) + .then((res: any) => { + const dataDe = res || []; + const dataHandle = dataDe.map((item: any) => { + return { + ...item, + key: item.topicName, + label: item.topicName, + value: item.topicName, + title: item.topicName, + }; + }); + setTopicData(dataHandle); + }) + .finally(() => { + setTopicDataLoading(false); }); - setTopicData(dataHandle); - }); }; const getMM2Config = (connectClusterId: string | number) => { @@ -207,7 +214,6 @@ const StepFormFirst = (props: SubFormProps) => { const detailConfigs: any[] = isEdit && res.length > 1 ? res?.[2] : []; let sourceConfigs: any; let checkpointConfigs: any; - detailConfigs?.forEach((config) => { if (config['connector.class'] === 'org.apache.kafka.connect.mirror.MirrorCheckpointConnector') { checkpointConfigs = config; @@ -215,6 +221,7 @@ const StepFormFirst = (props: SubFormProps) => { } else if (config['connector.class'] === 'org.apache.kafka.connect.mirror.MirrorSourceConnector') { sourceConfigs = config; setSourceDetailConfigs(config); + setGivenSourceKafkaId(sourceConfigs['source.cluster.alias']); } }); const formItemValue: any = {}; @@ -222,36 +229,47 @@ const StepFormFirst = (props: SubFormProps) => { res?.[0].configs.forEach(({ definition }: any) => { if (existConfigItems.sourceConfigs.includes(definition.name)) { if (isEdit && sourceConfigs[definition.name]) { - formItemValue[definition.name] = sourceConfigs[definition.name]; if (definition.name === 'topics') { - sourceConfigs[definition.name] !== '.*' - ? formItemValues.push({ + formItemValue[definition.name] = topicTargetKeys || sourceConfigs[definition.name]; + if (sourceConfigs[definition.name] === '.*' && topicTargetKeys.length < 1) { + formItemValues.push({ + name: 'priority', + value: 'allTopic', + }); + } else { + formItemValues.push( + { name: 'topics', - value: sourceConfigs[definition.name].split(',') || null, - }) - : formItemValues.push({ - name: 'priority', - value: 'allTopic', - }); + value: topicTargetKeys.length > 0 ? topicTargetKeys : sourceConfigs[definition.name].split(','), + }, + { name: 'priority', value: 'givenTopic' } + ); + topicTargetKeys.length < 1 && setTopicTargetKeys(sourceConfigs[definition.name].split(',')); + } } else { + formItemValue[definition.name] = sourceConfigs[definition.name]; formItemValues.push({ name: definition.name, value: sourceConfigs[definition.name] || null, }); } } else { - formItemValue[definition.name] = definition.defaultValue; if (definition.name === 'topics') { - definition.defaultValue !== '.*' + formItemValue['topics'] = topicTargetKeys || definition.defaultValue.split(','); + definition.defaultValue === '.*' && topicTargetKeys.length < 1 ? formItemValues.push({ - name: 'topics', - value: definition.defaultValue.split(',') || null, - }) - : formItemValues.push({ name: 'priority', value: 'allTopic', - }); + }) + : formItemValues.push( + { + name: 'topics', + value: topicTargetKeys || definition.defaultValue, + }, + { name: 'priority', value: 'givenTopic' } + ); } else { + formItemValue[definition.name] = definition.defaultValue; formItemValues.push({ name: definition.name, value: definition.defaultValue || null, @@ -290,11 +308,14 @@ const StepFormFirst = (props: SubFormProps) => { }; useEffect(() => { - getTopicList(); getConnectClustersList(); getSourceKafkaClustersList(); }, []); + useEffect(() => { + getTopicList(givenSourceKafkaId); + }, [givenSourceKafkaId]); + useEffect(() => { form.resetFields(existConfigItems.sourceConfigs); form.setFields(formItemValues); @@ -306,6 +327,9 @@ const StepFormFirst = (props: SubFormProps) => { setBootstrapServers(bootstrapServers); }, [sourcekafkaClustersId]); + useEffect(() => { + form.setFieldsValue({ topics: topicTargetKeys }); + }, [topicTargetKeys]); // useEffect(() => { // connectorConfig && // form.setFieldsValue({ @@ -332,7 +356,6 @@ const StepFormFirst = (props: SubFormProps) => { 'emit.checkpoints.interval.seconds': 60, 'checkpoints.topic.replication.factor': false, 'replication.policy.separator': '.', - priority: 'allTopic', }; form.setFieldsValue(config); setFormItemValue((state: any) => { @@ -366,9 +389,7 @@ const StepFormFirst = (props: SubFormProps) => { return Promise.reject('MM2任务名称长度限制在1~64字符'); } if (!new RegExp(regClusterName).test(value)) { - return Promise.reject( - 'MM2 名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~' - ); + return Promise.reject("MM2 名称支持中英文、数字、特殊字符 ! # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~"); } return Promise.resolve(); // return Utils.request(api.isConnectorExist(prevForm.getFieldValue('connectClusterId'), value)).then( @@ -398,6 +419,7 @@ const StepFormFirst = (props: SubFormProps) => { onChange={(e, option: any) => { setSourcekafkaClustersId(e); setSourceKafkaClusterId(option?.value); + setGivenSourceKafkaId(option?.value); }} /> @@ -421,22 +443,24 @@ const StepFormFirst = (props: SubFormProps) => { }, ]} > - option.topicName.indexOf(inputValue) > -1} - targetKeys={topicTargetKeys} - onChange={topicChange} - render={(item) => item.title} - suffix={} - /> + + option.topicName.indexOf(inputValue) > -1} + targetKeys={topicTargetKeys} + onChange={topicChange} + render={(item) => item.title} + suffix={} + /> + ) : null; }} @@ -1006,7 +1030,7 @@ export default forwardRef( 'emit.checkpoints.enabled': result['emit.checkpoints.enabled'], 'emit.checkpoints.interval.seconds': result['emit.checkpoints.interval.seconds'], 'checkpoints.topic.replication.factor': result['checkpoints.topic.replication.factor'], - 'source.cluster.alias': sourceKafkaClusterId, + 'source.cluster.alias': sourceKafkaClusterId || res[0].sourceKafkaClusterId, name: detail?.checkpointConnector || result.name, 'source.cluster.bootstrap.servers': bootstrapServers || checkoutPointDetailConfigs?.['source.cluster.bootstrap.servers'], }; @@ -1015,7 +1039,7 @@ export default forwardRef( 'connector.class': 'org.apache.kafka.connect.mirror.MirrorHeartbeatConnector', 'heartbeats.topic.replication.factor': result['heartbeats.topic.replication.factor'], 'emit.heartbeats.interval.seconds': result['emit.heartbeats.interval.seconds'], - 'source.cluster.alias': sourceKafkaClusterId, + 'source.cluster.alias': sourceKafkaClusterId || res[0].sourceKafkaClusterId, name: detail?.heartbeatConnector || result.name, 'source.cluster.bootstrap.servers': bootstrapServers || heartbeatDetailConfigs?.['source.cluster.bootstrap.servers'], }; @@ -1036,7 +1060,7 @@ export default forwardRef( 'replication.policy.class': result['replication.policy.class'], 'replication.policy.separator': result['replication.policy.separator'], topics: result['priority'] === 'givenTopic' ? result['topics'].join() : '.*', - 'source.cluster.alias': sourceKafkaClusterId, + 'source.cluster.alias': sourceKafkaClusterId || res[0].sourceKafkaClusterId, name: result.name, 'source.cluster.bootstrap.servers': bootstrapServers || sourceDetailConfigs?.['source.cluster.bootstrap.servers'], }; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx index 8f06b99b..28992f47 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx @@ -204,7 +204,7 @@ const ClusterTabContent = forwardRef((props: any, ref): JSX.Element => { return Promise.reject('集群名称长度限制在1~128字符'); } if (!new RegExp(regClusterName).test(value)) { - return Promise.reject('集群名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~'); + return Promise.reject("集群名称支持中英文、数字、特殊字符 ! # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~"); } return Utils.request(api.getClusterBasicExit(value)) .then((res: any) => { From 18e3fbf41dab95d1f26d54072dace6e70f39738c Mon Sep 17 00:00:00 2001 From: erge Date: Mon, 20 Feb 2023 16:24:45 +0800 Subject: [PATCH 139/150] =?UTF-8?q?[Optimize]=20=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E9=A1=B9=E6=97=B6=E9=97=B4=E5=92=8C=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E6=98=BE=E7=A4=BA(didi#930)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CardBar/index.tsx | 24 ++++++++------- .../src/pages/SingleClusterDetail/config.tsx | 30 +++++++++++-------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx index ea683e07..4fb4121e 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx @@ -165,17 +165,21 @@ const CardBar = (props: CardBarProps) => { dataIndex: 'passed', width: '30%', render(value: boolean, record: any) { - const icon = value ? : ; - const txt = value ? '已通过' : '未通过'; - const notPassedResNameList = record.notPassedResNameList || []; - return ( -
-
- {icon} {txt} + if (record?.updateTime) { + const icon = value ? : ; + const txt = value ? '已通过' : '未通过'; + const notPassedResNameList = record.notPassedResNameList || []; + return ( +
+
+ {icon} {txt} +
+ {}
- {} -
- ); + ); + } else { + return '-'; + } }, }, ]; diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx index e7d4f2ba..e72bae1d 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/config.tsx @@ -215,7 +215,7 @@ export const getDetailColumn = (clusterId: number) => [ width: 190, dataIndex: 'updateTime', render: (text: string) => { - return moment(text).format(timeFormat); + return text ? moment(text).format(timeFormat) : '-'; }, }, { @@ -224,21 +224,25 @@ export const getDetailColumn = (clusterId: number) => [ width: 280, // eslint-disable-next-line react/display-name render: (passed: boolean, record: any) => { - if (passed) { + if (record?.updateTime) { + if (passed) { + return ( + <> + + 通过 + + ); + } return ( - <> - - 通过 - +
+ +
未通过
+ +
); + } else { + return '-'; } - return ( -
- -
未通过
- -
- ); }, }, ]; From 3d6f405b69b417c349e13e2dcfe5b7a1289ff848 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 22 Feb 2023 10:33:02 +0800 Subject: [PATCH 140/150] =?UTF-8?q?[Bugfix]=E8=AE=A2=E6=AD=A3=E5=A4=B1?= =?UTF-8?q?=E6=95=88=E7=9A=84=E9=82=AE=E7=AE=B1=E5=9C=B0=E5=9D=80(#944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Bugfix]订正语句(#944) --- CODE_OF_CONDUCT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a70c8889..5d8023ba 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -4,7 +4,7 @@ ## Our Pledge In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and +contributors and maintainers pledge to making participation in our project, and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, @@ -56,7 +56,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at shirenchuang@didiglobal.com . All +reported by contacting the project team at https://knowstreaming.com/support-center . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. From 4852c01c88541ac48a21d8a108e1a4b6477415f8 Mon Sep 17 00:00:00 2001 From: zengqiao Date: Wed, 22 Feb 2023 11:29:01 +0800 Subject: [PATCH 141/150] =?UTF-8?q?[Feature]=E8=A1=A5=E5=85=85=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E4=BB=A3=E7=A0=81=E7=9B=B8=E5=85=B3=E6=96=87=E6=A1=A3?= =?UTF-8?q?(#947)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、补充贡献者名单,如有遗漏,辛苦告知; 2、补充贡献指南; --- docs/contribute_guide/assets/分支管理.drawio | 111 ++++++++++++ docs/contribute_guide/assets/分支管理.png | Bin 0 -> 65084 bytes docs/contribute_guide/assets/环境初始化.jpg | Bin 0 -> 184517 bytes docs/contribute_guide/assets/申请合并.jpg | Bin 0 -> 81863 bytes docs/contribute_guide/assets/问题认领.jpg | Bin 0 -> 645752 bytes .../代码规范.md | 0 docs/contribute_guide/贡献指南.md | 167 ++++++++++++++++++ docs/contribute_guide/贡献记录.md | 82 +++++++++ docs/contributer_guide/开发者名单.md | 6 - docs/contributer_guide/贡献流程.md | 6 - 10 files changed, 360 insertions(+), 12 deletions(-) create mode 100644 docs/contribute_guide/assets/分支管理.drawio create mode 100644 docs/contribute_guide/assets/分支管理.png create mode 100644 docs/contribute_guide/assets/环境初始化.jpg create mode 100644 docs/contribute_guide/assets/申请合并.jpg create mode 100644 docs/contribute_guide/assets/问题认领.jpg rename docs/{contributer_guide => contribute_guide}/代码规范.md (100%) create mode 100644 docs/contribute_guide/贡献指南.md create mode 100644 docs/contribute_guide/贡献记录.md delete mode 100644 docs/contributer_guide/开发者名单.md delete mode 100644 docs/contributer_guide/贡献流程.md diff --git a/docs/contribute_guide/assets/分支管理.drawio b/docs/contribute_guide/assets/分支管理.drawio new file mode 100644 index 00000000..0e7e3d37 --- /dev/null +++ b/docs/contribute_guide/assets/分支管理.drawio @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/contribute_guide/assets/分支管理.png b/docs/contribute_guide/assets/分支管理.png new file mode 100644 index 0000000000000000000000000000000000000000..867fecd4b464a1adf8d3ea9af833b6460e25d9de GIT binary patch literal 65084 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 literal 0 HcmV?d00001 diff --git a/docs/contribute_guide/assets/环境初始化.jpg b/docs/contribute_guide/assets/环境初始化.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31ff5f2801856c469321cae19867e03e45e21c8a GIT binary patch literal 184517 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%0A=GcE8)d|FVGp-=F^<&)`n%)ssv= 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 literal 0 HcmV?d00001 diff --git a/docs/contribute_guide/assets/问题认领.jpg b/docs/contribute_guide/assets/问题认领.jpg new file mode 100644 index 0000000000000000000000000000000000000000..62da47281fc249fc7e63a9efa09ee1236fa8b2c6 GIT binary patch literal 645752 zcmeFYXINBA(@S+j z;Qge4t^Ka^=T7>V^lu94$;XhtVe-urQsOqCZte>YhllyXuL>#2p9a*=8=I1yvqX_7y4E(dr`_AQb6{ubtZ0&$gz%2-biW^QR_V0=mcw;+rdx2snJNtpm3C^$US z((t^HoxOt)(0IQ^S92xt2MDXX|67|r{|39cx&PC?o14!+@E^IrnjjMw{KBpVy2bo{_@Dd< z3XA}C_q&LLzjVH#MwZ~64b&zXpk4tE5I*hccEJRM`9RnpIPmhNzwqwfeg?)M3|b3mv|qTf6$o>I@GY+}{VRXb zJPw6h{?!+0lXs||?qB!;w@?uJ)7N4|fb|Lfr2U`1UdDg54`GP)wXy~AGynh*jr6lQ znH>xuEFTtd<%A!QH;87euigp%H%(}SYejq&_Zol~lWr2)(1?&E$6G)_2nB56)AU$c5r`H92 z5C&z3Y4;`5T%b-Mmbm#@@_uq6uCJwf#LUx`^3v3;1nT&&s2aqFE2K&(^gMvM)fLOi1 zBs|f|-y`zxGW{a~@UPMMi;nsW>EQC-ru_ZcTN7`)%_oR|INXF z@Bd$K`~XjoPm_N}<1Y>%w;=V9en=-|5b_Sv3@vsXFv~rH73;0$2VL^7xWHZLdL-! z^0GpTrxld|;KbLQ&;Y<5$8R4(!twrJxa55R(DEb_iSPb}y9Cblh7@r6fBhFuq8(f& zI00ZF!y_Uz@=tqYCpU6%m0$#C8aE&ShyaqHJc@uSa1PJ`^nr_j8DIt211_Mw`hs>I z3Pb_%KoXD&WPslIE>H-R0Oddx@B(-ZGz0H|PM{AM0!D!;U;(tAb>Ihp0uBKz2?+@$ z2|Wob2{(x#i5Q6ti6V(Qi8hHo$t4m?5_=L?5+9Oak_eJ`lItWHBzYu-B&8(PB(F%` zl5~;`l8lkek*tt>Bl$`4ixfgiN6JRZPbyBTK&novOKL)DMe0oILmEOFOL~JeoAf?u z8R>J43*msAg2+MCA$kxqhy%nM5(-Ixq(cfIWsq7(3pmab zkY&ggo}7KRo#)i~7# z6_%QbT8vtQ+Kk$hI)*xjx`MiedW3qN8cV}WBSE7@V@2anlSFf$rk19c<`c~xEiJ7u z?O9q2T0h!E+WWLGX$NRmXpiYw=%nfN=$z;x=(6c5={o4<==SL8>BZ@F=pE?8>9gsp z>AUF{=`jqf4Dt-d44w=Kh9ZV0hH-`+MruYeMqNf1##qJz#(KsP#w{jlCUGV`CO4)8 zrXr@dOfyUe%&g33n9Z1jm@}E5F%L3tvQV;!v*@#UvfN-PXX$2HV}-DauU6%x=RT&0fUb#=gt};SlFA<_P4-;dsq4&GCzq zpHr99i!+V0mUEo*h>Mp?hs%pAovV&(it87*5Vt;e0Cz5TGxuj6G9GCj3!W&RVxC@} zEnYU>bG#nB>AbIb=lMwar1>oQ;`qw>KJXpz3-BBAU*&(m-_5@zz#*U`;4g4Tpk3gb zAgiFZpr7C!L8Rb!A$B2Mp+KSgLOnvLQ+%h4PDPw5J@xSvR#-yVS~yv_PWZD3jflF4 zx5yolE)kTdps1;6yl9Q+yco5Zx|pxnJ+XeVBXJ3FTk)IX&ElI9ToT3-u@W^BpCsue zp^_nzWs;Lp6jJI^0aA~oMx{xmRiu5Ti=; zikQAgbV?kgL$IKsc>(I`DMG>4h^aXD*&eKGSmMr=ql?hhmZ9 zq!PW7ff7RLjS^Z}M%hcbSb0{3RmD{0rb?FzUR6ysRJB(1o0_PatJ*`g8Fe;wbM;L1 zfwSai&!0t{eRuX)LscVOM=t}AO>ps`rIe+?m==sL;M|v81*Yr9rKrR?wNWbt=pGDtV zzfgbCK-j>?pvC}YsA3pr*kMF&WMY(SG-J$f>|tDOjJl|H@!G{66FQSCCWR)em!vO+ zUTQHVHN9w>Z~E!7*yZ5M&1Qg^v01*^XLAYj5c5_G3JY_KB8#t(KGfn9la z<5_BZk`@jnVM3#ber2HFPJ z1yKdL2E7et2@VMEfeXN+;bT{2uBKjH4$%m?8-fZo34Izy66PHCCY%in>OVwCMx;cn zL~2JCMPj0CqUxiWql2SA#7M`a#caeH#6F25i}Q%!=(A=s#=DgUvwS3e3 zraMA+((mlwh20%2P%bFBM{_UY-tzrR_uo7aeQ^5$uF$t|s_1-C-9x^IX%CMcc|IC1 z)+w$n;V;Q3`SsZE@ocGKX;YbaSwT5Pd1U$53Y&`lC+bhCpYlA-c#5wKu3WCNtm>&& ztFC^=_bjJ|v?ikF+jFPqqc1MJcvCA^TUy6am+=yK8S(P_E7w=kuT5Te)vMRnHi$Jm zY-DXrZz5@mX+kyoHZQ-ie>3*>;@hs4b1jXn@~xHcPQ5E?V{gl8r)j^@PDI8c4?3=P zY<2o{u64n>7P{@aCwnYsOf{shsI%* z;l_`uADc$hM&68`9c>xY9BUtkj(1L6nCP1{o*bIGJT*3LJv}?)G_ySGIlDO*IES8( zoX0LCej@*r{+a1>!6M&c*^<;!-LmR(+scKN535&J7uG!1etZf0f?L1wmHunNhTul^ zrsC$iZwB8+zdL?k|8eyPb}Mz8dHdmx+*jW3Ixs(2 zJPbO-9;IWrF;&Ov$Nj%-e{Eo+aFn?Fcqx1f!G!RM7(^sqbqjU-9SfY~-K5@Nmj1I6 z0O;)jfOQm%9~}SWg1;$9{^YM9O!C)tg8mQqPri8a0*oPmTJUxf#_M$ez)SEFPXyc6 z0f3Vb-bx1`RZ{xr0x>5g%v(wEH$Xxc3Wg>aB5{u&04Q4k03S*uVjmHS_zEzem;->0 zkUu%-2{i?nZ#=$m0?p++C!XB@z5K~fLCoJW{^uQ(TuDj!q{@G}6FUHAD)LS0NeGEB zK*~%4VJ0E=0D_>*E7a2EGT# zn8{gAozbOWwQ!>p4q;PF$}Oc5IsdAY-Es^is^lJ;OijbV$;HhhCN3cdjke>3R8g?iSp;|Ddp} zyyD5z%Bt#Tuj?Bco0{LeZRzUn>Fw(u7<@lIF*!9oGdnlGvby$V{p-f&x9{kmd;156 zN0{SZCvuShkUwJmBiVn*#SF?tN=61Dqdbv|gft4gAk1Xsr_NBY=vq*^g|G@MCQ-4S z&nx_K#ryl4}7ByGef+2q`Iq90Gxm zQ;>s&f(ne%C@83CsD2lkzY5*&!f;X;|0zVU5fZQsGBPqs@IMnRH7(Qs<3julZWR?1 z=K(qh3200ZW&jG{ab@X}K*f`?L>fu(y67MY{9nZX)yMxo%%NP_q7WdCf;O600NQ7r z_FK#g)Z)-8IkKx2$vmgOy6{GOg5&;p(p7ZW0Bo0umi*)D*1aww!0lH!JY9o;!$b4z78VABS-u)%L6q+?H|gpLgj0 zqr9{LUlFKAex*4>pO=+s~IE->5`90(=R#zl4a?DWW^Kk;s1L??UjCQ z#W8JxXf;RlQW}5x_&)8~y5NhzM~@`h+v!F_s;A3EM3bBhJCQjEngp%Q{D2bLgMkC9 z`k%d%z6*U`f-339l*x)Hw6M^~GVMgSN+i z(svI++x_&EN;ADEKkDq7%A{|63b(Wz=d#d0)xoU{G}y8)kK7`VVlo6u)si%Iz3Rm8 zUY)*Y@X_nOo??W-{AH~iy-8N*7XaH8=W%5}QP*3K^Nc%y2+^5zoGiXvn+oS&7ywO8 zkguPH3o(rN)9QsY^9De36)T>Qh-}c<=8gzwkZj+vEx?&!rckZRz3q3S(C2!XHkp3d zQcO>MwdZ!Rw%ia`2}-B54U`-p`;vL&z<5hpf!+78{S@kBIlRU%s-~T`#@I4i%xO(Z znB}=5)$##XSx2CSi{6S^4w521WOdlEBY_j0P!hK)C%qbHzfT0zhTiVINMpIRmLG7D zsd~tOJNk!9n|`8Xoh)V;9o4l&N)YrHWWo9He`vW}h!U;w;Tbxk#`0XM(RozxL0^Jf zuNw}6!7n4`)jFG9&Z5X{Uw)g88u!~#C^PcfE6bL$OEJ71v>?5_Q5#(uTpG5p`Fp6l zZHWNNd}*%xGUkCq*N=<^BG9Z?O)s~r-@;2?^5N%$%ti0`CkeFMeIz0xh1h~3Q}V8K z$qEvEF*yL7S^%4+?NA99G!UKyC4J{6nA9%nKclMgykN{ZCHhn&pGbP0I4yO4 zGiJlSn}$=VbZRRuuW@krB(7#Ci!RU1%&cKDFjTQ6u)-*az-VkY5g;ui0;^Ro`L)|7y9WZU0g|a=R5(b9f(} zLIjfIX)Au%aLz|u|IVWQguKCmE6;94`YCT(QRFwP3pJjoE#0M*NNj%HG}U zf%3OAW3H@<=cCGdTqGM_qHUUrwgW5Nxs%j&MzV4nZr+-j<atn1rA7M+1nh9={sH`p!$S(-H?WQ_RASa zV!(9OOLBJ9QUC5j|H{3sJv6&E52o_`)B(Tl_T!elz`Rdg9HE`IiXXCan~Tp3U+Xq~ z{KmrN4BG7QItGfmVmZ2Bl132DIA79ZVBz<*BD+GbIm~FRhH6HZVxwyVNhwfW9^e4I z)llOc)qOPT?+}CwP-z=($dFYs=$78xy!g7fhtI-Qd9&iFiLCjBy^lZmLXtmx(6KgG zG?qvNei%_p)_M?F;s4c6iNKASPUOD81UPS{6$sLgK$ANm`ww&eo9rbK=%_+sp24yoTilRY-{@Yre`9ckq1C-@Ovvs)bFY7@Dgt8xxlU|gPQU0N!k@w;nAaebIEzTEm zIemz*FoxJ5ED;Emm@#~FIbsBl#N*A-kLfQ;zi8Zk^mX^OlcDy9;MvYs#Si)dzK&!Q zfktGP+1vsR`1EPCbMV{KleY3nuQllfOnI zp?Fnhuwf{ANAAUwk34FTcd31eK&Eih#9eGh6;`I(MQk+Q|rKD<2P+B^h~NY zH%8A2y_C5_=q}qt$IK{EEn42Plk75h{=wul7clXX{~G%jBxQnnyej5kWdX?^uQ0h} zH4#`__}*S%F(9;{!o}3uHK3n3y!W@i8w;;+~A-P=!Gvb3f zKVmi=c#zThtzjZ0CWww>>~n8h{L>G8P7P`6IiK;Xl$Uakv{VaO+`s^U>-Un+{`0wD z6ST7^C!6x{8p~2$dhgn->pT%CkJ(H?e@)e7Qa=|vGi5aol(pNUmbSA{X1ygm;uz}k z&x^mw64Ua0SxaglO%?)Ja9W}#;Tu7+h1TFU++hH6*wZRJX|h%@nP zOs7n?K5jq0P3yreWJ!9!b|?D>PyJh(XVjt(0m?8PhO%}uj2zY%M~69Bor|YmKDRLO zyf6RK%TIHIJnv5PDJSbMab3A>@YOPuLaFzfn`sT`{w}Vxi^uDsh(K2kX#kX2(@I>! zCWWyiI1p%m^!c9a&LDsV$NQSj`lOTZ=xk_xf3~fwy zQQv6oG1o2_lA1oIk&>3$NS@}F5B9WkOEWnKa#)^ctO-zB`jM#o&-kVPzLwOKXB&OZ z@=oc=9e~J44YL|mx zw-#N=k!R_2mU#}I$%3)G78-kq#&}^har$Pd?NXzS*9^_$eC6LQh&G>2dHB-$L%oP+ z&_-q$?CONCJezH1{1yCT0wa!XvO!B?6>+OyTh!a# z&i-e<^V2zClr-av!te7G6d`zq;bB3-Lv&*=-?wP6JgrmyyoEDmTp$b27FRAaByR%WRd zL>X`Qew}iM2!u*8YFZoEu-ZZ;5pIZu*?7|c=nX+0xTOL|2uD$MyNIa?AC=c;O&EUs zwAi<9JQ0{+WRBNGabDjMu1CYu!iQN_!}mn8fBoe33IC`j{ri~ zhR+Cm9bY}WVq@|k10ot|#Y83QlNfN6L*PVoB7O_f@61Xk4)s~PV! zL107P;)KOvO&m)j-FvFZ-CRcV}=D4>hNHme*Y8VX3W7}Jj68p83 zBEKwpNPkkH)-llB@w@8ykVr(Au#tVvOLklb=ppLk=bN{UCAL1Q9Hbi^1Uyo}n$PDk z&fQ)3fQ1$^4%hD-KlFK#P#4b#24K~;97I36DhgN7HZt9 zwhGVL&q+xDX4-`VP$MCgn1pGQx8+S{iau6b=|qT2zjgRhc1FY}y-&Sr%!II8!pU$haCST)13T{*0<*Q}Op zc*||wgd6+DBg#1P!GP{&h$jBHEehf=Q|E)dGN+@UlBIt~^jFF(uW+i6 z+50E6hGa1va(O07XMP~ObLMAO`nREORchC_;SHa!7#J1*`nV%_aNfO>=0j1LYp)~Z z6h^`qn8_ zdS9TAmN2?RV7VH(x3@|JaJ&*kK-6{=`g#P6qL&|Q`1F_1y2N`F^}v_E#N}4e;&tWa znu_c%#I5Z)l`L-cQBtyF_4FAMbO+dv%U5zRQWB~0rs$TT<>v`ht;wrAZ0iIKAJ1jm2a`qZKt>qXG{dP3xEx7Q+ z{;(?No40QsBpb3>WPRA`e+<|ZwOq)O&17a`=>a{Dr71!a);zNPK&@{$1Y8H@Tm3Xy zpMK<6ji!dqAB8lNhaA0FgCBU5H;)X=rDX=6JtyeGh>;$-agf82LxYi6>8n|#Y;DEL zC1tOR6mHdTWj@z-RJ^xFV=SUM^SvM^tjgt^s%>Fg$zjAk%24#M-L+`o?j6pY59W|9 zciXF6>H~Gdh*{P{nT+M>e9B&AT7lcUuxENjCVmu%*FjJCG9%ySUKZ7|NYKq}M_jJ{ z_?evd0NjIo{P<)uQX7J=Lc+mJ3|U`ti?^eI=Tf^I+UiWjJ=Is2o2Czwb+|cV(g!YN zR-D$?`+3{0)*u=>2P4oh)+CITaFk+v;+F3#tMACk2d8|98a}5mH_88mPLd&PMnvH5 zu}U>2K_PRgS-x}y!DjT+Av&X9jicGlU2rQEpIdIgSIsQntMz8T?weWX5(R+;r&U0k z1Bq5M9IXv1J6&-Yd0Ou(b0Qtx`(%U9NpA|oS{@co8;hO~Dmj-hwbbO2ftC55z(1h^ zqx%${^iGey=l$p>QPNYv0~P!Ob03L7pnH1~MhdMxvj**2VvIL?{qmY3eFDXUpo$QY z{w9MQ@0!A)Ebz^n2wggk9m41Bm5D|EyI3D8E%o98N zYdl@@`TPI2b1PD4_c(3iQBO!8te8D#m?D2I(fLsax?7(q}X$X5Rnt@>AeD1P&ysoUQ`KJ-# zn^bcbRyH(eY;*9bv~`=W7~Dj%J%6M1ZgE34wtK zbebnn$Lk`SMsaZ-Vaqbu{07r7;ZNT>a{!h1H#|pZ5xvZ`|<1 zjXo0ZYyR@`^s2 zRi>1b@1M=QyaI4%uMTu{7@76YQ;i#4ui#2OJX!&#xN5Hk{#tDZ)34<4VPzulnlst+ z(Y&Fe{{!!{n*M71!;aba*biPEZi~sWL;l&euggRD@6IEQ@OSr-4Bs8#`n5+EX<~QlAAxprEeMtkJuxo!-^R5A8dur!v*cEBGrfJpVKipO1FMUZnMjcQ zHt$rZtdZG#hStxM5ZyiQ5>L$VmnI1(1Xb%`EoCB0*_26gT zrthd*w2yJ!243(AKs;WAm!M4H$I3Z$pcyY#g1v{@eu=`d)e|ny^dJt_pCZ0VTlY!5 z?}F?7vYUY0T28!(AhXPfV7bV4Kr(rna<8T_Wd(7}(NOYh?>Z6Ce-S*bpCTp)ZtQ?T zmZjyGj$q*u%E~PLYP8Z}LPm$pUeM&busHrVI#-uG>b>4P>W_yrpE_WK6+@}DHF3K5 zGy-k3tsQFjX2a+6YpWIaS}OAVrJ(l<4lU(gv?$yaMMxcg;T~ZjRaY5ISXkmL#g`)e zRv!DQ!O&*DoXox)mUSPVFGemDC8|?~KRcFjlUuPP}H3d<-9fvJ(qCAM3@1!c9 z`%eV@+}_$+g~*!eR6UTGZ<-VJX@$}fftBqgaEoEn9JPx_gNhquKg!wWMBhBDy}q*B zRmV;ExX2rH5yAGYF7A0Z`Mrgd)?xDJFD2>sq`w*`YN1E`aU-awkE#-u5KR?@k3QG; zuW*TDFT6@>aNLZfBMafXHuJ_E;)?oN&p>`H30*{>AxGD*`1C?qa2hCNcZLjJqI)LC zC*W-A?oqCOV&##igxL<-wBMOYJRv4wdWi|)59gOiG2E7UJX9u^+eR4L3)JrZ`amVtC{!7_iIbDYNa&2Y=1u=H<*Fg}y9U>GX*yR*SJq;KaCaVQg3(J zlRyLJJmmi2tSbeh&<4#Kq|#REhxub{{rb*NLLmMV*4oiD?d>lUB0zSGuh5ntj3o%7 z3(C=otMjWt9N))fJc5FomSqP;z@zLK3s@ODu7}W%XcX+pN%XNnEhXZlDAR<|V(Smy z__3@kvb2_)w;T6=;5yim`4#otUq;EOwImtQc(g5uy@ZK6Y(aB&!Bbr1+v=MN){Gh^ z!ZBpLmyVijo-;dKSRPcqv0lWjm7;&hh>?LFH6pS2rwFulf5I5z=bZorEH~~HIJK02 z;l=Rv+G2!h0n%xl5*(`@9eXYKM#p!`b$7i9_M|p=dETS&J_tUphB0c@j67fdB4) za(D$NgVjNFUXqPhD4*MOp=y6S5wx|v)_4aKbAOQ3piEU%l2YdSL%LtK5^E(3&4)V} zub7}p-1&)B)cWw2x1C7)SP#9h)V#gknvj5!2~6nif!Y?*3*>{*EZc=Qf33*>X2-X~ z?kfCKM+m2CK9%Ar_-O*JKdZb>B6H@uLQXtyC73L`G%g3ZeEm8;;?$ToZXfvdw3G0~ z)0aXSZq6oCtt46Mj-i@L!)l^EjHVQ*0y2iq#Z(Bna^|UccWe*7_}*uB(z8hKZT?Whl*swOUH6Y`e%BbT*6Muhis{1?Nna$)6NaFThRFy& zBt_O0W0}h6{LA`0Pw(tL5DM#mz^$%8ThfAA+iBvhYl~j$6kx~XtT-EXx~NQ)O@#;9 zh_Ls*D^KOuB@2CCN%y=$@g*6Pj>^xOt!^v}?i~I>L>s*4m+988!^Y}{+Y8Rl!sQNj zN)&eNR<}PI33$HYeR;Y{k^ZcBBa4l9&h`}AHC_Z?h`UlfKm6r;;l79dE1_+;28KQN z&H>$RYEQB#3m7x#14RjJ*NQHp5q*yg__WrZX1IQMqwVDA)8cAo?Q^ValzaCRVRgIhDYNi;XhhPWG@b>wF|_|9YtCxzfFHH!5UDxzbTz}@Xvh^>4$ z{-s9*%I3EB8=A~DsN zb>EDSeN{KGi;;kK2f^21+hR4iP;_MQ3!IAV{G*-^+V%%Qx+Oz=L?BAhr1RMR%5<9# zfd+|gge%~*P?Q&nuGY)cd~apMHAZ+g+*m#|h~YMK9xI-lpSocDEi4D*(kfoMq!-GH zGe*}r8NA8qzRhc5|71vMX=!8l_L)K1+o|+RM~`QAz2cj(JUC8760H8{rsk=t?k_)V zpG9byd(bJ!cerN}CM|B|k_N?Ix zXsV<6-Aav!=U2K9*VxWqs_*MjD-ap@cqS)yBLDL%&H0kapm-H*Bw?bdr2eSu?fxT% z4FMIjljZ!kTUmPx1_pC*-q8yTA_up#scgC{@lVXQ_OJ>VSRbr`ZO>$F=mk`8E-WaW zaEM{OEBTnr`u%ZU(ERmR?$4HPmr!fUfEm=n!eK@m`d)9FyNoHrK(BW7sk^R5vfXOA zZobdwoH4G9>Lg#>VY?UHrtYlIevu^t9%g)tg!i_HhUQ((f!g@}iTAjXa&b$?kzG&Y zR7hps3vnZ1qzBTKaXxust*)CZo_|&5c6`7xMmTxv?D4J~6Itq(Pk}m#*pHjX*^sKb zSIcdfh@i)YE57pkJ!7j)945YEHwQW17u#icht&HFW#3;#7U#@UuG~w9x-(|SM)iy& zYm6gr?uy4W43^*ZVcK{Qj(y2>&-9)AZu`TH*jMZcLENfMmHQoW!h#ZfB}4?St8P}gvzjLbNp$^YJlVn z*32mC!}RXxyO_>A3TQ;f+9^l+-dIGx*|9K;39;oeh1}0`ct0I`FAOEp0c|Y7u_>%R zJTVW3h|h*Sp?>dkt~P0Cmz+uS6g_pV>0R)A$o`8QzJzhmq7bREnh8RfBt*?7Phh=V z%O@A#0_F?e5#Cmxrr!+G^f3A#)?q{$ahV8K#E%KkxsKgq-|xq<`w&cPOu-4B2l8uN zTjM>o?XKan(N5@rvcw0V;fJB9qwIuJ{)V!t>Pv-UA! z^hyr`V`~V;Ra0o6{#}arV3b03`It7;EGA^tyFT4IU|TmrD&hTy)iWEi^+4-&x2TX2 zzNz!7ap{($gjPS9Na`Y%obKm8iwmt;#60T>3iEMzY0Pvw3XnL+U2E@Ofeu19Q^p}q5V4mowLU<-N1 zX^8-QLj@BC#z?hjit8&QIRav>Xoe?2PdX)1YneS;9tasAnjH8`A0}QVQ9rER1DB}e zOVKk1W4i}YnCq4An@*dY)%7|pD}25O@-?bA-ViIFFbr;DLD$+$I_9+-DN^JgJsqrS znCO|1kqy&l=y+!J`L6ApsAy-Bh)xZ!bqY|-9w}cCAj{yTH*Bq9FVHiUh=4MTF(}ZKPBPLMumRv^W*2w4}%|3a7$m+@f76~U{ooE zm4LfBmqw12@tlM%jeCR~m0uA1MsG`>wdQSZP&bR1e02GmZF+*l=A!g?plsu})dF>S zj^+2s>K+;NuV@a=M{CLSLRZF+oQTax(13R{>IiDE_B+;b2J%)3id6=q#)xq~5LY-Wm+CJ-37jhxac1;%|UrSAP(JtZ^dnpZy@ONmdmxIawMK>6cY?)HQ8K=To}p?VLXMwJvf! znlcN0W+a&9R95;rX9EX17ZXGvD-lq1$%gK*Rv~_ZXVO1M7AOff+62J}OyedI5VPHZ zXKM>bv#u0p5>8=~>g+dpV!y;R=gaEOB4|iMw>O_rYo#Pu#D{}+R4R8cgA)RiRzjBQ ztLnw(T#lNE;EnK{u8ecCF?nYt))iAa*M?jJtae9lR$kelwi#P}qe@@&6;-j_y4VHF z3wpt6S525R(W5!jBXc(odC(VHhDWyPu~$sRU#%|OJ}M~mT07pRTL`_!nQ2Tfyc`q4 zSfELo;L5Ho4ze5;e;NhvQ%KW3i$SanvA2&$DA0VO;1^BTjq`hLSM(_F)Vkd@23?`g z(-m{YSw5gv&f#CE@J6&w;3P^v>&%fFH4JT7>UMTImPs$+H0uvLK|5Cf?Y#3cXc~jo z0;QFN2_(u?kjpHlv>7LcpgXYd{4D(T<^B9bQERd1JBKJP{<{8O9ZzicYnBn)2Uuvl zPwI5nHRt$Kt5O{{6OBH0y0_6yy!p4h23HK$!|Xyu;W|Ds0?zH<(%c8`qEiHX4*piSVKw$8GIxiI!Moo3>{8MP(dC?jXrWVIk#0E) ztH#pa9P-NBWc+g|Z;pu3;rJ&ep*-6M=I zO21Bje@&WO0q*`_T-mm+a3#FZo_pOU zUMasZ^n5MbRdUM)n~I&0MC1wzjBv-q1uk7iC* zt_k8bGCxB`I@@*zXLR8Fs)AQo=iI4TeKxVg>Au@EfM(wGIi_Ad8r*T5S2tB)bK&tPT1 z&}9psjo1X$vF>O^1irLXOO{#B{eBWtZ)jE#ofDVFS8`?Hg zMN&CSi6}Y5QDcJgIa?^z**5m5>Xo5YfLV<6!>AnN|y%RQ2kw;#gf@-(r^U{ zo^S?A!!*sePrS6VUzJ$ER6w0sB`5{79QDR2T<5qa$B?p;;VWM&WP8e|z18frX5!WO zH}<5EiLjE@x+hRCMvMew*JeC<8Rlp$;$gAyOhjum{EAtOi_AI6ujc4-*-I~ETaEVA zUg;#5SMnC#v9xs$S{(TKLXAL!eeq(HFpKem%e{3g5wVK?Y>QlKb~VB&ZNnaeCs8xo zbtv1zj5j=jH-p#5;!Tp5<}_~GZQPOkGz^hZyafJ1Yh^T0pmVkoQ*_9Rzk{=Q(*6LN zD7*i2=tgnplZIlYY%b+s2=nGj(hhwBL&7Aoej^Rx$(YH(u_!RLBl_|UZMCmMVTts* zW+k?EFw>Nlq`({k{x#f}XANbC6!V`5l``PC`a_RZN77-3vfr0}8H<@+*4#Bi}^&UP$qnVIrY%lkI6QL>i z8orfMLzwdE6}gBO6%YWoJ78Pqa6ckTK4Re|nL*(T%>!qDJjB}0E}=HfI1yt;ufVa0 zN8)1J#qjqCii9y3)kvCIha5+c2iA5AF|Td&K)!taSRg95L{?BC>VX8TEyw@Y5P?^L zKm(fVdFv$ts~Qw%8h-I;blC1YbY-*{3<1h=JwP{djKCZS{zo_9S4DwHjAR|2+7^Mi z^oar85|AP>X3v{H{oZOhxia}2 zPhpWvHg9o?i`<&oyv#DZ`%x^6Hu^{3ymwlS&aK^`2z6s?Pu|rCvPtmCKipKlrM4Eiq$=t>JMdBIp{G@snwRnHIy3-`{cMVR@Ke#-h?$PY;CWOkZ!sNqXn}X+9++14e2IB zv}uRNL1E1-eYzVo%iCHrlx^|33OE+MmAh{!5Vv9e>i}@ zC!F;Jo>q8(JO&4-b2yTg6>#A#!!kMA_@~LszP#kv1){ zGI|TVjiIm!&k*;gYzXPO>jNswC5^VYBC}pWET@VYUOe{jvj{=g26oT|#go~lB}~D7 z20_^nTTWBZ{YO98-v$z5ke*9(w6^$XBk2ZrSB*45Cr5>xjXIv*hCVu6f^yA&Ylhzr zn>l+}_X-T_|Lddxo$qN}H0A(hgt>!b8cVIi*B?TgH_QsLLs;meFGS#(i1)7I<+hN1 zJ(w-1>1e`Y$-a0Uf%OQ1v;H&Fejxs{(f>Bn6fkhXg^4=csSI!xz%PNHN*dzSa<;)O zlJoB1&O0;;+)K5>cJ7o&5T;6Af2_vJVy>SbRWW1ITcwzh^LlH4D(NxvttIbq?uD~I z3JDKRPM-!vP=@%#{Uy9uHnK1$r&taD6zN9~yExr060M5e=Fwz?Q_k*ZRB?J{z1tyE zy_|3tFNc##b%w6wTvs024+t1&a3qWNBp0>GUR1gId!C)?{Bh-qS4ekQ+ndZ`xUA4} z%j=ErXJAP%)F${b8FK&ZrrE=>Z-p8S`+wQ>)Ng3jp2DXhJxjO~hR$HdG@YN%`fv2~Up|e$IgDSLC5*Kq3e%3> ztz`O?4krYG>-1%n3We`~sz@vM461hAgV@S&wZRRZJ`*-iwZOdVm<{Y}cg3x+h z-{`1bm1Vh)kXX4g9<(J^gLYL(1iW{DNxN`?4EKV841e8+JutaUcx`TssS|?a69E=o z(2yFBH?U%aVOsD!#9%6(MpI`|pm{=hFoHEwPJ7a+0@d~E*xT;an~RR;vQ^h8&ywDN znSb>;I9vLoP$8|M$T_aN8RZ`v^piSh=S78mX@SyM?&w|DYq~ES_ROxAFx*>_=pq92 z@hT>J&lGM3t~GEPD2NW%W8SrmG;0^-Oh1n|!PKF4m%#&dF|=(@R^)=-ME*GkZ~T`J z6p?x?>+A6d8YwMw`iJ__H1LpEgRlTYnYYTZ(3`9ahUb+ctLg6DC`VF4bgElwCMyir2iPGj%lTg*FIvQ@+Wy8in-=;7{p#5ksl?Emx- zeL3Lm8ktjza38@)+$~8I=wGeu4^Xd*H|4f}t{f>N%M-_7V~@U@7cyn#C8CKNNiLLp zQgf&%? zN7VOki=fXPa$@qUar~IpN~htKJ8u`_+#ZcP7=4!CO6zJddgJ~$<+f`wZ$6&a_C2^4 z$GKIqEI1DbmFPKb9gbwRGw3#*TGjEoalrguea}_m8EbByRq|zhOYBVrk0nj!wG!%h zBUIbXOdMl%_jBL*Jb3ad#rxdRYD#ivGrCNO6d4m$Z$t#HH`)hGsv1UctTU@l1cAvH zg|sr&nuGhtMwl#w`>24=O z&tx)`$5rbEREhW<9fh7hRj|H7)8~0>=QFD(xd>U<{4?{mMwYud-@V^8ujSl|$f3fq zW0F>pj850`t;gb_&lcy+8e#9>G&SVel?0^P%ztO#VhTy7skzOfN`$0s1^6cI2?*W8&!#;Nyn zeMMT!pojp0nuG|PDoKYP>{cK)qpY#ltu6d7MM59KF;X+QN8yM=stLrtl+3TQbsM|{ zng}8w;Vg(xR$ju>TqI=sjd)7#j66aHDDYy+SCCt~Yj9qSH0E2&V!j_=K{KiP`p?gm zS=4jVErNG%t%LhH%>?Q}^awb|p^)fSTTFHBqtM+;lWxhMhuhlP2j=;EL*RU_dmoL& z!4HVOgGuKl40!b7at873k~R7W$09>8uFOGfDZs%bbYE;ItIcf20hfudKrUAZC}Tv> zH9bh$a0I2_udLeS!_8*aPZwVsc{iuK8|eU@`9LfUKCEs-WNq?{6Qc97Yx5iZJgIoc zmAjs<-1ZmeNU1cL3>~B@j;NI;kt;h(EYMBeaRf?y6-;WIdvbm;g_q!{i;*GF6&6$_ zo+J>75`YrK+odr}SoeevC6ISvPcabNiS2`|ivc%sELAy1-xpe4y6O`8zu0@zcqqfT zZCH_AvSo`YYxW{ZWK!8eQ)Ej_5n@7=DAQb}BI|@uiYZHy82ip-4@vgjF!pRSVhpqN zoZa{PyzhPA&->~9{C@NEn``EpYtHk(9LMoLPA<*UZxS^{h0hwCKhZbBL#(3lP%LLI zcL8-hih?XGgM?RQ< zxJQkC+JzbSPE~%Yn}52`vG>I4>5W#_(S?fiqi!K{QFi=sF`SZ4=7;0B@jtN#pax@t zmmn|NXV6Fzl`e)@5m53kmca~i7s%@!ryN5CVWHQtNy0}0n}tREc+l;eV)^|$5KDHI zBF}m*qYQNBen&xas6m?p=!(k%K(ud{ss0T^NFL0Q#bY6v-@l)=V;2z!jX&7_UI$ZF9CpYDgPhHC^t{+pi8FxkT!y9=2lxDws>X+7Tg_#Z6tXDvLeHu zsqDjgp0@)F@1#Bj?7-8LugU?U!H1%oR0-q9>L*H*cm7m77~gI^wS+9d70pGgj`&hO zjUuq!1u&k+QHvJTosS`g%*>AOrpo$D*DfiT;P^t|B(3L0!XV3=)>=~d+EQ{5ze=%U zw6EiriY$&J4OV}(?!EAJwxk-*9y2?23tNK*p3Lng zeMSEAmk$rv?(V}}1P{p0NZn^rpoawuac`8G`-v^+484~tsqW`GNjmx==3LA*C5Owa zYQ76;Ilb8zcaM|};0km%QdaXRZdw3RDv5{=TKu$8reQz6B!$^8F@em*27(kV8afAC za|ohq6KR?DOk6!|*+{4Uoy}m7hE(qwi8QsAd_FMDPBS#^D<{IJc4!8`XAv{H+pYew z)n?!DdrEvxVkFY;Fy8FP4CGH7i+y!3%&ntQr{~ABIxF|jvzta}W-P&*z=!FqSEdL? z2jTgwNoUU6mqvArOjlY$axk4A?`6L4{E>89it$}fWpaY;Qb)68MV76z_(6j)qf1R` zBo5b1!NLF%2Q}6!kWWUC1dxc5SS|mxl_TlS?=tkP=4NeLEE5YwHVVW)6wq0cNU9Hj z4_u}R46!kYB4+d2dDuc{V(K-5|K1+#@$>F|^12&@)t0|(N;t4n8#;jya0dTnz)n6 zmoJ4z8SD#zuL7i6X@p|QYDsejG@k-K1kc6%4A8hSj0!Ydk_Rm*B&h|I>`XBep<5bA zYMzmbltbiU@-{>6FWXJxf^#V=cnr;OM}3;8H}1Z^Fj@~vo``cHHLNHTEQ@arJevWE z)(U}=LXD!CqAz0h+%7@EMhWh$o{iG>Aeh$oKeaJCH+DjSvjuY{)L{_$e;_2N42Sw~ zpEl5M?|)7SW{O6`7~3jXAaD03 z%>c-Lu?;XPJIIj9r&)eR2Fp+xaaA&R=^DNnzjs;0GV{E`!A{hNiL+md3%!(u5-bh} zovB>ME$K{O&~apFl09*u(#{hm8B2>4b6#^Wyclki!y#rUG~C}ma{cc3rYgl>3e$sR z#~fPjr8&2`p6okeeT~B`6qVa}G4JsBbL=^Nu~l6LAUhjON{OquG3{-$KZdvp<1Ax> zboow^+*+$?!221wPeL`Sm)cbRDIOb3$!2Y1h-jz;ic!aZ7+8asCEXu6U;i$*Nv`(2 z`(AyC$OE$4E_cODFhl6m%qqys|6qvtIiN0P_+~GjE@NidxH?`(6kDRwDwDMZPdRXT zuT`E|o{fa25@7uK-*gz~`)9a5Er$l+e)Pl0OZXSCO`9M=`X67Gu5U|zyMvJ=c<5n3 znPb+B9W^$*GG*;EcD>j1?MREcVl9P*0ONrW-C>qzG`Tzo|k7W_yQ^^ zi)`&iO{p}qe`rq5|AXdWY4?X3vAZg#Bbfi9suWDkzETZ`!wDNHEkWh5RmA?XnXX4L zZko2!;GlGLI|s@m>sm6JfA--YbXT0&J0+v3@63r`a35$yx^Y()H(ke0l6~g>$@{Vi z%9kVfu)^iWk3yo4RG+AkK(?7mAo3{&96}0}DZBqtKiG`>CdKGw76QI4O`3X}mg_~P zpBpI9eHWUhDsZ+dFj6`q)hScu`3&2I{g0QHG!FP+e;4ZuI}SSjoV_a{G%jnLGx#9> zFWccft=(xDD@@I_@WlTi+x_^9~mt@sZaTkvKBS`3Ctv#oqaR^caNO3wow-ezAFHBaOwTW@0y`aY0!Y) zUUo}(DhczX!2X4bnykhR76b@m0wXyN zTTJ`EUUSFBmi^FJNMd(Pp_2`7irh9ngWA7sq>-8poN zjYoG8BUYKP+(f-+9ykWl3X%URCXAp4;Nw-FKX$a`JjZeUV~fdy$-gv1?2^UOUdm=? z>FdtU)7s8OzYiYX&|!2qUf0PLn0$#Tq1&VH4HQhycRQB-ns&R`;OoWTV0PnLP<-T; z{#Pu`8^RJd^#KWS8ia|Mq3|n6F7@sM6FbC7jDz|gi3w&NePj(H_rYM$;yu7D1f>8g zflQVZa%P*kAKJN8{4e}ZzqVoZHbdQQ+eMHsNC*on_0&)t<3wBA`Yxa(Hqt;`YP}6Y zQ3{Rum+d@`W&4#t>k9D@g}mM13p4cL|8W)Z|I=0f|7ZQzC;wlbA*zxby!w=4cD63I z_EtGdsn0^zZ#ZFe$)V;*jOwBNEf1IC2kWn9z6w-bny-9W^M_E|n+(M~uSzoe6AZu4 zP$YHnwaLm9Q4ry4`v!5Jm1yI4%}#mT9C;n~)j-Ha1}#nJU)HS#&Zrs4M@6 zTDs^&*3)mBuoHh)_2LZGjNS>J%nbt73WVUNjI+#-7#&Bn4n;8hM3-jA+Tia8=>s|1&-^9t za&D`IJ)$7<1{ic!TL(b2_DNGtALSLlOgY7NzwNDVFkB2o!gN9zJf>bHq^*SoqsXRk zrWBw~$8Kc>`UD45JCBbW7OBa7vop76dtxpVnfKbR-Fo;BeryoKCX{JKN= z%jV)q#4;k0zp^2@bP<{cg`MQzx=mC0IO+V`NRjS47hjX`Q0iBjq4}+ppGT3MV?syA zv`tTOkm><%5^5b&bI?au51UIe}9CQS7S} z=R0X1@xbrstb@O>CruyBVqd>cM+_3U;V9hLzj1)lGs|d6nzVN?SGX?Ei;`ONzij@4 zZ{r4H-LBLfxz}sxkI|bFPI_+q*4g^Hukp==Q6{$*@mP2bltT@|^o9;^5_V={M})T?B(g7Hg6 z1%9J)Zunfr`r{lHZyJ**4iN!_^aAD$7GDk&+(O)5g*Os1q*Nd0#9y!X+8{&Tf@3d3 z7Fc3A48`3Y3SzXriTVEIJ8bEKtn06c_gkBXHu0Wx1VhZA?>{g8JO;w<6)ZCj%jkkk zwgQu?g_F9^(9G-AkS|>Mk+Kg7MKoM)uL3Zw%cps3S#28?x0(@cp z*I%|3^k25|r}*F32v`DaIh7EO(F@6JmfAO-ER_^q_u1M%Kxj~C$}TP zV37M4E0m?k8iLBiSM6qIreWdQzaP2c9?NIPMSd)4Q#-dm_mD=`Hn>fqgd;vRVt z1nWpT!d-r zp`c$Z!%gIVv~3lQAo${hSvB&dbzWJPuMDXzi1}IRXtT@C(+lZ-(8GCm!?kr}2>>tr z8{uRA4M@pLWfbbdHPBIxR9x{T43YXj(k)M5kZZzg?lpS~$3nIw@jvkq(8ep2ApNKa zwA8Y^8}=nvGb{|w4^8258@gyfBH=YG|FW4HBQueOct)yM6LyyZDwm5M_@79c+5#gM zDF7{Z+5lGbl|#aBLWRcA6Ual51I*eNQ`XJ-)KXw8%m^sW#fZ;ZK_5!B+uN z?^P!6NtKbps9poG3f$^ET9FF2IgUeDPM4S}gLEv%+zHh!E5S1=Ru!JGme+FsXuB^F zZVj38|`yojd%I>v(h1{PepR_8F*9c}0!0 zWPb`}dqy{7-07$A`i0)?rmqe4x7Rhz4(Cp1#4y(+E5E@j`m=g_1k`?aM?k*B(GkcK z!8tq{;>V4z^&r8I-;sYeA3k}sP@yWxc;6FsF>(2Ow>Is$D2Yu{;;U_Rm+LOH#0zyp zXcNz3ZHs$~f#Hs<~P27@3@c^6BEV!#{2-e;lUy;O^LpP_M)&~9`s#5K^HBI zGT?jT_T)l>g(j)^yvn52b78^5N87}D7l|SSPsGT2V>P_7pE4PlITrG0(7-W2r(dJ% zXTOA&>XC`MG4+MpBQHyAt*3#u0%z5laH zPH#kEaHVy`Bl%_fTuqmwuVm0fe^%ufJ^4A#!7_yBK^f(NVv!B3$3gv+iX?#b~Fbosk>% z%`3Kc1^%H{BH5gt+cyJ?SxG7qt>%tT8OW@DuEJoiSg`(G&|K=Z-X#Wr8WXxB&6awt zl)Ore+_apy6%uFp`lVATYVO??NjH}J{`woOX9Umy$`QQxk0L*3RPmP$(jd^PyG)9A z;&+GawASVNR{XRUNNTqy9saTf?Q!4bWdQChq6&Q!;Eyn8K&dAa87`RUWvLO$P=ngK z&|*nbX|B6EgzX34fH1R*b}?bd`#_)f=7P8>X%artx9-Y;Kb*nma!XKLzkhI#CvD49bCjKL7hho~TpTTbNeu>Yuy zO4ghHvb~h@YR2&iT$CF#}-|Zw@5eoeAi*_{7i0penXWnUZkZP^2pK^rp*;68*=e4AM#VZTy;X{W)6;Be`gJ!vxTNK$%8 zn1vFTwoYM85N}wD12Y-&Uys-3+KyP5zKWX*$1JjMrsn<}mAmR+MRTCb z{KF;~?*61Y_;kwZK8&u5j%|`geo$^$TY#4u`g4WV2Oc@kZ`yi4pRaj(&_@kkhEKt7f zFZGW?mJ-9z8YF|D>fYpsc)yzgRSM1$3Y8av;16Xl*xd+Gy%eanTgbNO>FAfh^5&N7 zEQd`(G@%T;f~D-apN6y@DD^$}J+CwCcS2oP?2iju8zZC2rVUrrt>b?VaS}el1kqNB z)Ntlot=b@RSyqGtMfTZanXD_;vt6h!4+X_vD=MBeyhkn;&di+MD}Z(aYv~R-F6ALP zqR~kIJxi1F)p1(4 z5(Y=l!Ph^z(|QxDD+lA+W`K7gR}>5uIpvrN}RQXT5`VeS}mO%dr!6<{4fJ2{19DyaK4NvtCzk0~AJ2VY%>>d-OMv>>Akq}(8 z(B{*`NJ9jPT2$IMUsYAvpHq6(8%-=WQ8P#HM9ZnzDND-F^O~*URsnpe6i*Rn)oBnm z3wRmdz};EWluZZg)k11otDp0oSqR@8b}P}`vyr|t;%d@uIwGxPMy}bvb&Td@GrkDj z!j5cHo5EL{h4Xy?OxhBX+o6`AI7JIUw<}OpUSL;{A%z| zD}2d0(-z_WQkJ~!4?_<-x%UZQd3{A>FW#U)f@{a_VnTl9p50*eq6U#}HDB}o5|QCO z38;?^;tEz|Q{`*P zxF*pcfTfSJ==q@=Ml6qtQACg9nX$&hvG1Tzk8 z>6w1CUpGzkl5rgI3XX1RRdcoB);U*4iK!=(&OD;BU+$uI{R<0=dcXQ^c4Bg?%dY|$L~$^VY`YN{;KJj zf=`C!0}t9ZNr_#aJq3-Nr>2wFSK;KHS~IV`OR3b?%l==M8n$<|3p*?%Pbo>BRpgQ6 z(0-q!zyJWzK2YYzJ5S@8nBT}bNJT-ONrnSFF^L{H2!s=ZH)7vPvU=VpD`0$OF6C*e0kWQyl~D!gnR1{?tBhNpB@ls zwAxPJan(i1JTQw#4?ABOOrG3&b}wG0tK&z+NMqtP7!NQpAdWgvT`5y!|CWmWt$fwi zE3zLmQR-a}g^l*pAI|@XI&1LC@A#=R`uySeDR&gs5ui0MC*{M}hs){;q^d>$!p_E?&_pOy7 ztqan&8><~+4b|>fRGXIh%M6ruGOm2r);=9BBZ*Ko=6Z$N8D@UMz!}%bW~)J@?{^r! z__4*)Ml~YLurb@JIKh&ZxsnU7A~|jx<+xjKS&Bu!D5x>(}v~*SCN?j&KQ+F@k{cDyA6fIz9qu$Pi<+p(cA2Op1&*>ADLDG z1x`Et_HPH`0fzo3l(?)+s)Ms73iZ+b(|M_)y{8P9Zd$xOUN$TJrf?_jF4rQgbv@ST zBr}=d2HO|nY#T@FCMgjwTI z2UHQG*~xFSo!bCv?e1g3uPP+EXP8Z`6}k-{nP~v!s(-|$mO#i)v#4&9f&49 zkuC}q8n5g;&5pbYJjOlCn;x)%>oj{&3(vo8beimrIvEJowDlhDbpJHjrlZ2;3 zs8vOaLovsu!XQAbPJO}2Y!Ufr{w6f(X(qLY_DSS%?zl=rsN=0Mnd+JMhEdlpy;sE_ zdE?MQtSC$SltxE|1S1MxXyp6mmr7(r>GH<6Ciwmnn*&4YZ(_TG6#tpvl?KvZw$W!7 zm^hXq+7NLdB*t3y&1iZqiYIJ%yk%jm?cheQxni)%;xnyR&e_hP+74II>~u-YFkue_ z0M&@?RM))eG}IM}ebrshet(V3vf~mY^>T8n>;1$oIZM z0+_?fFSK-{wIUzrAjdBF$(JXOo=TW#Y~T2v{jw5vf6CV|m4;yPz94r_>otN>WNToobsKVT7rouVMB=CcXDEDFhbd?k7nJ(BL_%So;w_eESJ3}@V8SVw>GI)v84qLUaXWzI`- zr+lsZ&Ng0xSq6jz-Js91ZRjM2HdYu(`Evh;`O>XpvvFxuT*;0zrc=G9d+-jHtMEb7~#L9#=|u^qq=|UD@pE z<>in=I_f3YhDR>GY~Xg2e7$!MS6}L`AWQZ!-2%ktV|_qCE%le}FI${LD#P?;2zXFw zKDZDQ(!>EAJunW82dP>S_cKS&vCF~qoaOuv2C?WxSc zc8pX1XDH?c5U@^t1!&}JP_#S)NYHmXve)WUx_uM<>hk2F!3=U!$gSHa`^Mgkl3gFx z-^*M39xhYadi+`H=llmE7_a_GX^=RQFg>SX5%=?jJEzNDw3Ope`)X~ZLrE@jXQP_G zzM18xw4j6OS1~>HXP6lb%Zz1NIC+Z@;kOxTRuLs3pL&MUe#Cc0+4GDs`=J2jLz**- zw+}Vl&A%io>;IzL!27`i>La+ll03@rQ!WxiO{$+SxJRA`o z9^s{kPaX;*ceq;9E4Xit#l30R?GY+Os$@HZ_i2ViA+*{}?_8w7xzWCkI$??OOEBeaKVW?s zha%Q;0bK_?0$P#r28q$WS}o7Oalwmdn=C~_nASSOY z=s4=N;5%~lEI>`8x@Ca>vJ-`?ZBl`}nn4UP)Nyw5cDp=e;+uXG1~gA;Bj*aZ4-I|A z3}4$H;ZHw?zL-wDR33Gqr+~M(G3p!T`{7#-tuV4Vc1M{A=51B}-G_jEtZU;S)yR7^I!Ia_$x`4GgL#Xn#KUVc})gyDkYsQP&ZCE@3MFFPjkB#63Gi zdD=Hi+c@)gzERFS_=U>k;g^}u4?NspG_DCs-tNAFRAg9{)-ogEex;Oyi6ZAentfKv z^B3mlIq}%Mc>G+Hfhm8F(xVeM{zNfdnDzBSBa8rM>N?tZdcb+rvWdHznx=r%OY@6- z+r!;1XU-GEzOJ2Z`{Bta07iK&hHDN&7 zekw;dO8e>p`Fl^HizF*ZtY7gAb;mVuT;SWQ_dojmIup;7xN<$>3EKA~+JbB!IYZ>{ zT=0IhmE~~vIK9E+@`J*a#lWuy$K=a=mmmF1neLLMH%m=)I4zCd5*{D#I?@Odpvwd{ zR^ zW8-EWYeh(*MMtY|0JBK?7ZcF9yciI=)jj?#;E9A@6yr+W5agZaJfpR$mCK{CS;w<- zuvdq%cWq70@|56}jWPL>RrvC;;hnsZdSO=ADy4}kL=lWc>w0=opfCpFv#Zlr)LTDa z>!ir4a<;vWNiLKpJenWPh`N0qC>^!sdp)%tbRjUN4W?Io5W>pMXKcLD2KySn8(QA2 z3bQ_W&UI4yXt-$gM^$$#1>ee&Kt8%)3znvMIwZQqBA8s!Z0{;fX4a*Z)jk+H)@it^ zaP**4cDysNVz%l;Kl~e!@3U5ath!?0zGdgKZ+Wp<$d%g2*XItH zHmbP!zuxnn5~q6Tt-V=;3Q(QIQTp);0SuGgw2ULy`J5tcOz)oc+pj-gLktJ8`Xb*d zduz349)+t#D)=SZfX`g(j(b?htyF!vTaD%tqBk6m-TV$Lpg~12^%^>`UW$v7)u%ZZ zbRK?pz0Aef-p!I(+-Q@i(rqx|saVp!v4j5wryWJa&6AZ^@TA;3IVZPA$3~Zu*S^}7 z1ZbCj5c-LJoe(~;qL5U8@m)re5yVlxDdOs$>9;IypKXl{A`x3061HEdmo%3}zwgd_ zu2tgx#=Ja1V@G2W^y5>SQa`Jo-S@I2QDG+TaUKazQ(W!F2I0e`x}(s( zcps@BdetRkOG}UMX>>ICql7t+?R*^*%X+}U&;JRvijBi(v)fYQ9Tqt zGPW>H@G@i=!DGU?Wr(HI8zf)@KhWTHNFL$%=3}|HrTCv)*k`rwlKAV)Qn()hIKbi> zoe>c%A>ev>l1j9%Ab9&HOt>9y675OiRLSyf{(ijmn4s=?7SBgAV%n?q@x8Wkf(YXb z(B~!j=F}Dc?A3bRTift`W8~2bna66A&{PIgg80_`9NMdHXiJ`HJXw7(PpJlVr>}7% z`B?kDChY@jO&l5NX?bRTZn{NVNQMaha#1hj?!VusI=lJ71N%UqK7`><`KY6rwQtiV zhM#=-=XbiN9gm_J)9o1IwB;GW10Vpl77lGs%9Yu+;^7)!JV*((Nq!+*4( zv&D#8kQu!oV`i?|H8;Mrkx(++F+Tf2N$L0Sh%!fvnW}FF(AX_KBD^QiR8tu5Cn}iH zRQT>m5&>czqHQ;UAz6V8^f@x{aZ15o%l@G-6SxLD;m2hb`_xS4jS%t)jBKa1qhI4s z7hi!keOp8;cvc|O*H=t#IsSJyaTC!!j77`UnsPk?c0c~K3JNr}5bMt{BO?*4-a)_39S!B^75~T4_Q@)e zwUs*(_l;|EPOYi5?(bqx&eu{)47wh>nJSW&dSM-s%Es zF%h~IrXLco5O)!l}r)CT)Ap#5i=um6+J7i60^76ytA|nUjE{6eT3_%2YQb% zP~NxaEE1?f{e;bj_&kg@14hQSA@)o#^eGLMQr0W4I+aIm30YQyQ{E)3a&2`XtFqlCzck3 zTWMgf1_QSOP*ukCZCh-|`qLf~|J!AlGprWS!J>J{X4I!Rk}_5)T}H}MZIRuWC)N5- z@#`I~Q&Y1;%fXD(-P9W_Y7KBCU-~!6vmsACNfVy}kwA8i<~tK!&H5hKLA~qiA#~Ao z;K+T>M=5P6?yljhS#*a!AKdc+8%D2pNbC z*B0V6$a)NsL~1ZCbVfs@%@yYBMMs>i4c{I6S!agrdfN19ONc)v&eY`bvPIcrUWeb% zsRvLYaaCyMjEEDhlWv9*zqUU%WB9s{Ly3>b$((#kUzD^J>8O+yr)t)o6Gg8c6{wBY z@5S^J3Slejeu*@#Mp>nrp^IWE4us;8^qnUDV`lWrj<2^J2aI8%0rLb$$eXaECm01$ z)lkS?6AjEk%r{p7W(xWm`T^YAGWMWgG35ELPrT#fZVeu5+2hQPALJd}1C5CXj5?j4 zkrO`W?H|EDY$M5b&`9z8sIz-k$lD8_+i^wL0xI2EnB{5Q2x^9~l1M)mLbRpSC*_8; zxmNaOW%5SiEoPny7;!OOGld(B~g}JP6nSfJ2lds|Z!9E)ID(3iS&` z(d@x3tYGPmjEmStTuCSjC&;++r7ghlL2B%KtxWS@roGizU=Nqy$%g2V7 zN@NHxu)<{dfC%tCwBd^h!8D)5!1@UO!<)b_|AgdnLI`GZtq*OFm?|IW@l^B5MbnS_e$8f7YYh_udo^Y}1m9 z<6=)`f({`pFu$!8I|8*%%F^;429e&Kv302}eD5PJJg#36Ds7cf8{>3V-RVJmS={uq zu24Kf6v47uAaOAp37gB_uApJyNCv;kBZ?j~Ru8#*-ot(_Wc>Bl?l=fV818If6L*&KaFa+5H)dD;nu2sQn+5B1lv)u4pujTdM^FzgB` z`2|?c;vv=Y;*oZ>HYzGor&upfo`~4^qH@a-`z`p3qL^|L9ncIrXgl@suwFU zTV=9i&g`*Z3vQTuhftxJtxT6%#@vBg2|kbycIPBM3QpDdP1v>CqJikju>|*Kmg-8j z!532j9Y(Y{$||Ef(62)6WoWPU?T2J> zVfp*O0^?AS7=sAnZ2<%((&sWj)d}vqY-m+)A}D#XYE9|6k+N!L7)uFkLeMZ+0htYa@8&>Pf&Z3<28CG!`a(S znf^Pzm1ruvwVks9D9(M{G!qhb?`;eZ4WHLGjSyWrSUA#tW_@kNpMzV-(>dl&?Ax=N z#dr2Zj+!rRx3wUiPqNYarloNlXw%-zf&1-0w*)piIX)aHZaTb^8E<^b;|vd%syRH4 zC<DyBa(W_;A=Yq|=zUCR9*4oDO@K&2Y{t;#51`euX|I=9UL%CkuhTUf=!tr- zl=(d0iK1Zl0?tPcf6kELBU;pYn#}!ChU~$Cm98pKyr09ZDqg);6O>r`vVKimW_i$P zA85JES^A7)0~)2om)nRL{QkV>>1w_3QnyPlGOAkV1BOKWxgbHH=PDB+JTmNpjGPYx399|HNS99*w>t-`#0?RH@1W=~zPvb# z4!)Na9jLKin$y6`FBDC|8H z&pt^ZFx=`xL2Y1#`3Y?vr{O_qqYmwG)dnHl%mt}s3I1>xh#3(1*y2Y z?^@uHFn9k`#Vphlg#OJIg$#p|H3ZEcsHIs^8WpU)uE4+`it!%AuIHEma7=GE__g<4 zV~7pqf8pvaO#|a45xe!wS1W7PAOBIMc=w7%&XT zaU3IB-smBYFGZ>(9G!VpD0J<6jrlA0_p`F69Y$chU^Bialt7Jm;gOK?Numu!HuKYu zlF>A`dHsy2tMspOVMpEFa*`(VpFQj6#r(dRXH-c;W-nh;ZbIZ(#|Km{Dd*^U% z`rMC_&U>QV#kB>^$q(*Y9@kd}@Gdos`2^28g${;EPEks)8M?2)7OitiS6R%OdY*{xkmOQ&4jTx(c7@M1>pD5dWOb$(qND=~G)d5Xvo(Q7737)gW#lRR*7`6s@k zjqhmMkE~?HXGp)j>uCOAa}!?+O@YwEjZs9q(4Z;0V0b;Oin8w~?u)fX^MeLUQ|zA0 z&BFV&kH@pX5iwz!gsEKkezeLbntVgGX@1UP>IGNpd;--zMJq+tqIKtHu%E@;o76## zjm7iV(iRw}MOpyUWTOs|U9^D3O(si*hs1!||HIcG+-<2G3Yb@Bg#kTMA9z!;@ZBj zmh@u2GdkMFo8nJ1Ewg>PSY9l$%e$Q#{(EqgI*L;Jo*bfV8fQMvX=WzjaUy<%{_||D7<1uUs_0U^5`o0|;_m1(ZL2-}$3F)X@h4>gYgg@|p>m(GLty)4PX+Z%(+zET~ z%7fnTe(RfU!h8w8_wGC}li2uqZyA|E=j4OW6}^atgB{ovRYifqomU3;?V-l3i* z)yG3Z#-c(+wjJyCO>Aa|gWAJ(7eJry(bdn2wj;kgiTh|}+*Dr*Ymb#6yG|lb zdS0J3i{a;`8BlE~6q+slLI`{7P}bP+r;Jw!c@Kw`ig)=+=*=SsP2qN1_+dfXQS8bX zK*BL>WNU208CAVswR#W8?H|0$m$@BkRYV<*w@L67YuY_wcdLSGYOy$iAASUJiDnIq zI*!+oH&JKWo=Pl@W;dg-hQE>Z2@kprOnx2NW5{!M)$U+D$&Ncs5b9=a;p=?4Mzt*Fkc1IV>J8GRvP2^K-~Z ztJbBM)^s&}UOfg@Jo@y`6VReuc>QeAZ02C8lS2*MWb3=GbH2#svR?%WZ8oWsSIa8n z4g13%oRg1##hl6ybEuTS&o5gtQ{Z<5M^i}UASv>3PcSnt)+Tpe9t=uo_uBKKqxQiU zHsL*CR}P5hXn#Qlccj{D zd-ZXDuWxM~aPoH|@O|Bt2tqJarNQW)+NLE-_9k8F-}!O>7_NO4FordZ{6}yE9IiD4 z7;6oH1(AIEQf@lI(g^?a>Z71G0EIwUJoKyS3p9P1$cRT~Aphsp|A2!xn0CUZECIwJ z-!?E4XLh27x3g*k|IL3mkNSkd6}xrh89z~rOTgt4eD+aPl(6acBHZ?dd8XT zc@ls3tb!~L*Ei@k0+?2{;GU%*W)ROD#Ep*q`bz9J_B)9>=xlVO^%P}(^;=WZA1|E7 z*)Gg$sObm%7B~bC)G$-!pi7R-_vK>oYS1-960i5w8f2qVijh7Q$41ZQ=UhMP?G+%k zx8?30&qJyD*~1UaZUUWk<05W~Gx7@~aKNj7E6u#DaG1 zfc|=hekG*FdVnRw>h1>7*=sq`w~=i^55gXY_FgeX&v7ko-F7H8PT9$s`l=A7Btlh_O++oL}UbC)%qT6m|S$F*^s zl?2XPTF}7^PI5d4@jzHZ-F^JGLy|L9N{C?()Zh*j6$DP81T{Kt6LeWinQj;YU%Q@` z`759|A8Ifm^!;V?MeZGmGw&;PEMieV-i4?>*%ZEybc(Ao9YBKZRSeKX7TI10og`1* z=yIcxE~baDkHrqKJST?B;H6~4vneo7MA5j**LKc^qjKD{Y=Xy4r6s#1=L?-vOLjV$ zp!-L`L|C1A7`YnqWQ13~-pk$rMR9*Q?v~tF1w{3jg&+FeAMl0H^cqeSTD;IhSl?=+ z4Xtv$*hIcC(jgBfk=6wkQ+?+X^RG2M^wt}8M`-i>vXJ%-8Awqz25IfCfFD3z_Dmsm z4{j0}>=FM~_o1Py=df=XIsaU6VL`-EWzuC@O2N{DQS0Hduzuq^KPjsf>gPRv1_s|l zK7yJijKBu|6`2VUm$4K)=!CihA#p+`@HZc{~%9eV*vABYzPY zhh;f{aMs@%lnSRHT57JY$A^SqI1Uj*AlvxAgiB&1QAGYAB=^#z$DatiO$ua70}BbK zgI1s2!vZVk>;L`ACQ$nc$~yDKDkv(`30DZyY-D97AMhF51G?hop=aK>()E>|j|>~= z>8NJzK^$qx9}H_xwi2x?Bc)dEigvu>M$pHK)@|m3ZbkU|b>}Y6;zk8Yh!q0eXmSL7 zo|gY%X_e%1(KNNu@&22FAj`L38~677yzG0s;jGNuWuwc27k3Bi-I-sFco>?rE^@s) z#ch7sx`!dxiwbldexY})0VPt_SYTt7*Yf_ejRo&n1>TG)>Pz z$8>@O9;?3BGkkOTRmfzs-}&EgZpcTArmXcX-*A|r{btFg5yoU#`^VnEM+o6EZ z7l1Fa-IZ@_{6GU&e~S4LeiH(6jTb+Q0&apnp*za6Q^48U#4Nr$^i7Bbv%UGv)G4uU zsHq*g<-aQhCJ>!KL)V%MP0tdF^_T*|aGHAEztKY;3(--yoS_%FlllStl4#lnOe_St z9Dn~^q?la)N|VJWI?{qk!O!kLrSNue zjd(^{XCxP z!=Dp?(LdNR< zalmf+00rI*SU==S37KZeOZz}er5;AaXEY9l5+>fSl*6~F#TLcy;Kx_sbE*I|bH6~` zy)?kw*A;~FqnHI~UlklF-FRA-RI2CG%^I1N~>qtJ3oQ zd<(|IU$sv2YM0D4jDM|a-LD5zgcSkv0#SZJv970o*=!F1OMW{fGXwG=j`mqFa}BSM zvD0QzeizPPjRPmrekd+ZioO_GCYJT|&D^^@J(-`tgjN9rb9?;G3}88?IFrDl`8?S8 z;5alL37`5v+|=y`sPuol?R9blLwFmc;Fwxx|6H^?MKLKkeeKi6luPGlA7Rrh5BKq! zV4-hbXf2og7-=FIWYdOgV8Sx41V9W@4*#4v;XM!vK6bAWd8k&8+(*JjX@1q59Vq>- z^;J{-{+9c`@gkY8?#~Vz`VTmtGCbf-Z}`aM2EG9esA&Stv{YaEVz@M-*x7JTu(N90 zTx(ARe^tzl0^>u00vO%f|4efuUAYMq5JfjZJ~7E(j`nnDtO)2<%-#EQTFS(LK#W>r(DKs!gqra&vx$x}9*2zek}%EcL4L zTmXRZuK0iS)y$bDw6p==SsvPN!3ZNm%47v+SpdIIhH-(5wWMXZD(-Y=%-p5NYMW+2 z^YWJ92V-Y=kcXmJ3bg;l)|-bz{kQMKifn~sU#GHFwq(yTN%j!Rt|?1GNU|hmM3xXy z2r+~t%Vb|Oc9AvN#xe%kWyUgwSw7FJ`+k1k=X>A3=Z}s9hs@0TwOrSAUgve5DLdqj zkf&8zOIN&p&XyEM=v}+-evbB}h~YUA6@hqUo&;wkZ4iz+7GM!B&8FuuecgW3sRoJ{@{_ ziUBdrx{Hj5f@EW>X^|r)1RD+J-fe1I_5Gi~>IgH0Tvj+jfb%J<@h94qxfJ>4M3{%+n;LT>TcB{MtLzwS%<_G;_R)s;04 z(j#-yg&rzh4TK`e0njMaXXtz~Y{yg+Tv_*@BdAiS3F9Xz=JzkGHSDNv+7Y-GIXbsr z@`;L(a9+wqu~v5)>Tcxx*yJF(CQ9o5<}p)M44quTqktfjw~sv9-TW1?_Ool6`Kfi> z?I*U>6y0!*x05z?qptrKbSJ&>WD}tQ#hYR=x;Xb%Q~&NG_byqZYA+a_S^Iggj(I#J3V|8qvea2(?{bAHxD z_)WY&!*3fc9@}HXbeE-f7w79* zwCH~g;JPIN;ed%GN+2~H0os@05O!a$)mW6Mv?26-?UA4cIa@*Eik|Qj-s94Cd>&^` z`G1J)F(!-Q09_y*w4d)RILPx(Pp{t<`}K4x?YUK{V~>)BF*4xgExl8ZZtpaWeSIAA z!hH6ovik(&ETFRT+|d*i8n|Hi=?tt6SL975s~DPt6^K{*~K}uk%yPEWX;9=9U$Eb4wv|7 zD~!nZw}b8vlMV$nR>T8R)*@2}2Rr3v6=CdC21l%HRNu7LyA&l|f4P%@$)1yXkVN4| z<2X7|@n{YT+gn8OdJ_~LDcn$$5|$xY z)o`qu%4HbeTT@5t`}uoku~!W%Be&J7S;b}F)#AkLWz`JQhU>xTe@mnkf(`Hs(UGSK zOFF#B3nj^cQULql36pd)i3wx3;DZ$w&dtFWNhzXWy0yc&`E`# zfKzUp*{|NVj(To;qO4S<_0Q>#x!O(oiq;n|dYuPe@4yeu*q#m;Bh&pK3t)D>8~@Nb zd;zXu;M(uE-oLks+z5aj(U6C6vt>8in^6k~U~Uz0yjj&74AC~muVuRZ#@Ke9*O6^g z>=jP=vK8eNZ7r!TChVdaw{K7B2hXMWY7d^HedlW09fDiN*(Zj)HqFkNGXm={JRxZc z#LNC!DD9*?kYot!fMsKBwleTuHe)h-pIg|1TbRCOs#zN~Yj{@VB}YUD@-ajS^s24d zL#rpc%@Ym~f}s-REAXMZv?Hn5fN9-g?D_#`&lL=Y{w${WykTzCtBk~-Js0>G-kJeV z6=WwW2^cNfEuxQ6vAf*_PX4JYyJ8EklX`~OX}_>l zD=*<{8`f}X-m4?^qsk?=ykmb{>T_n@qJwW;DrtANWEL(-s8H{j$V7CR$2A^o6%$T^ z*{WDQWXz_f1aVeH%*$tC;#TsbF}aiPDN`!d{ec>(>fKlGeRU+C)FogeD5hneG+{ik z&b`>Pk-jt}s;s$Jd_1u$qV#*gJMofxoRbrFumor&ETTodmuyL~1LlA-w+8aOtYsJ6 zuR1j$e7dDRROX6N`5xN{#Pee}rHO&jrApkEKX1RVYD4bG+5v3GoXS6T2ql{$dRs{y zG#o(H^w{8EgB59dB~UUbSn~zZQee}f4%Hz1h#BZVbcT7T%mu`}JZx9n{55oDktT7d z*a;3y!!z8_ zX$R#`dCp{s*9~pmIt-6>k28x!FpI?z&8PfnCdg=7I`YU!IgE!;(qV7VBXZJ#0DYM^ zy?~~_>tuTuQ_Ln#9N($8WU@3a4yMAN3dQoQ5C0gusZq?g?X)&4j^>(ccVn&P9FII0 z4GA)m+*&Cciq|y7?{;aQ9#Mjx_NrZDOOMUl-OO+`kQebssxB!D z!cDYpo#~|a`)u3g%NydGDRje;CfPI%aylqWGaKR)N$3^{Q5IbM+puM}%%S6{vw{CX zdf%h_A(ch5j>nxUFRi4is^o5_)`VfuH@s}lJ}J2{8W?9WcHp7$5RFS3JhCT02Q?pk z^NGCBuojZr!VmxAwUC^ggAj{nZgF=W{b9Xn))e=}m_?oGp_;(?k0b$buyW3igc>7H zpJlQTe2LL|vFjS!6X33)bMobGoC%LM}eo#Qq&PdPx!0RV=Ww;y3z96A|!mD;7 z-)V0njN?^_fx)QTcz~CPD2q#k$=bpT*VJ15*U}89wg$P~#fdz7DzEX|9ajxn<1qLL=2u%dl3robqsp6uzRWmkdK66? zuPuo`QF48k@WKnUhLv%-T&U|RN86TFPYYWGKH&9o8s_zWO)V;aj%6fwS0dq_ zCQ{p0kv?Wmy7p_DlQQ!_Oz#6M_dalFPh~6 zNA}hH7Wd>wp&wbdLP&#~V3HiD`@YhT1QY)hg%?7qtxm-|NzJ`Uk@s}XuLns}&cI;*SC zqci5LEQ5kJ^gBUBa6`RI1cImSe-WOMDx39~tyHqX8mtP{E=@H%SqS6VNdDpe@!Dm_ znJc(*&;%V)JI3CLcktZ54|{>2LwGRL_~4^Zd`qZlp-_{z``I1a&^UmU9V8&U%rUG#7EmvnA7+A@m1nvqu(9c8rFPb-V4Kl}$g?r0 zjPx*hM!u>m*4XO&@=qRXxkDrgrnao(|fL7%<;4L z3?&Kx-)GiFYTlfl@DL)7k-)LSQSH!uNR zU7ED=whgqvGllWt8W&g_{m{bu(TU@lbtx|&o0-(9d)HeK!+7{E_3Wa_CjhE=;E8qu zRf?`BEmryvON{`XI&uxH9HixW%7`000YT@5P5`S_&^ycnxzt5{C{Z1~t4!sa_@D2u zLjQh0o_dw*H0rl%+nn$S1x9{28SLJvvhnPb=UuhSGp&mkm1IAQ5bFPwYg>!7DO2ri zc1H0fG;_2rSg2iK)y|kL_c4QdiDE#yKaY*eq5DM>46f~x98p%&j1;8%B!XTqh))pf zY`t@^E=4_QILuE~6)(M69n*(dDK{CUSnd$jGMuy>XEu|ANlK%bB zzo`0(fwVp&yM^1C$Y=N*1A2gtZ*4 zFK@k5zj1xt)$MxW)Y7fIbfHF$;W7;xiva$kxJ60uqC>+y#zls};p1VE;`NjPAX|T$ zHX|6c`Qy_%jfqem@AriSvu>gd+dWbdYV!K@`aEK`?|X_^sYmzzB^#*9W9_cLEQ=HO z@y@GUYv2n_)z3~Fbr&q9%dFM%>R!CRrxvF!@ys^R>Z^N$uwc6Ko`gg3p$(x4Hx%8% zNgK%=>hNWrbA+wgGZuRLa!=e$(omVqytb^Y*Z18n*rIiR8C_;i(+ait7>vC}DkZ|s zoX>=CnLqb^2CrifplocV>lOAG@f)9v{O+tn7a>hPS!k1s`$dDnMSw(#PAPOV7vRH1 ze5iF+bUOBbX@|4{?U2ITkD>oR${{KwRU#u{I}zb_KqBYfjsJFczBj=Aqn_xRIxQgrt&hGBJ5(Bqf$T5J)875KGw?9a zev{G%((%XeNv1YTVcjO6l$<{hWG|9a(r9pq2mhdM&LMh=khkPY;U^Gj@+d3 zJZu*DGD91Nu*uY}UQKAKX)?4q-nS0gTuJZf3#=3l|DkI*H)POG1xJ*DNCgXldqQ&4 z=A!5{54~=N((Op4R${W>xg6Vh=CQzkCdjR0Cul39g#6tva0DrWXfpwMBwBDC!4<#} zeplevgHcW8Jl*5#M)dpx)Pa5z1v@)4d;XEJ8}1S0c%Q?-Z3+Z@h+AGcV)(I`*cO@K zdJ=s7Nc{a;nsxiLJR6~i9~$)*X9orD@Y7TDo58^FeLS*Ll$H9i1_Ckl8ORI9sL6S| zvxZgxDPjhE8jW;8d~2s37$A6r%7H)pm7?;t-P#(<+YJ4JAthpXOkO>m%mZE)Pw51> z`ZJ{x8dmNz$eS*gILKK(Z7E_Ql+aj;@V+?tHZ&haAB2%0n>om+dt;rE;Mmz`V}57q zPp4fPymt1oCf@4@zywpEI}V|s{pP%jSabU3OdCbkswRTg$gG$ZDFC058Y%l_Q=N9{I1R8pGOeT zUJ?YoEa*#YwMffbZ-Ti=x|J#0r{^%aWiRjO`Ll1eP0BYn?>2O+p^kwX_|ad`F^abY! z^0myj^y5H~VWsO;*V=dxCv@NCP_5n+Ml_>-VLmC=JI`^Nkv{xD#iI9(Iw+G3Ykhk+ zjy~aw;+_H_Yj+a@BHAbIz;(MZRx3!>cx5=9&d#9fSgXc{>g1EGbbf`JE(jG=jw+Qr zlPhSq1d1#tI(_<`D_Ed?LF zB^CZuOOeKjVx8%WB&&?;*EhDjyS&r66XmoR$l4jpxat0k^~RS+MM_~ScoQ0{`6A>s zLI9*|O$MYeuM@RRAtTS0_xmgC^#$$RE}h5NozwSjXcH6Iz6*lWiI9<%q%Ih{GPP`J zX3CSdsR1L%ay~TV_ziTT^eP8Q%6BuUG!#;a1c)A4zLHdAS9f}ic;&dn=$80lb|KTB zAr-*m$=9jIWu(I2Qftueuh3vjb>gn@NFeH!k{zPD`eWxY8G@L!LzVEon+dmt!yzuB z$BLd7=GHv?dsT!5r*(9qD}5tQWJSuoOSk4@y;|_qu{S)d?~3Y3R9GIYUuv_Bd>i<-BJIPUHprip}0A ztRKwPIMB?n9gzLCVxXBJoJv))uz4KavkXMlo&zH#1CVrNf}6ldUYd`_auMKTd|fc& z70@v$uucWw{s}$uNyotV?-|l!G@YJidf?*gxu2}SpetMR-Qy9 z-T8;kaUVrx?Q59;zxM>uiwqW{HF_eC{OVEZ(E0M5Q)9{yreOM>w6ty6v{yl4?oZdR zR8Sl4Ka5|@m1MjX;`-uO!eRQRDLFLsB1swlWCCa2Nq$Z?+VhG}uW6_Wchr+ny`r>q z`ILy0Yal;HhhKeAPvh=YR-*fA-G6IzfMhBHr1jrB2&%MiPtjEN1vL29=lN$}vntezS`}6hvEnOm5NZu z;&1s*A-f<v+_qr2i*~P}>rPou?v6#09$xxBNL>uu>M+{`- z$m{#0tWLq8mlV}6q$1llx-R(`^JkBj=4Gr28NxKyPiROBuv@cj*7Cx6dCM;*Menoh zm)x+$0PNmY!sJ^%)%DGQxV9B?fwjM2DLi*e^cdL*Y+^k*nmWmH5m+>cNUn6y1b?Ye zTb2pKcW1i`F2}gu?B#wQGZgb=@wbM}W7p!Qq-;@H5K2iu%WJS0a_dfy5O>XUazh3a zbH$XIj@s(UR9Gh~8B1dYk@q~R19n6Ob2^SZVEx+^!Cc#O+^3|^6b}ORU1ng-g0!E< zC(O+(i5T+-cA|DX4b~Z{fyHgi4NwIA8?ag}i0@_i2^u;;p2~N^Y5TnQAEYUwO&PuP zQ&SN*GlU>%slBjdK#>_b)PQjUR4^?AY}8^{Ie__n50#QmfgjC*&0qM$W+{lmT}3mS z=tS)Q{)AY&WWn+3W$oWt1~2dPz$;6JkeXfpAb3yu`P;hkY5h1{l1Od0|5hfD$czPH z3lf8_9!gXO#JFIrealbx(Y^haY6q?})p>5Vzh{?w&bDuxF>(MO8);h)K{$F|I_#wN zOGn#vduInn{aMkAdCco!aVgS=9|+r#+H$luShbR1d7P{H=HXJ70XmhX=GUe}w2{gd z+dJ>K`pg$*5<(Mhi>^w|P_KemXcWp0N{*&ZpzDeJhfWL}KK2w6$lY{%tzdF`oiy z#e-~|`I$GNdd22)gsqpDbXMVe z*4oJcC;j`x+=ezcYu9VfUk#fxcFmVz*VO7O4T}OEL`b1+`@7SFP@35lp)n zy+V)pOOf28Q|`$dusB4OR2Ro7iU4U5=le=q{^!!z-Z#M@kMprF&I`CaeEFT@)Clj~ z7sFoqPmY z)-X+gWUfz`=-7CU<(%}ezU67dmZzhTwxed@?te}_?C0?y(QjU_z-~*U93$!CwIMO4 zcvMI7DwCbJt<#~uxqKGU&*S&XU^1bu-{<|tkSDokUVhV@EFau79|OQcp5XY_w1dp@ zid>KMbpM($Oon^TxVrcg-g~)KXCBjuM#MEaY|+nm8U z!B1n0YDPZR$P*iqt<}Z#zw4g04B2Ck=!>%JTN!Tj7PV9WA|30pIL&@_qv+fSGzbyH{Zyts>@})N~4K*Vab#4p{X3AxY z#3}kWF5XJ-f6%*BS->EQNXq&6x9sJi(7qpz`?T*>pAWRTFsD(-gFviuRu(WSpd|5&Hb9U2(GZ&bxldoA; zb_#^gyO!;bZ@+309)=6ftQ~Nz@}^(*bTPZZsi)D$+C9Y2JeVue zdgeC%F_8Qxgk3A%`c|pg z)I@D{3Il0(UP$DGl9r+|!8b<68B`tS4H3t;g_%#!UjNOM!Q~-NC}cUMg}3iO{-a_0Fb^( zXfo7c2;m-7Fzx6WqU5vVbmQkVDqEu5=$XqKMc;UdZo^xBfkhc`P%R-IpkBhSq--qO z#Qcu$eDGL_8t6yNa$u*!ct`w+>To}pH|$WOPM)$8uhaG3gm1zJGFQ4eI4PpTHLyf` z&8zZpg-Hi*x^^_$>2?Y;Pt!%%2sdAQ(-Ld+S_@O5sj=Y89*j1OgyBCGdzY@I>przn z@fXK~Hl*94g!zg?YP^!c0HoG{l8NmcBGWq}jDZFT3y5&y2MZk3C|T&9P6+~k3puUZ z4u{}@$RNTU#yI_{u53MR%cI0G{XCl%jG?m$F>aoif6ir5BkPj389225v@tL(B~a#x zWv87-5d%{fe!Q26zw>}PR@Qz|&mkPXX~s-lejJk~WaKuYxASJMXcCIQgMGQN0Euek z==#B|(~QratZsUI8QuZEUhIN(#T`$abFl%pOq;s)rJ_z??OS3xtbCE-C@E4rj5h#o z;&48uMg!5b^fe*m{Xm!(LAC9wY8Plp%$>u__7}^>yxLnU`x$U<f}nJDEh>c5W(b*0}#=N1YiXT0GPOn4TA*0_V39p-yOk0z~?Sx==sVJfFS7 z>0e4RM0nd?Ve4Nzj;z+N{~g(C8wGhbrd0y!3t#Buk8VhHP@Ja70xG(@4|}^N(MiK9 zZO4k+%cE%^;`$xCtRI1IPJij#7NJu<2x><#Z2(70CmiZHsm{vuxy48|>5-3IryKA5 z^&DFX#|r3Kx|AoKyl36Hf6Km4nT5OuHv*uAK`|}71g6%qyI~W8LfKX~tu?buqEGq@ zCK)bYqAgpk-HimH&XO<$M&a==U3^dU;=^hf&pMmO7sve5q*n~X_Td_4cc#9E=+(XH zZb*6o`^wR2{t{Yq0^8*z)Tv@`5Lw+s{`9r>+V0Hk+JSM$=8n|&SRUAeo&KP+kF%xs znYvn7@TkO1Z>1qVj|$JgTGcenONAdFB*s2O*t*UqHFo6TqVK%0)G;mh$~D6DEJ<}D z!;9vbC|nwL!vTh&Xil%7t9Oe_+_xqA57Kb=h&wKKt}_XBTqchJ5C?BGbg0S(l7g%u z>iJ5PCD?_@h~7L>6RkxFO_hso28K}TV1;lf#N_C+zh%bl>1N&H1E!uKI;k%C2;l9_ z*t+X=)v4e5P~iCQTi=>oTBh!B;9x00loTo43=*UynxMIg#{hg+a5meEhZnm;;OZr%6mWx9Z$9 zuj~iwnmZ~(WQyFwcFf!ft`WZaG+b?&>b{(o(mOL-%yG>R2*@XU=uEbe@zWiHQV%_*l z94F(LMK}}PvAYh0QJP_TCvf+y@uiBVRkjrFLC>S--#~X1C;$UQnaTrQU`HF!Nw|YN zRLNGQb(_djB^yya(5d`+^KR_xqv&tGc!%3gR~8N9n3^B7r2PoFIVeWg&GLv_Q#5s! zHiD=;*iyv%w${!`lh9@5MITKOJRkKN3nrp`#1jqlcL4D=mET7=VKGm+De2qu46H5@ zXk^!c4G@om9v%Jy30ke^D#8aD4!RjFC)QOmP3y835Ntlb_R8|*#X5@y>qLgKMX!mR zd?{7vkHwp!50zjnAk82Ao90*9%+|ci&iH5#c?Wi$@pqcM2Vgk|d{gXd)FigBWIk;k zF8{;conlle$II=~kf&zDPG{PMw@sTSKqx#0G&7%RS{E5Y74d_O6e83S|6B4QpvzI@ z_Y?8bq}87(X>pyy7f|iUX&2%mHCG!#fsfOKD9Yn}9fIc3@FBh*9^QVkzur3ZXc(Pq zh;ux)e4pnrYq*)w;+`+@0yR$=hJntP-jyTBD0HCKY*N-TUql5OD2<3eDgB}TNZI{2q2{L3-%96Hntj0B#^6fogQkA~v? zKmpX_Wmm?@xgO%4h*&ZvFi|7_fJHE|neU;Tv7wpKgFBrlzr0gf?EIxu?#VT&TqT`{ZGVzhkI>3!ASSo-s1`xIJPES=thl35 znjnJc1BR>$aY+Z-~+Sz=Hs{>hMM0ihNLQ0X%H`L@o(>_sJ)|fJ&KW%2dL-~V0CIXYGZFddfpaoTdD)z}T&u##hG;`3 z`Wm>IF;oYjuAU3t6p%u52kmMU-d8q+Z6vy?8l(^-uep!RBjF7QV8{D}WE$DjT` zbNo;+$A1?Kf+x>Y^T~~#w)e+ZjOj#I$d<$NaJq?kpla?R|4N&k1o;e_TI|j#Kh1eI z&dfT^PV;*Gi2ZY~uGT?~N8jdI>>ug0$8C&^TbQo9;;IkeRfsk#?AO-qLd0|$b4EnK90q7^={X~FlzYG!SrzhvR^ z>|KxX39jBs8jkO2ihh6KmnZ=#x?{sOLyhRoXXG1R*srKa8hcnY#`nx52}$;Nboci; zm5|W!iL+f|4a{y_H|8ti(OF< z!o0d*(eS>abt%PT&CAir*9E`_^Iy;$n#bmo*eM#wwj6;`B^J5zeV&YJ@&#G@JGa9> z`=7aZ?}}ee2I?2&@8Ey_{|x@aJc{9ws_WU0Bj zXH^jW0LWzhZ}EXW^9ANjxICgw67+2s0bD@lK|%Lw`_Oq9+HC;%VH9st-?vD4YD34K z+SN?@R%h&uFmTDpl*!<4Lj0wV)4fb^LD#_Ff-O7O%m-A{R&Ieyjf5u6UVY=edZE{r z8eM?uL6M$MfX49Zhjj=E0)9&n)c`aFF+oPu&yAcV)VIU%Ae( zewh!Q?3Pw#jJGt%^n!s2g>Y?AQpPiUiEWHs?O3U@&B$`Z_#yPTu~90^ z$sF59(E$wkZ{I)y#>c)_rFZ!(% z(dG!}=qPH=WQy3|_%W<)TW&v&IX@SDJM95I_WqI0?>b<%hu{Z8JcziEJM}*Y21X=D z^+%rWyPE{Oi&*i0Vpkg}Qx?^}w53OhaHt303%$oCXKu?b-x>;Zmw+W-4c z=0K{@TU_$3<_V(|m>~0nYmW4HTDK3p|5%f{)WFUXxh3lv5ry%cMt5Q9TNo(^7VcYT8WSbvmLdP~#_paZ8zb6olp`J{y*!F( zU(;mBWW^47D>MmaG)fbn-FUx48{iFc5t!|QA0XO4k}QD!!rfP3Wv6{vGG;9?C&b?rxQmBW!P(INTGofT1Q|0cAV`jG<-a^g8SFrT0l5hU$8${xJx zyF;6ZiN2#RzY#tL0E8G+)R zyg|TOefyfokewt!eT#lr4D}d_4uYNjJ!}xeR$HWvB)T<{?}onr_!j;T-KBr=Pzj06 z1S};K5d0~ZNQ$5j5!=Xc+HqYQGWxqILL>D-RJKAmPJkaZ^5OucKccCX8|*o}MiU;D ztuC?lKNtOzJL1X`oo(rTeM35~1iUmrOsA!2roIdA)E7mxR6)n`n5COAyfp_#Wl`H8 zDpXN&uE|?|P;n!^FN-Ex0hVMq512O6qrsiicd$(om!g1Yeg?V$&^AWt&Mp;|_F2s15 zDRjA|j9RGdq@hnmYKzaUr?Yso;Qy5HyQ?u8dAr`QSnuz=Aky0fw?wp^)d9V)_yvzG z;$OJ|-TLB@Pn+6BfAkPq>Cg&u3OBn$hMSY;YE0mLfc^;ussBTlQ$uALu@JERRF1fM zbz(D;91!epnq^1ei(^kOn!fkLKVabngF^H<{WZ~hB`Mbgw9KT95Y9=Y3*52gnX^Bn z>2yTJoSC(Hw!QYUh=TiLx= z1@HO$V}dVm{74M3+4FXJ?p{3AR_rm_6W|db_~X>b<=~~)t!_`D2!5ZSuqebJmbB=%Ah7Vz99tyh0mIk2M z47|T!Is(r01_g?~MviPXDptkpEqf0h|Z{MD`!J9%?WQ1m4M#tu1-tGBuqh z7^;G=%_=4RJW17`FsyjJ^>lLS+Z!X1M#k%ei?3`sBLsSstKL>tWE!v3)B4aXz#3d} z9un2U8~kM%vwdeoZv`ovrO|3SurzUrv*3nH)Vt%xi8@<%lL+Rx<#>V%ZXy;g9318E zf|}~}97uRS8|{*+v-s49XJS_GS<&VS*(6w+m^rMBJVO#}r?n61e*+WA55+Bd#rVgs zt)EZ1#wf%urMv@rFl*$8wxPWEt%-Kf-LWZ#CItj1_#1J#lh4oTE!&6{b`(>8?!)$9 zx85R6)jEwOwb_R9ERa#LyjXh7YFNEjk+I>;-=#lkcH;v}RtJy!znpy0_j&UK!V$vW z%#lw~!b`pYn4k`WZ7{s=RBi!14l}A!|TXCv^ZG7caItyI)qM;B9Gl zrx>+TR;zVqdvfL6Nn-bPo;TSAi$nwBX==q&fUjtuiEPzYYc^~-yZZQL*e3US8X@82 z`?ie>^ap(!lttD~xf*4AF8V#~78}w5Srq6>VVSp%(|fK zLB(3&nVF%LtcG`6nM;iyqrycxg-?q)trXJ}>1nL;WG4e)o25uOgXh6g?zEh%lm2YZ zd04%{63xsIHnn1T{ouwA{7u&-5b0GBuJJ?BM+OvmSZrW42d7tUx7MYfcRHqQe?*L` zK?Ci&FDtxDig>bFQtDv5IZ*Z}vBLP&wGmF|@);p!!e{W~u7Q2y4 zeZr>o**Iz`N`f?v-^F3Od}Bh2!doP(6+8q}{FG9}&3&)z3KKN&mHN+^@|I5DeskXx zYx%+Aun^Bjl%mFg{MS4t?JkIr-Hp7k*}L}I`2sn8YuDsYqPO>+R)Wzc}1A@ zK$_IBrC!}HEAx6I?;2@Fjr>%Vs!L|!e{S_$m`+X{e*~aJ- z_TQgbz&u_3J{!AUT6C%JnKi>{=JQ{Iob-eg3saQRRO0TiCo;Qws=5oSni+iXpIV?~6RQ8+|0_^N696S+>mZQBUmRVfGv0Gdnzw+#jN*8V`{~aP;3g zkTU+EyXOI*o*NDgmIfWjf5|eVPecD-S>}gejUAeS{tLdUEr&kNv~r`dw5~jtx>`*# zJ{?v!bM7d~6NJNv8;D*l(hGnDn3z*fdo3nmHo7v4wqRs8dx|nXba}=6Yjb2r$a90; zZq{B}hX0GUJ8v3f)D*gl)XHSHIT25(l0fVJq9$m`+38 z91@3~DgjoKKV5c8pQ4%91HVcgI7hS_4#QdLP|NZ2FreJwLVQ`*5|8_3~!tnp7O;Uth*z?n+vHkeFq2AH#ZSZdz5^6cY+YO;>7NVKDB z9)qY3ssa5FNs`)1?@Wrqvcp9~<$rBe)8mO6v0^D(!exCr)27P_l%<7;n$|`SQtK1!6jIksy+@& zTD3%TBIdtBcQ{ElfICuyq_PeIwxG*)!kj$m0Iy*}db{{s!$ek}xUeo#Yq)l@usZrI z&Nb+4N)c2+*e@~l@Na|wEBhv?9ewn>6hsK?fYa8=mXlOnYC3djTALR@qg4ff`yvzY zXMZPXbBLgeD~#FP!vAb1cQxwO4AjLa)fdK@ke{ZgpPAxGepi3)9hmVrJ5i5lpC(y> z6elMhfEPl(H1%8K`3Ca;%HA2Y@9_SZ2wAm+kkfsxl%4OtIg|-e$<}^iE0rwsMiYNJp9@I+O36j{nP0xF{5J{jMUFlj``5~+L#?S3~ zcaGf?774)W>}VTcLsiuG&~G4hm=8e^GAWzj&Ata&&NK!*dU=ql3@@Qp`^qf?Z|Hdr z4FY|>|Ik>S_ywc;Tg=D6s^G7`JcO~o#?_9?omocI4odneaNWaPmt>U2KK%T|?QYceOE_!UlnUa2wgKYt9b=56F_j|+joU<2#Zc)&!LAGs zgi*3!>1eK&%far8=&h=cp=S87Z?om8U%c)@4D(wI7e5Fgs;-mMRFn1XSomFfij1is zlZ`%2O+l=Fm;%a-WHe0+7LO36L4q}Kh^?(p!P2ev&K`miX;rxHXpBnGZAIDAM4jY} zh6hrpkjHh`Qjd^7e~0E1IEdoZOxmU3AC`oJ#86quymHS0A;&@Glaspd7j4s*;#_rJ z>Iq+1%c`%0&b)`>Vx95cWNRG&`5{fk#aTDJX#Zou=^ttV$ zEoYfQ%aczzyPMvpJ-YNF#Yk%)bb{=M=q;ic0_gV%xJO7E>ujpGYR0NJoUFSjKfESW z9==DMG0b~MJ39f^vyP9F8Pl0WW|F5RB~~T={+9F_9Dx5Gb*99Ft@@vm8crTVeg@8E z`@@x$_04W`vWw4{h28H-K@z12;HBH{p`fepz(Pe<0qWWvvgh~8iw4J1UoddK(b@DG zLjPYlMj>zGYi!;jI1(HYfLQj?3PNKCF!wo|m6*+KGRTCu7L`E!?T%2Ml8W1bR%9Wo z=zIo^c0(Jqw?EB=l@POt+k~Lj?H6dq08`P4c8nB7qD8JYv*Tuj#z_kquS@STS83aq zDx^hhNzu>7TIfSc90(+K0dt1r=1 z9sTV1NiR#jr*US-q!Jc0se+)*t%uL2z5K=n0>U_!;`2%kd(UWc2R5ZajC)J`KynWt zusbmXKc4pVUjte-4yc-y-nfgZ+dCh-LuXuk`8S3@hS4(=D~N7KMQY2F;&XLQ4Cdpg z+79(%4b~6sM)}z}!3+#PBxKKy#9;aS5pznqG@`XaFI-XFE23LG|^f z%-)uTcW&+JDFc~Xo8c9f)*cd%vU{K^S|`H5D{|OU3!$sG@L1Y0P{HlE`JwSvsKcwP z;C|)&1P+>|&7QxRrL{eaBtm-tyg_?vL9z+-WvJ4Z=9EXl%` z1)lvnc8tuHJo!Rjg~av}Q!e|K(VekF zL81*N`{9xAI|b;)#Yh<9r{(fVNIH^jbOF&iG%dAobsfPGknP)n;I1)I1VkF*kaFS2 z>e3rk-*Z2nXUXcH>HAToc6J$UaiJiuDd9q4f%vhvt=!?o^FxQcAUE79q9u4zkkn6z zrTI{BD}ne-Js*v>)f52S)mbF98en#>VecT-!#!K{3zVd9RAdt6cy!BO23+_zME{{S zxJvz#Na;&M93SJP=ZE@+e5oIly`^3nXT^eTl&mBJgacA2?Wmh?i8t~70Z{4Z+RWSt^cQc`Qc*PRCvdIf$niF);fU` zk*D?G?iNni=_~&43PnV!n;fK{Y8iC;2S}+K7&+X)j7j^{972qPvlc|t3qpfNnyvDK z6Um*{g2J@~t$mz9P^`wY4c<)7olBAEF@T$Hd9P1rm$u!Dy>Yr z>T}JzVl{cq0yS9yF@N#5$FI)EQ79k3CVHrK8wCXI(MtVwwMzJZCD?Rsuc6EvAfE6s zZ3TQ(cYH2Mu@#eDp6_~)-dH$NlP$;No}=%VcKzB9(~HxSF16CX&FLK-Fu_2S!N#)m zbjN3V%@mCjH}q0X6cy&_)Ws)c_3ppwzFHGjVm)oHn=bOc?;FBrtafym{Qt1`o?%UO z-P$OqNL2(uni!NSRY9siRHTda4h97TqzOn52}M8yWXS|Y5>jj@pUFJd31O#`|#bZBmV`W0vdgg(r1vrA5qG?mdNJ|`4O~Xup-QeOSfQs5k!fwyt@z`k z+V^(3*^;&0V7Dwk#EXE#b~J;sFOF81&;jW@BR8gJ>>@GHq!s1w>dj?pQt$ZKpt#R~ ztxe$4t+FNNIMs7l^tLQ*%$N~~cin9nwI5q;tWK8Xe;K^{rnr>P9?OWVPjWF1f@`7r6=&FT|s zVry3bm&v2=#F9*?itHw?crz^_|MP%CbA8G~!M4**dlzfh8L&315(zTz%}m*Q74>^+)~q#Z{JiK+6cc1nI$qHdv;NOQSGpk%3b$`&?@MMw-7+Cq=%L$ z;?1j!f+#^jB<(ZK6#zz!4AO>)t9{{R(f#?Jfo`@UKa6b9hI4J2&N(trS zm+l_m-nFA=eD84VG>?Ao3i7MC(QdF=DXS5Ll*L>?ke-s~O^cW|v3!@OwU=w_koEA^ zJDr=aexx)@nzYgeyRwI*Fq(ca{9i&4b99zZBRpnL^*(|oI~{KpSC z>v)B%t&rxP>d1#5k=m8*9yyo8_2UPSE%JOH+^Iue}thWZ(PXJIl&VQml$w56mQq4AbZ z@?1p6LLg|@ZTV%Pyn=q&nwBEfnvy5<^a+E)m!}_@5gVLDV>ktCf9{*{AM4~77mJus z-&&v^X7IhLvmE=$yg$f%%aSOwmZ=iGm5TjhYCGm6oEQ6E^)$h^pe6akY_CA2wd_FGaD2Y|P))f*a;rqj z3=fyUvcT%FbAjIlRe3@T@e~3$G_1bwn$?O4`a~JS#z%YF`TBxsU%z=<=BWMh5Egh9 zn&hor9=)i!WlK~baZfDbgd<487tBFU-XlR|#$f(7(^|rs?ywx|8`mXAc)4hg=RSDl$zVNiRoW|C5!06Y( z-=-+MM|7^nqnf%sfzBhfL+bnsT(g#xg^RzvI_komdf;f28y&- znQn3ux;_7py6KW1@e{*RIWKB=tAz4=e%X%+#sP{YY<0fnI44r*`N-1zMREp(F+g*1 zz6e{=@qn#%tXZb~wqOmpS>j5+@5N%n=FJRBaP8@|Iz0WYgZMce8Zfe z$bG5@_s^rJvv1FlxLecQx^||kNt0h>tF$YmWNz)lHK?*9Fd}w9`pA}?=jQ9YJan8q z4(TWLr2;RfWUV3+@)_u5g}?48gsWmh%JTC#`CvnhJ6I1+_p1AY>xpf2Cc);z&uZ@9 zGrO(MW)g=*zC>0@wGJhSOCkgaI#Wh12E|hLbM67`h>i3YTmp`~$!`nyD>Ao`3u%zg z?s=G*$HShx_Jia>fEGI{s_`O%8+$t1CEF!2V_yBe z7;BY14aT{@;bh7qi$mE7o7?Pj&9!xmEmxBhr}9ms=$2=I*7DFh1UCX~%s=@q7iI$h zuzQaD?Aw1MMWI|3gY>F-d?!Ip-|Nc7BL3vB+b}&NqcJz}z|<~e1o|vUk$WOV2R8s| zIknXRp5D)2!0O9f#!Te(tyvv|QcVL&b*b?cKsL%Cd_MSrg?sD9oqivwG>`E!6E|8&c^|aP zBJE$`qB|;R1Asp`g*-!oJtTiz++4G-%lzIiuA#N$=y1v5UJ?_Vak5=N`S}H4$mg)u z3liN$zMz{l`n%Dl7^`ho&D>nzOKN*gz@eqt)=OgixrP*5E4*`FR(=3x9(YHx|Im*NFJ*pun>{Gy732&7s6G!7MFKt=q#IviPt{EEo8K&$*FR+m zyyq(e+{_N9mnLRNGUONN$H+*fT;PF_c+X50(VHc#8Ybzk#=rTg?0WNt^TKZ~1Nko@ zFm-bo_E+W!a2SCai&~z@Pq)X}-=@q(Xax$?z-nw}13g8k2i@~t)ZR(pOzdIbGO3a0 zYJ3prSSg&Mu%@uoj9P+$Y%WFk zL!5h0LJNO@CehlaN$X?E^7i2*F^Qcn!hwS&yFFBzL%79tJ?-ib@!Rg6XELhIQu;r0 zvKVrdvujZGu7=s5<4GV}D@N&JCJzoD?NqUz#M{mI)y&ka`(*xtj=~UA{lo85w%~jS zlS2Q!(&sQU4~L~nmj~f*Ys4bBRtt7{F%XT4YziyoTh<8*t*#&j^h2oEBM*AifCtr`PrzWS@I_?czPhRe zFQQ%;bU7`iCnf6EjA@VEXh>Ldg6;My+sPXXBruSQp9}8-uRp3;vBWlyQj}-)z^3~| zY1QqENg~FtPF@qBy07`gEs`pUs&~Ux-&ylAPW}bjPCpa~2FiYf^QY>$YTuws-%9&N zZEVOP?IS*_dYv!1A>cb(_ld}vJmd{u3_%#R?Ta9GCGmOFr*bbng6c>5jCq-6Odj{(ZAop|_c&nGJ$RS10jjjrXeXx5#PB zz^>Sfp=ew^B{PcQ_deIRG^I)R%A#i%>0RqM`FiN$`?8kB={8|Xa3){}45nv#FVqol zx#sMD_&WM5+A$_{TXsye;gqk%gx2=uis^+Dp~cr|sfaX8^xyF!VD+bN4_A?W%mg%bq-0*l z=GQDT9$n(G_3m5gHh+2IZo-OC%sc-R@>HF1;WD=D?5xRsT1Ud5IRpw$OO(X89#YOs z%1RiL)OeeX8S#@fw!73(bsTAZd&|oO+h`PV&_G9R{Je zYvwczpZH|XGT%}6e^#9y9GP&l74(Jh?_g${4UliOvI$as`FK>>Y~+GSl)1DbO+V0V z+MI88!q*dC<9HT1TDR?&INFKU!2>Xz%&}+Y=TzF?3cY*iUBvT2oH644d#3mP5Z#eG z_RuT^^g=$Y=0^Z#QI8vTSX}~EMD~x^qZJ59`9{~)^;p|_c3KHebWK*sR?GVv&S-l+ zeCT*#+`c}(@Wnd?am%;3hIHEF#Jn<AqQ!-+RA;G9hKl~EuDt5%Z6%VE zo&ASK_@fsFP0N}M?vPl9&8}KwpA5R+{}QrDq`szqt=prtbO}=>*5t9Z{X=Gqzbph` z<+CAGJ)~q8C{_!4B!_nTcAaC70UWI8T`{g`O5<1_JK7 zBg+lY=q1`z7FEYCrT?bQJVy2krmhLqu~#D+nc5$#>8pu5>L& z;aE})!#lPH@?YA}Cuw3<7-ND01G?vZCea@oU30YLzJ7?VACNVS7F9h7#(rEPwUv=` z;neAI*()kNGn`&W=9v8nQ+0+|wzC~!_X|C?TxbvhlsQ{;wU@TBiryI`%At(zliV|5M zvl2EeDB&{nQ(x*GiHvkZwwWVWvt9uEcLBK5FFq2TC=5-Y5gkbru*Z`yk~;EMwc)mM zMCvyR0$mGTC_(QHbs`CjPM~JnkPLo<#ug+?BKQI1wuVx!ptE(X6iMJ}l0P}M256n% z{fmkhF2&J3J{cYU4%|q!>J-iA+o;d?9{w09d`R2|8CnSwL>6^+X@71_;1Ab%wq|j` zk%TMC*B3aPCik~>Ch5TDV(kdbVSp z11I0U&8$CJCM#C7?Mofv>GO2tlX?=f)YhJQ^)*ZX3q$HXaEd<3lw1!oQeiWY_S7uK z(q{RySx&`;vA-o)+qZXi?1kUzD!)F5YPE}HWGWKWHYQ&-n&@_UCirnrmqOnK>dNaD zJfGKKcu1O6N}ey8Eq`jV$EDw^P4WBnJup&xW>03vVdI0b!XuFPKYtb59lZ=Y^Ca^e zPNi=4S^Se3Aa*O?jBq$#q;GrUAlRLu@2ixULyBpQK1COF z(wZh-1b|i*lwZ{XFx%^kuaQu7`XP>rg)YuzRSuEQ?~;l)(4^D{WGrgIvmFzrq_Pu+ zExAkS_%!ZBG{KScmsa7kCp_kS#7k98Pf~?#ryZnSRlN48*FM2{hU=9!Pk3pmG@bIf z+Z$)#KFdSjIq()Z@>W{LZiKN`^P7~w8%24a&4gbQ-_G`K{?JS#UP*brbrX_#0gAoH zJpEwQQ!ZG->cA%>X@iMlotURVcHKlpK~Ac&khrRo#uibLrPUCXJG$Z`50*?HgzLsI z>MZzDzu$XMp?>zBHeUrmz@E?qh023 z)LDPYqmv@;C3WIrJI8K`J)&F5YN59~nBt1QLc_Q&?F%tc0ul6xF6brjV)ZXVK(wTq zz57G1bE>!ap?uIQu|iC~_m$d6>?4#X^|UFw*6)eXR!SHgn(aK#KD-4S*I9*qvXpN4 zO;^`|R5vem)<|{3HL9}Mt9^RmW())fg~1wFy3{4eF?kHRUnI60bUm~Bbrlcv26W-g zNF!oE>9tZa^U}>pBh6DGnd1&Yzv(K*7;aImU3K;VniFE(XlKt8u36_>=m=x2m$!j@j6y&P!;Gx7N!c;iJtE0pj;nA2;}AHmCae{dE&K6 zxLUSEYpkmg|(w)%5z-PiXGpiji$s0V`Nk;Z*er zry2cgG|68M- zAdSa#AS5j>M^Tnc>}h_zy{!ASP}(UHI#I{d`uj&%&Fg*DSeMy{H{i@TRJS?glknEm zqBSh)_&QD_Blkg9{ykrwcgkJX&(j{iO{V3^<7Bv2QZE{34|xx0OF%-;D-aT~m`(_D z8T`Ec$GR7#1!JpC9Nzbh7~g&~+KS707<-*6MPW0ROqqD*&i|mw{iaQ2gkg<0XNM2# z0GHTTK^vAH53yAJNFZX)?9c6#>ljWfYohnAqC~4#Q7*qtgkl1n>cPS$eyosKf z_%|st8l`UHTlv1Cb<7@4fqhQJ`TIQ4r;=on`4Hbs3_8vUjA zGO5np*||~5P68EzJ%G<3vDNiFxIVeB8)Dv>s-&KGFwiV7{bXc4rK;^>X^cpm9-Xf* zb=0`>B^p4RkB)Fza5s=BYgt$w!0hsL`F}JbL@vz<-m2P*N7aU%Cb)3 zCBIb`PrH=5)L&!-1hJD`JcVkTSPJ7r?+>2(oc$<4Tb-^|Xp3bj>oX|R-t5ejn{)>~ zaYnC7PY=k?W{*nAtIU58llzNGiPW27G2L*z7PO?CX@>5c{g~&H)_m2y#Ihrp&0AAC z7grklxwCEgDd#yZ&eO)@=jc`{MuxJzlRd;sceZ|w?JOj?S}r^1#*E+aO)sL)hwC}o0u!ibg zP+u28p@-zIQ)nj&Kniz)H%504&r*zTD^f*((rL#f zNW7A2!xAzos5T;4IBffYS;`9N5w#vvs_FJh>RsHs>oHes-q@Ai3Gbv#j(c^n@s(;7 za=kqFk*ezA>mKj>+Pk_lH!Y*39_dhm(9Y-?eS9D8+XHC=S80G_!mH!R?kw>cKh!h9 z`ly+ONR!u+K;Y|_hGoDYeZ`tl${WXrH9ny22r3MymO!S*@Z31`3;4~&^nR&x0cZoW z~QCShu9Ws}(L&el%YPM|>IYTBG!)D^_V2L7b^pf6IKCH&`D#72#>7r`aFAEjT9B z@~Mfa=);z%M@QX$6kn4F5Ly)10#c8yTEpx#s&$v(iJonuJZD6b&Veiy+zf8$B@zHV z_pF%>39(k-_J&u?_s#!uN*L|Xs;>zvm-{SUr7O1}S8|Oe!@%M7CTuj1Z9@OD_faN< zC`S@YUnpqB(7|Pz9D?UA4UxsW{VzAw15@DYmCX9JP5&wF$vlKDvJJM7sf&qB(&Uk* zJ4hC%<`31TdGr0+2v?#Z&E5OjX7D_aI|GIsy zF~)(;LPy%%kG^%l4E0b`V8zuO8o6LL}LDo zg8auIe;SCk7!$5yr;G{OM85#*#qK_x_biqzIn`+Q!*3C_=;lR z>_`+s%wf(Qj4UB963oq}WbILs<@vhv-wt~wngCoT zP#_DEGqXrM0A~DFhhU#0|M@1C56BFLn$^D{mpkDy`sX#VnD#`%R3MIFU5?*qPp3M| z{hV-CbGb>~yCwmz%wz%?S3+T6AsPa;?uKPZbZzE^;quaITb-5pQA=^DOpO{Qy{0O` z(W1vMKDbHBy!V3D?$=yTvOPUQ+CX$=(tv)bS@@>UgxMu`(O+)c6&11>#s*2WQL_VS ziYL@r9#+5RFEpY3bt_l`ss7CHZr;<_N>UeM^Z^S_7=`qNumwo99bR=Tb5+{EFzM6F z-<~)iU`XS0<+LN!Z=LWEnh+Y`L9UT&&}+ysB-XbOJh2L5E9-1PU4Fv#RZMm zVu$yC%Or*+1mqXDeE8}P!k(5F5R8aPbmi{03TTR%gFBsCun6861!*pvk7g!saB*kbTy5|eJ&zoc3%6Z zI^D}ek+;$}+`hcCU>Q?h`l8fgLoR|o)|Hw;-ofrh9(bM`%3g0&&Gz(AZ@!}~Y24>P zm-klmn#8Mb<+M-*lBmaP`u{NhKOtncx!>Hj9j&KCeI{M*fCdV{$RmZMxJ*8Fsp|+K+YixER zKtiVKzIPLwX*dY{CPuOu-l74LBvgj#Y}BiV-{vn(IyJu2{?u~$UFcA%a8ZxF^`{@R zKk|uz7fcU~n17c|XU8rw`G`2G#QU%kWYm)T1F1q7nTOAbj>mZ25Ge(wcVFsg8N_6v zz){d7@f~yg5&r7|%t~y*x5&N$D={(3xe!K5AaZ%=FRCs>&>K$`aI(rs+$tbs3RiCT z4d{qT{e4LQ$PE6EYk;GmjQ-DS{vQk@-Ity8ls0JPz}D80R(JG8ebamQqx+`R3rU(6 z&0b(t|BU#LUoG+L(EK!Jej!ox($BNi4H0irbwAtQ)UT_2a98wnh`te0iL^(qq;Nd> zw26b8j+D);3_4#pqi*2%@%q!GSI}q=c9a{lk>=WjnwLAx4+&52aIGJ95jGY-1YF0S z{q$O~gkLwY1?h+#kTAmmwpPyf*YL6uh`LSm=he0W#KCTPHK%>j9n0pF6e{fvt#GZL zb&D))55Ne30b~ROx;&DU2osj%EC~@nLw>8Cuwj1D@;bf+`4eGkG__GclBR8g9-jaw z>i`C*i-9R+3d8DCLeWPu{(#_MLr=tFK>5;dVX`L3tx-loenUmqDkk&^_Q1#?_rclG zn~tVveWY-LQ!|T9jr{$17xbwW`HRX-3@J@Qeua+1_4j&kK)3RDaSGZLy7)?!9#nt; zR3hX7dS+ii5geHRc_6Yy8L1y!&Igi@aL^%04++)RD0X1{uRjn4B?$jTbqhv%2S_yl z4F2s-nGYc6NjUJ!q~g3kH~L?Pr*NJ8-)^%!mJCAT|9$jD6y?9(_rasOJ*gg2#6L|; zkt?{*|Na9}xBi#gEXnyZE&qM^|31P0)4UYF_{J1-if=%E!p z&vDZ(4ba!KU(o#!lI{Gd%ez^;lw13B(Be-guS~+j%mWj`gFN!O7<}OBQx$~ckgTT| zc!dh6Y1pD4NQi7TwsjpjQffh8YXIHiuf$hsgtQ7|-O<{H-8Z75qGF=fg+06z=hAlP zCjb6DN$wji-EZV>s|!jzc}DXe27&;@i;#a!{CkHC&^JSD6*hAop9$owZp;J(9WFs; zGL+T*@WXlUUBwe~b+|33xyIivi-*n6!77;HleGgz#9AAk<|6mZAD z_Fzm0WB{32iG#wljU@cKz?hk=9#}z=^aN-4zVaH3AOjfr<>Ymf(0}9-wjtRFkL8_z zPHc_5d3C!!?wqCp;)y3o5OLv8xppe$lqRrb#-Dg;1nrRR=8K-kOdywG;VzP73c;oX zSW_b8U<4L$ckJq+Ya-=H3hNLfa`T*yc~%p6yM| z0HLQ#2^QAOUmh@Pvj?qehT8iS+8pV&=wBHXO4ECpm@-pvl3yrPNk?qOGZa#j+K!yL ze`xuM;O*IYeDP`j)4JLvrrcl2I_GxIGQ8j4hH2aUu%4dn_xBgQeTr^KAfe5WMZ@wP zii)~dI8QD#GpU(KxqxZU`p*(L=)csv<+KK|v#+ADwG=k+CRTI6_)rTD3uKPq6lKhT)4r&Tfm72~S-t&h-6r{`{Zd%YR9z|F0jEt98Q8HB&D# z7H%n9FWJi6ZFr~L8H>mBI6dp-Np|!V^rri|e5GNR04RAH0F{ArPC4TaC2ZfM88Xai zMpHPDZJx&v5)>5bSnE?1t%9Vwpi!6j#}`N7HLNMJQ<1%LH2Wo>u#F zT-qtkhyiT+9Q=3I{`GYF8oB&a`4Q)z{927D0w zJZh*vbk_X5?eZv%o%!UQ&^V_vVlh>}W$-@;VMUZNbWKP_M+Nok5Z7FNj_4c1xo>|p z1$z0&Q!T>Bsy}_b%IT4O>`S!QEI|yxDnrVnu@z=b!-CjZ7bQ@;_@>NLF`n13H{h${ z>r-guBp(x=dwtUi9uG z23tRUe;zESPu-=cFAxeFn#Arz3BM!+6x;H_A@I6nXBVgzkkW|A@4$`%1QLbTEKJtD zdFC!9P!_bS!kP?l+-sinRWa@yp8l>T*f|6?uu7F%tf1s}jifnIqO>$h?C7uL0 zZT;pH2t%^UQ*>72(Oajpj*WRank~v6BaT7c5Rl8RgiC?Vz0FQE4v1Kc?EW;v@Brp| zo?K+dA&3|fK%vB&1LxwL{MnDGf$xtTMXwbBn-T5{U{oV%3oF%Laf#$eF7Vx@f!~k@+Ndo7}dt<(^+oH<8ZsI@_u;E{&3@$^>BeC9D)V^w|(uFaiAsiC7B+f9Jvi+ zv>Gkt(o3QQVC8I`zyS#m(q|MQNNTI@FDez^3K9pO~M`P{f3E-*E z_LAQq*O-ft3*DZ$;zRW!0yx?k(XD{EoP}Xq>TJNC1B22_;q&1Y`=~09LHHL;FmDwM zfHdqrY}%6&>_cr4*$`gvsAX=2iK zeL0Nr`?(K!cG;C@?@I@^LUoX&o5FZQ0OM5iz|!YJcbBiDv3gn4uG-`7zPb{^hSo0? zjhuVOh4_@+xU{)e6GuPJi5c%c!9i!tDSAUo1?1SYRmv;92Vu)!Yu203EQ!EHZCs69 zrd%J{+m%bkiC>=ic8eBqO}lj$HiS%gh9+FKK``J>Whx_Cvd&0m`T6RrE}f3vx^m%9 z9#N8c-(1thr8n&D_wb+xk1#1719Yc)?gRR_%6yBq2Xb^J3+QA#m_Y6)Sy3K=XWN9% zc<-7_4s8w3q|N7E193+1VP9gFNL8Tq;ipp`XHfS;T=BaLTaZ5;AM8D2SP&yZ1VXmu z=Dg)N?Ktn!m7%XRbG#K4^p6)(e_f)^19?=UwyXqLdUPAOQ@g+*ja>C+A6 zs<|B|9H#mq{>aQ4xEuDni|7x;ImHr#DZF4`dV+^@iUwMhR%oz=ieFE83I4Q$y8v5e zM?fw*wWHWb8kIQm1c}1?DdoWZ=h(;vn1F|f#XX&!i%zt>PlL=0zf78O-T?e&y?L>n4WF%~MqfaSYFM38LHD3}McQ?Dqk+8f~F0g0tUa!Fq{-NRP zeuYO)<-K7_AapSvxsU_f=Kv4Wo(l3czz$&o)`W%?tg$a(7qX$rE$9!$R%qw1KQ;Gy z|A+rgrEpGB7=UMRI~bgPSCPaJq?(E0wuLujNSJiJg2bjv2|ol8O>mgtkAd=fD*Q6; zWGjrCB(^9OYt&4+I=0-vQ6Vuj^P_4`S0FIm$CErOvf(vW6TB^P_5QNqC-Vj2cqp*+ zvk?}!gr<`9ppT4Vo5Z38$ZYA`SZ&MKzvg=SGXKY)qe zL*h)39j5_7zt09GsCrUF(K!sOaN8vebHMoy?yNYtWl)-5;>XrQ4tZ8cGXL$@Jx2QK zhW)HmrxH!KEtOw@eo9It334vlNn`X1;oXw<_R7?$0e9ZoO2N)ZyV-_!roT&4sm_nD z@}3EjYR61F;rj{bg?Q)^$8%Y+4<9!&AJ)2-J<<&nu=vq!!>{U>VqBg1F|qrMSEe+s zyNc*RndH{lIAqRSpBEf2t1jUj$F~8u1EIQo>%of`@}`^NiA%7L_vVl zty*0b8ST2*$(d7}7K@Q5t}xqWGmIL$oag*O2|+JLKmo;Z%m5)lZ zb5x7?6!-CAr#ZecBu=S$@>6J%B3=55R-&oNg2UNsV#oFstJa{8;+ql_@uDyEmkQhQ z^C*4dHIRO|ot?De`XxYj}3puxh^hDZO_*MP6jN-<3q4AZ^;tC8atH;j&KavA4Cpt?CWfA zG?c$Bdkox*@H%9#CpMb=vIq$E__I$sW3bv1{uU!!zK(=c_j6yuueKu{8)iX*uhTZa z!k`RrUD)d=y2$6yEn9uESbfDLhK@1@smiN(zHjZC6+_{QbuKMS961P9cXW)oeTqw( zPQ`iUiD;Xnzt5f;g_pVlIig<+9>*8}2t9Z|@qB;_iGv6a@NXNnVXPgl;YGB5!;rh!(f_lgrRQr&Wc4xCt5wz)gGz3+yIk z67$paK~5QK#QYi|X}V`iMd_uXXZQl+^7jV2<)oKA zUk?TWm%Ig>lxhJON~|P6t)VhN8ivq;hY>JDH4-nXGX~3lH^kA`-r4s|qg!Kiirrpw z)&b1N?{IVK;xpL;B&g>mu?bV2$Dpz4P318@Fg02G?0*>%x;Q)!R%Oo10^|Y{hUDD< ztb%(5K}4TSe;&Cg@2~myF#P{EOwnP4+RD&O1^&hq+A&_HVxt|r?lQj1j< z;`b=*%=k7v2vQ%=NT>tkNRBxQia${WTz0XeU2;K-GGZi)QydO`h?`Y&=jK(?jS=@> zPg-Bm^i@wi4MSJc3@SM|6;`aN;co+oZTE-$vJ-=nPbBM3+j#vrR*e-Y)jLsD6Muj9 z?x?44`dICJJH3a1Y7A?t=df910AyQpNs6GMN<|nmFpkdboR%sg`jl1n3~T8x-WT0z z_xL(M&O|y}s6wqaQ&SpGH40eC%;v8PTXqg8c;C8wp5_G80kQpy{gP(<7KMkvVTBv~ zK;ICpS?AK6JSS$L6}=RfS(IQ)1u1 z7MqnbB$(>~>#zbUhS6Nax4cBP$p|WTkeB`U6(BarRIH?S~|8 z-Jzx2wAtOV#)78qY!FjI7Va!d$@KTRbk|_Z-e+lRqW$7k5r^&3HdntsvVN-2Ms}`=p5`;D9y=P_UZ=yyz<8ld7!T~XjnU!#YFN%o{nGIRD|;f zE6yb8Um0?+cu(a-&2WBWjm_T^R!@D(!TSuthcXSTatfbnN z>gn9A9hUTd8_})9 z>^Q*GP1$9h+XUvAIP5+Sc?XjwJt2b*N+ixqTh@c0lkmH-W#lZExSnQ5_>q49B~68+ ztW&)8d{f#)O}GUt0?kkZWwXAY7=S>%Hn#+gvTmAy4AqpS{<j2hMwY-K0r=6hK>kMIkQNh!Wc@~+sE3i{b5Ar-0=e*}(44WKoMGQ9eDU&XoAYK|; z|0sy|?x}?n+FcJ%9>0tE_-gyS8Us_28J|-2Z1qlS$9co2uWL$$ztuDx`y2L)8{FL+ zTvAvz`u6Q!mUb8$Kw-Gc#wsaeQ&0pgGk`*;}7Yr;IH+fF_}v z&bmlc`{z_B0+Yp2BgMSV?bII6uvqqU>Wb?Z_0%ZWTU}RPKz6=v5OKY9)JGN(?_KI7 zjowd``=ZZ7Jz>$8&W#I=kT?qTmNwJ#yKIzd$}UeLv4#U$ zwkHb%xG&TSC$ba8!Va9ZglA7;D_}r^%3+9(VBUW0AK!9$J@E-eaC}MkdHDLNR~Fq1 z(tI)9OyAr5>~!k->f0U@g{`no&*7T+wWtc`TC2#Hk{F@jfS<`fkcy_A4I|CRQ z4gbyj{+|#2-#8zdiy(yh*VNzfk&{D|t0Y})McXdbCWM9Lq8On)@^DY!ySz4kCH+Oi z3a&ujWHtw;Of4?{BP5||x#A_HrXarA8s~l7i@A^8F31vaMvhc}k#(3pb$O8bvH$)Y zf2tb|t>^v}u46H|pd0o)Q7My zvJAbi9t<1}K)k8_O}qh;`eS5(K8aPBqTPc6fJnj}zz#9c+BF~j`-xHy`T4V5@nbnZVOPWRB)=fDQw$lvvBKtQary*3|Ntq{QQd^{nLME2U?>d2u0AnMpx%x1;He&1`h1(*c5Rb=5H5;*I_ z!JmJ+s9+$V(%J-ZY-Fyov^ijYAJp*_Iva8wh*^uhbGJ8<=E%76IR4IAixet5-*s37 zu=s$@H^v;Z*X)u60DMk#Rp|azXa5>G4Z*KsrMh{2mZaKunSQv`RTMs9b`AC{TyL%B zkX8U?W=cx(8aAj~nwPRb0DOwX-a`p~l11SCh{VmTLKpJ^`BQ-27v01Vv;bzm0-VS* z0O--EfY5;X129$U=%Wj@Q(uSFK7v?)D}`Yha5Odgq_ZPv!ZrjO57gYztF-{RhaH2j z@m6q6pgmEuskd<)q$}Lpv+FZ>(5QZ>VR#{G58if9_owoed-aZ@a-?a_4Y6SWM*i*h z#azMf1L%8Y#OdeFOh+6KUJJ}wD9L8(BM#b$Vw`hr%#~Yz@_MLn{o(WLRZ7|PPd`#; zpza@#g+is3sbJpOp#Cg;2c5A+iTG}FU@H&0=7 zfmSnb2baU8CJ(swOY}5NrUYHKC#=-vwr^xpk-W`mXV6-J(L*vp*McXZyIp94*`97u zn&$^RK-9Z`s3&z{J)q#cg(adCH6je&P32x0PYDmk(Y#rAha`I`4<%ekS^^g z!ZTP0^YNtv6cOi1VZVU#n%C?B?g=^$$<%UKrA;zRYOE#a~Rn z)}+v_;(Dx1xDFBmkgaGu^%!(NMagH{g1=$i^f&W%4DtR3p{}_50%-%YqV8?eIyJLO@TXmK(w zF2Tz(XPQs~1i?aZQsnbD^+{ZS)A+iN?7+ZvfyT@r*nOwHksuu{AFhTS+WG}-Vj~b> z@>!H%)F0k|uG#S38LMNUwaL23ANf9M=VKUVv>q^VK^0pWxI>+BS#)Fz_lT^uillE{ zz5noXt(cf3_WBJveOkqn?0J)T;RmaGpR%gYWNx6*wQ^ivI)(J1I3KqEbo4%ZzbYYDeJvomJ!`t3!XSpdvufBxuw3jfJ8f&p zubZ!%Aq10sByPZRqayAC=ymyE1gwm<2O*y5-7mBx*HobX2mD_e`@g8Nc>ma|0Vq(V z-RvcK5uK22w-DT}ECInwv@do@&?0??ADW(lIWCD>!%fm+k*@cW#p9?CPG_WViG7?R zSuX4nM5hvz3&1Z%2>@b|Jl6c z=9_lxp~~TA!xt|R_=uw}+u1uPj1mO_wpH0zXd?j8;DUQi}>+@_km%T*I8;D{8NeGiJdwkhwWpeF_2&hsVR23mv z5kjy|?Nx-HCDp34r~>umJFC(|Lel(E*HaUR91ZR(Do`aRI(Zd8N%M%wpL69N7&nu< zaJ_kKNCxauk?s|LKR(e=Y3(I+24+?sUjxvP-m)$<0!??Nz8)KAp4jEeB+?%8c)xZ$ z@paViFlb|382ON=_Qes%+cw63A(Rm92>F;r{|hdb^W|1W$93)aeN&-~42K69S2 z;lzlMK9GgOevp9wuQxx{ZoYh^;TakBBtau0hGMel23jvzrtJ7os@%G@mbW8{;7!aU zFjYh4hz%-iYhc!>{)#X7m=)CKLz10~C@53&l>=4Whr}ldE@y=#TDD6q*aV0|0;&Sz zbaHq)acCplHuuAG`DU*5L9KN2YvxxJoUb#mKm zc1Fi_tlUSXuW6;d6H8XsH&R4w-OGpl!4tN$DJKJ;n8kZhBNKpMeOn{(~E(VG+m*!)p^mMM#mYM1; zB!BztzPM(b{e;_6nM|Uo?8W`lH^|QrK7v6QK*D0tiP3XPLC(qk@IK491J@MltY6DdGbKd%dy|HnI->B4xNwDlRB3m0r0*Cc)CujSCE#z z@CA9CL+s=4=~5AvS6Vd$ML1dPn2SOc5JM2WP$D56FuMeJ&{lvaw(v0Xb$IO(tVyC> z{+mn~*$u);mXOk|Eh-&B03FO^F;Ty4?h&0KJrv|d|D&S#HhD4y^L_7$x9y1}+67sM zjgpUR44G^_p3*PZbE%z9yB_ zu-hD0T+8rNQwIvFIzEUL;|p{{QUR9QWdku^*CVoqT)fo4`1oBrkd-j(<-GEyjlN*E zA1Cq4%d^ehGd0nYSO~z*Lt+YYyYK~kH5LK5U&jLxIofq!@xW2te~sqn^r+vt_}YZF zs&IN(d!97z6fEQxK^|cm7D~?FC%c^6RqShfp@RU3TNjV>2mb^J@If1Mp~$s-fbRzS(StB9|BUsBmeO6e#lDmld;H{D;PX`D#@ ztff8ab1BNaUKDHr;4*z8P{rDS1<~3SICd9;4}|P@5tqGzJQ=osoRffd0OK+-3HWx; zfd70A*p>k601NeNWt?Z-`s2peahT@#(XzV%QJZAo$o7 zL=WG;>HxHqEK-1)(uA}FU$6t@dl%4z{hJJgu*EZA@8g1J>vcPLpln#-+_Ip;84Njz`Mj#jD+c24f#Kw(H&04pGXVU8jwZAJrwPvP_FS_svK67ro>iQfJ$la3OdH zti$>W;tL*v2E`=CNcze-RlU|9$^OE#BuK?Y+oqp)ZE1^2?sA0PsE1XP_M>j6nNcSh zJ(<{X)5h(iC}fjEc)MufXsl+2LG)ay<3ZPqJ?PI!5-jhhX?f!r`%Y2lIA9-BQ7Lib zTMa(Z&CSgsKa|e|@$a{csBgX8eTb_mYWixd99~St>TtqT7f#P8*c!VWj#p{e;Uc`3bV;MjxKDQwF&;p; ziY>t@U{#UvN3u5eEvHAy>3V3F7b6tAqj`*joT#^Z9NjbDQ6qkOgu;2+<*~@F8hpt^ z6Bkdm0QpAuuA;^qvd1aXE!2uSolDBs=nI_1v?u4^`K7IFb?0M@5?PI+SAgecAhY7d&g`!2;td2atp3-Ch_RWG z<-;E@2FI2C1jNv*_`5D6z=}9bU7IJ`=kDW#zWAU0M-#NC;DrpFPZ&@OQEJKrSBKer z%jOg-s~68d<`i-`eWjrM>vf|-KbP|{ZIbA@#jO^3=veOwyi4~Mmyi_!{I&^5iY(UOiw99r>#b|ZoytI! z@o<4$ViFK%$T{G?S{Hj)rK-9Af$L&iki{0&LGY!hzejfmC?AdJQHJdA1<6AWB-B(s zn+d%Nb>vFPejc)|X3gIzIi0gHBq8x;Rc6lnOdH7y_K*vLBxae-7N{(mPq{8zkTS(j zA|SmH;P~LSOJ0KYjG72k>{2R9l3}c2y+!NOaf|TpkJa14-pet+XjD?d6K~i4P3D>y zf=cWgr$Pa1iKKJVEzIiY$?tI)q~5=O4GvTSy|%PjgrGEo z?Qe~Gyo(j`x;fmcF#Jumi!Y`IlDt|saF8L@BXJuP0eH=cyH|_L7UF_jN;Bjilt+3b z%RV)&)tPv8sQ%!ykk6Tu=?9;M&liKh>HfnvJSaKg>w45_?sP&w>-j~!7DkxCNsPeuD3m|x8GidAq;wzI5_h?T8Tc|WWb-PGm_5pGUvr0OJR<71Q$ z29&Inzw3Q4C2>ay?+t=nxKDq0)_^a#tBQF|YO>O{yibAH)oRUm|M*@1TV48pgu1j) z)+meY?}`7ZAw5^d{RY~*Pw@Z`{E`LGF+MUoi-+c3dILENP+)zIpayVefoSRBof<3< zKl~qzG4ekNV*rJGbq37;_Gm+DNR!jefD^~a>z4o!p#(ryisuHPL!h&QZ^sb*%2VsZ z`4fLrtbqX-W`k6J6Zyh@43Vd;YzK42pk|>3|1h#6c4-XdC8;iW{h%qaa~z?ZT_!k?5-PubB^&Te z6>rWk%!Tx;-Au5u5m;hr;j0$di3AIgf8c|THw3ai682gI>?|Bt6Y@D>cS(ylfX0O} z_>Y)Q>{lUN+Z9=+rG#TRlkNT{)6de~c$N5T@c5cI(+KzJ#-B0S<`l2H}7sH{e@BbXWU2yfztzkY5EDkjAXzG?nqH! zq}t|sPcIzvOhU5+knvv-?KkG`B5+~eAQajeKb?(p}>*WEXxY&g`i_* zy5jW*!wU3jc7n+e;P&aML)xugkOA;~Z5X(HVh}YXK7uOXu$h0=2^gT`a-YqTjOW$y z;2oT*jeMV6d#KO)B6^dkP@2}tiI_PkEr|J$Ki0Gl5EmkJz^(yGj}^Yn#X)4BJ5jw< zBIsc(x;+*nff2l32?L*a)@frbrPoru*JF;aCzIsQ(^k`Oyi;qqlmmb^88HdD{RIF= zA@ac2OSGJcZDDqpKeZSDVK7V!RHPU_FrEVbFLJaKY$tVb_Z@tV;UB;&IOA1Yod~4p zh#?4{@_`TLZ*Kuj3o)Gc+BhNk=nC)w6|ktAdcKFbPof0_9UzARCVqcSQ~ZnSuWc@0 zo$55-?NDN(HUpYONhF1#!L7Ktqx`PKb3qayL6OYr3w#kq^vwfc`6=YPg*4MTK48$}uRi#Fl5krq zvEgqr_oDi5x3@HB&g>eZ;yxL$8C)J;Q3^=vKc>&`z>nWbldI}eO`L+nU*~N2C%lX@ z3leo$EhPupmkH24V;O;7D}ydZwpiUIK4JjIN}V|9?H`+2E zWi_SZB%aXQK`cp!<~Mv!6h1KyquZnsAlIN0*pv0fV$68oxHzX$nC6YVRmVJ0gwt-Q+20i8fW>W2G+&38vxbBu%$otGc{JLhWoE(W$@wmVNRlbl zZ&-BTHj}&^>$t-!^(1CeXFnBIa;l7F>Y4>J6Rx7c6nInP~T9vmatLj&Mt^EAK&6dXcbloD|o5DjD zWN1B`r`Ifa#`ho*1kECRQx6IfQ_s^kmR)Jt6W}I#>hGW-if^br}gC3loBNjwX1{>w@fBZfysfRIGC_MKtCelRP?cQ zy{L?6R#js$UhNt8;#SA@D9RK{rx)43Pcl?+99Y0|Bmf7S$^&^%LZ$8CVvDG%OJ>r9 zWz&8(R`O)sqCYm^6wKgX=H_m9`8Z7}_y?vk{m+wjwBj547eUp`Ij*mPm+jSOZ8blE z>^Az9oj)wgmNt0vE_40e6jzcKtw50qDQUp!blJ|D-mD!a;<$ zD-?LxMXYnIi^Kw#dB+o&sjyjx+rgOit2ZqWydP*Ecso6Og2cXoj<mH~euPUl+{++@xm}6Y|UOO{aw6;#oWsYPX_=JjB*nXuLPzUzMetiNOw(qxgtjUD}3)WrQ4Vo zIXsV0^_m)1rDd`tMOJL)&m-1Yi21Nw%#zZH#s&V$fu&y*0H*=O&-Vlq!^Wj__J?0cY1;kALg5bt4 z!~q+|N)KCE8DP1s9Eoi55pu8Bni>-!59)$HAk6aSZCuqXa+oXFSL5du2Og8$ zD`8h*bE}P9eWS*JIDD;rvN~_v2>H{SAtGyV-v8AjUK;*K?RJ=_OrwQBxm&x1YI z;=a0()*?!qJ0=74n{6bX*iE2X!q#0pm|yt2Jg&pq?^`A`U9A)e_N9FJ#AKCd-A6b*2-4gmLjDz=8tZvgKbk-dw%h+nB2cR~qPST@`|wj{Kj`u*NTQ;ZyS6;1Xa z-Ho(Gzs%$&GPb;q9IS<5*?n+num?%K>2I>LBPmjd3kFzk0qZ2;wmyJ4lthx!7H7$U z-T@4@Bu?8{!9o?v*i4w41uAm}(ByQ%*<%6R0)%+jh>O<;oa((gdDZr!QD&6=#~2y+ zv*eoFu|j!Yg6cLr@FKwgu(-Y2(1#3{P5r&%bn!N$nBz2WePx~NxWxoNw*=Hj5e#4z zCx(>_B3+FCqewWJUR#<2Hy9qA+oI3Ec-sae^r~8uN#z3u#DiA7v*!g-7wHFi;NoHL zRC5iEV8h`fyib0o$Hnz_eZbqQ63`hS!C+et#us>H5-n4qzvj~vy(%A7i(d14?aeZ> z?`JJ)M7;iD=)HS~j2_296q!58z66@i!%q^4aW4U2I2Cjucze1z>nCV37bXMmGVP%B z&v+K8iwDR*>*bfvcchCWiXH775=LK~FwBTPWw9AduxUPTZyXSJ!MWG8YpoMOeOcEE z*Atmh#fO;{RwFLHlwo4x_eFF}&M0R+({e|gxB4O991bMmj^K}FuhEKxac;b>mO~k* zpzjYm(5)j9>gr8kol;QKdDZ3kh(rsNfI9&tz!QHTm}8o(wU__`>h@zV)d{r3%7!4K zh*r}FS--p~H0j`5R(0;Xwsl=2?U!b1R*Gb|lw{Juz8%122u+6%tvCUfmwpO)>Y7$* z{Cgs@hR9)Y0{mafI-qiWnE~&U1MkzqgS`uJ#1>$5=m6|ivlKoLz`4K4bQWhc-<%2PVo9rOkj_y(*~J>F7^rEAQ>(E^Z^dsiNl7lg5A<$H%SCqldmB=CVX02$AdJ291k2Ss z&OcJI&FP|;HGOG$y#3^eZh+hI>$8ty_uLIBuZRJG$0Fss%>HcMtt!Idxxc@N*V%sY zzt#KdZ?e^X1ssm!LJ&}&Pp?wl!B1nlH#DZ+$9p_Y^YA%7Xn1LN)$+C~Su)Qj2eJoK zPp{fv&%1DK(Oh-{=z6<+rtMa$PAR$vak06NIon&3b!4m`4+PjQRbOGUr)UTkgv7%E z>PfQ_Hh$oZ=|KZo!nYG$l-IB9w@?4_&g2?Nb>Doe9J9%+_c{mN@h1#7K+JJy;4(l@ za%w+T&0X`{SO2(4ynpbtO2qo6ob8H=?fL;%Ump9Im1g$(&7UFNeuBQ zM2%%}iMrvDOHkEgpW}ruslWUW(7%xWjC37S*VL_(dKQsyARm+ldJGv>@vHru{o|I{ z>)PKIb*WQUMq#gnfT-N(5%wyjW@EfwF-D$YzjyWhQff9p0BcG=oRuHe?r{CSmaO~( z)d&)XpcHVz3?Ws z*lfTiYptT>xuo5q>Zg5~n{BbTAE?4iQW6v1*VzK$b6Z`YL;)FMTDx8uR2H9EWIa?E z9XswUjf>R-WK&ii%^y<~uhH-}HqloqE(wp)NleI(h}GdD+Wl}JXJilVuX!XK`s$d5 zp9T$@Hff4uxxau+1n&y`Zr_2sffj};)lkr9yO-XZyu3*H_xGEK=1aJ-gO(!*1vHpg z2sOHV>^uu*P`mCV?^LL#^!XrU*$;k?$2{#>+VhRg@Fn&BKPqI&Pvf#@%4W<}3rRFx z9`71Ez^$%PLc!8p6RmV9tA(^gDy)1jnA~5|Uji7Kk^n!-`la8Skzr(2SE?Ya`AvwT zUn^j4e+Lt+Mhm@PhpFl*LKTlR{Qe>u+~4wv%uH{y(z!h3PyaqWy1fE7h6j~HEiiIl zzppzo%p%t#(sd6e><`_F>3;VYwpIW1;l~Vqnh8*K9bc z0W5d0^QE?-vB@mjwyDWC&$zUioXkw~1HpE-0sI<5gN%e-wc4Ey6~=NLj2=>Vv6OLc zt3?irt7dg@-a&&8kk}MOXvB2A7}=N3HyEdMtqzaRAp!xq3LBCuQTb8_*WwFzIzUt6 z^%4SC8*8S&B&<+p92ObfH8naVqZJLR`L%o3`Os-nPv81?*S{6S|HlZZ|D_&O^8R_zVN9`f`C*5qt92~`RJ=evAY18+B72Tq)r2)<~gvi3I~AXRe-+AKjMb3 zYacoZF86u4Y&+K!;&z-W7h~`lT8fi;GQP<{x;aib`zJF z!+rYg=Q6=703DveJ=a=R4>j(R#S{=Ex ze8p8+Z1smGpUw2>OGe+`jFmr4GoX%nULIn-ncV+WIu%cwrc4ptg%4Vf{Lp?M^Lu*z zL2gm^3TJ%{Y3!1HSSUf?tIhf`_4$uX;h|rjQvz-yK`>c(*FC7;dLp}gJn}8WJ0a;d z!)y7TKDdloj|I2tlSuxDnb#i}g}fNEM83OQR0OO*2ra<$cObE`ewD8aOV>xD!(MAc z>x)!5aJ4bk2oAkverU7i)IM4u$CsLS?DX=?uak^J&CI8C{yf$hj_%WK-ap6?C zDE_4bHC>$|xrRz!H!|pMVeWhWe9z=)N@wAlIpobBLJYmGJ*8?iP)d*aQF!i)H^EqG z;?FPN>_O-VhC#kxcm7;dw+T(5T-m~09nv0V0!pw?4x;{pYsX;VlCDodD3IIkSP+o_ zVBSq8CLu;LS__DtT?-$!w*0LgDt~7_cb#U)#F<=eB|W^Mn8Gk1Yx^lo?Io z_wt*l_Pf@O7Q*`Su1~1&mqT`wT+EG^hUX60WZJ#<$)Tki+=4ULNpU2G!U0TVCw8#- zg^$ENW^L+Qvuf0cNB(b;em^o-Te}|2nmaM$YVUF(ksmE9q0`eEoeT_hJ5{}vR*cc$ z+ct_(TB~@Kb8k~X3#tzUwE#TK+B~^oanJ@!GGuk%N7Ks zpLe&GzvH&ey6lem)IE?$fZ-kP^n=6HB=Js^LmuAM^X=xxdczXkEJDKAu~lg0!b|cA)hJz?*WQYo&lFB>>o>$6EVmj0bBc_gJiU&Lx7~~;Xg{BC) zTf@hTFS=TWK7G0Q(&E))yEN*SE+M9GYErOKK${LzcFq&{*>M+el|A-%`s!kOVi(PA z_^ozWP}J^8K~m91wFYI_;V07;Zw~Cs#vt@DqL9pJ*B@R@n}l^V&Z}J z;bg&}HqBC?1aun1wkFjFL6zXX%|R`&E8RiQq4K4|t5LQsQ3dBUNp7c`+T(-6f4yR* zq|05flOVEAbINMJ)uJ8q>SffU;Jd(@9mv1=rFNfK4Yq|)k)|)S8o%&&Z>vd9*-9_$ zK2+TiQk1|2eWK({6iT~s7zae9+I#e^0$qtNfK_sV&{|iu!}uU{v z*w}Ekpp{q(L~tC2>x9d-*%E(YQa9nN?|+D0kQd}D?eldwWJ4tq`OzUwff1DJ%URB?E?n?**rBb)0HLsMM{s1nS zH^6F!`SDS|x}ab)qI2^fi6jiQ$0q3iW_08VMK0&wjf*(lYdXyX4RDPaBOc+2>);%z z1)X)O0mFDmgLxu0sJ~T$U_KkzJm+g;e7tb*%IS@_%z(fzGvOlIitBcwI9jm_mEwZv z$7^Y!NaYT(e@-re2WyK9&Db#5ik3W8RxWPLLytVwNkTKR9PwKt~@DkO3Bc*0r&M+C_f(4^h%;CtV)* z-S_jhHThktQXBdNXj5Z{2gi59Me^G=TMt}pNLS(~vW+bv z`i(t^g^|I%Zx)@az(}AAIw9>+e)oMoWBr1=YXc9Z(!mUsjO9+YUeZ2DT91)cn}<%| zpog-Sc=7~Dfs8Lm(=7bI0t9FlmYxv40B6;TG(ML{i_tE|w)M`gJS{}yXIGzCSgQou zxwRB+B}odAeE=5Ed~(6(mk<3b1_Z9+CD2FLp^7mpaArqysUQ#j9|p2d()WcvvukVL z8DeUpIXHpQ^n*XdV zdR;%eZC)ru$UfwR@BBc}ZH;`0vJ^Z~%<*#4>XD&HI67L6yIrnAuP{P>$4RkiULKqy z=$5Ucs9f^_5(2_}0{~4q=wuHKkp2LdHu#gsXk^0k{A=P?B8d}3;Ls!mLjg7?^f~hU zdOI(gsoQYT1&xR?=|?gHN%l6jRtX9PR++L*+B7u6*XGe?=X$CY7ik3uQ~=x1Z@3El zKXwO1tYo@We#6Jf#*i7eVSO*f#n|}%;De`XdflB1a?>GL=ZjU#5vP>@q?PrAQKU8Z< z_Or>p8cO3PI~>zis453kY%z1`=@;QeI4+kCx=)Uq)@j2EqT1TjgQ_}mTW|&vBS8VL z!$E*B`FIvpf3S=ShKXjAVujz>|!k{2`rMo5)?ZL3!ac{z-T^cP0Z`jHD zq|F=E)E>0qUg+amd4FG{QVQs`F|>1Fnl#}IKs4F)m$^bmUXwKT**?|fm3?3R+#i|PlIi&8w?!XQqL$S(^4H(4we(0&o2z; zmDj{m&qp{*u5S@7SD;AzF(=Vw|3Y+Tv(c{kD=@8cv@o(Z*CSSXCPnA(&N1^^QNJzS zCHtd$c5<=AuN=?gt0dn;b|d4(p!gdI6@VP=&X3GzcipJL{*Lr0uT*3iTq~Lkn-Q8! zOV&7~c=d;4+vizsBM&x%a1Y*T*brPeIMe7rG+bmHA9n|8XkbSOJ^Ah8 zoQpDyt+{L|dR}s1R>OQg*Mn%C(l5;Osg@&9a`E?<6O?KW2ZYXUHf}5m87W!|4TqEQO$k5!xWJ0_=GC&O2BiZ(KO!!nJb~{*E~?InhdJ zNbEOv)cUvG1LBY$Oskvm=^zdg1Nf0bdf*?kD;r`PXXN7ST_x_F(4We%KI{jtDAx>{ zK^;F#_J^%qNk9 zj+yKQ1}1^y1=@u5cZdw|b3t36uW|Rk?;N6Fq;r8thfhd=30J_I0>i3_g}}PJ3K#(~ zytocn>1YoCR(3}JeditT3fbZ1=MKax(}0txAW37taQ*^Jy1t702q?P%5tNWC91mn? z{nx#VZP5Y;V0JuyaTREDh3F8%7IbP(;s5>nXEC9Ua35mSkNwl$Qs7_q|DXU&FuF_5 z5Lu;tce)q;%T6x`x&V_bc-;P5U8qNarE}u~c~OIen}R^|o9$I~u02QqR2n~r`Ga2m zlHDB`d138*-9=;|&6oL1B!wGZAD+?Z4GBvtF5FW>M4supxjbr3=_if$CXKRj_`F_X zxwuGR;y8ZsC;S{H%CMeMm!G~WM>0SEJk#BG@P)`>1d$|-=GRM<^z-xAyS*!Jim9LP zc^Z3M{6mvsJnsEw$gUDA^;(Ep0b)yKP7D!>5Pxv%K?pE%yC?^d|m)p@#mKh3m|}kOaUhpz{-|VAt^!me}G!3pb@Juj3P0pKc222Ce6W zcuv^Ot$jsQI4j}G{HmN!8h##}B4nppMpP2KdI6>P^!Va9Bz?Sdg&(tN(BP}QbLSm6 z+Re6~sc}c+;sYezj3~H%+vNIls#uqm($%H%< z&hYFLlav=%To$AML(QESNYr6@DO)yNx%|n*W0K!s(R&ug1RFyo_5xAAeE25g#O@XP zx}l(RT-bx2hL0LiWE964?(**_nAK0CeL?d|FFYj1k^$e4iD+WXIkKok}RG240 z*SFvgO{_Rk4%%M0zYVAz_`N5lWTbGaOJf9gmAQuf(U%3<8T{fNZON@}1A5N7o zMeD!txNvJ6FQ~R;{cLOq=A4y|vrb!7`yIQM-wBROp8~Rfg|qdrS44cL8e}|g7^QH% zTOBYSo+4jjYyieip>oy!VC#0wT(s*-k8w?K>O=f+jvceM1|_p;DDcDcWnwi{6rTu8 zMF|Mt^UIP8bG6rk<2j8pCY2DMQ)FIoP|_!ODJ)&nhJC~@NHp~uh5*-V^NlH%G4!wB*tTyxJS8GL_^UWhHo> zzQB%DU&M~3#KB>PZQB9Yvqe4Oc4aQ_fE3X*YZ1!LIL1JWRT4kY$lk-nV_sIQgA&3F zFu1C>ZTlWg+|Oh~LNMoXkL&FuPggXifLQr1w1FAC$mTMZ&_CVQT6yiwnb zyP`8;frefcf7oqW`3cvQy#TQzWV%pYSDjtBGiIBI z!w&cJs{_O#ce>fD0flEuO}AR5qJg19p!Rm~14edkPNnns*dA=!s<+*ikMll@O;Q|( zxccyY+h&Ta@*JHH5rVrZ<==}BJmhOT=8l(OQ}PYtS&oPT7&B}X_5k1V)c~y=6dTO- z@WXI$?a@2dS|XrRQa=wB;E6UHCrhDbz;|6f@Sj`f%%+N}c9%G-VqEVIuATN|sS6&L z(>>QbHNW~PkA^bD@GO<$EWhm1?BGFDzRLkzq6ejo>nIuLKo;Un`UGw^wtt_H4H|vA zYWVDrjOF5xjCv7UCJ*~ZZey?cHIU}ZJ&YgjH5S=-AYjO*;SKaD#p<0refa8suohj2Yfz(Uf~EwJ-d+5)GQG+Choz#~jTZWQo%igQTDS)U zDd%GIJsa8Rc&3ZxL~EP?|C>7}dMk^WGu7$2hQnER3p!?&_Eji0Y#{4fv8UtQ+X?1d zPtzc8-w7uc_Ro{Jj7={=mc&yGsig#;{cxI~RSeE_C|exxrt=dKjj+C4J666~cQ}-Cn99S5+aGle8cE;nLTZNkISsG@8{k%fS z87x~L&pip350VXDE_Kd&n@WXhuxfVwLLy9wyPVftDXK-*%>2b=>HEX)nY>PPPZ`iz z6S;*HR-+@ZVnGKUrOvAy9ryPBgc-0kd~daqsf@otp81AVtms)kk2*QyMxF*`)+)Oi zKOTt_1MIIhRwJ@TM~@=Tcpe$|c zdNAZgTEH8s*<45_vZ=bs9t{siGC}pevd#w9_aCxq(B|b%GMnt!qc~NpJkWnJ zd2083OJh-WGwxQX@b=0h zFI_S|R(a_z6=)wu9_Ks*cQSbJ2bgs0_h8NYIoU zDoOAT;RIy{4j*0GH59Rq2rET|nY9?}lDGc)(ZTCA2p@hJLCFg*9oyDG7C8vz2c&Dy%aSL;XgS2<+oxB}zy%!=a+#G5SW zrBkAQ6gqF(M^gxqr>LvQ_drmoT_EOm^Ny?Er{$#RT&8cP(79Q1gdI>+3*L;D2zg4) zL$)FNiN9LSJ_z(=5~2*%BYq_bKt-{c7@e5EIxd~QS#A2?)HAj@98CJ_gd)=>gUoGl z=^KwATjy#+;s@yK4Hrn{#n<=>yoJS#w(GP-<8w~vW!@)K?HSCzw8Le+;Sy;tXz_enHl$w`b{f(-+}ncorZn^nr#97|Y;r3SB#K(+kcYb$a| z65R=&ZT$XwCe(_`H;2u^>SyDu{(e38G=0$q#&de1zs*ALwB;#q>V0OdDN8C9R~Ndw zxi|CT21p#7X;)YaXyg`_o9$zY2c!Z1`9 zmRaFldQg92>WMtVFL#yflhVgj3sZy~tV4pnnW>s4hgl3B{ne6TvNtR?0#yYRYOB#O zK0U1EfGG?9{fc}F`h`*2nshLJQ=QJky)Bc{ zXpePXYJOMG-z5OQ(Z3TMITEKoQuM=AB@M+5EMIPgZ=&Xb%_%+wX^Z z^~C)_XNd8S;bM-7e&|1;1Zm9F92ybjF3_Xh-q6$<&9Bi6Z@A%)HZ}Is)!3$?piHf2 zRgvmhZOrefGI=rWvkZSU3zc@NY}QvMK3=$0pX%8uzIzZZL|jkffBqrWK%*nnOK1L* z@w)(y-*66?1pfZoRzzYn!NF^Y4Vgj7TpAP`t+XFa^Gug7MfaIfUgjr6jX_?v7BqGp zvdG(ceKlGZ;7a@xAI+)=->!bRu;kph$I#x^Y~AdojaH|+>J{+%Wj@wrV>}c_x2wiq zgUzz8gbAkjB%s-KV#qyy9R<|xUcv*Z=PREw@$@@&t@NH$jk^?dDAy1h)cDkzN}Mim z;njG*r$3j3zZRFG#2+B?TJOsWkfSIPTi(Srnm zH@7=z;B!`p-}MjW-_t0AJSSh$U>$HV82N4k%aIu=con-flI^~_JNsg@JhV=Iz20bwTYv_V6za7`#1X0pWluxg=)IF@`Vcu z@g>ZCYf0M=zN!VSK^vmB)(0Y>W(a@LErXxcXWv=T%4Q7>je5oH$ul*VN|i05A@;FX z`?Y1BihaTBU+Vdbp~sLxYnMU1M2cQ>mQ1^EGwaO5gUw4O^b2Cm_Es*hHm{K1yp(sD zS4=*YyLp4Yrn49^GL8!ChFmAut^o~8b$obfb?mwagH2Su+x1n_%ZU}?+GXokc`taq z4u!T3%SFzk9v*gyya1KH0E}ftQ+KnRA(Ef}ysO%%{uMGD9lpVa zSLX^7XA#1S`I5&9Kc?)xe|-)MrRhhvA1;r|UHut7bgHv0$8iGLPu&2q`fseg46pPr zcal6R2#WQn|KX?F`|j~@!a&&#=*OHC(-b|Sxv4Z(^Y)%NOBlqfu1WZBRUhsOVJLY|E^?Tp7vF3#du6>CKH|7v17xjJa2Mx>EkGMmnuK1_O>=tvCDtV}i_Y zD;3EH_P3QWOzfb1T;%$HtSN^MbNo|Y6ci}0Xi0^`>&HXXMDP(F*Sg%`>wlB6bcO>a zBWJpg*mT+5;ZiR5gmr|U&JNssM?;s4!hvMICl)v&a@0g%(|OnM_Grg;=5(dVXsFID zDi?lHjy&Bk`qB*Xdp{IuDIVO@B@2D8#tfV2xQZY0h8Jy5E<0JSe3rL%LdCf-3_e+F zEqzP>*V+R{)8uCQxDFkcdFx@E>PpPHZEt?VKo63^zrqIdsP!cxxZ1Ia1yxmCT<@(P zAXp&{+|syaWGqv)-PeYyJ0U4>hH;RAVLX*Q~M!)!pmBQyna*~Wp%YR@mffFrgVHIp1#3<{*;zD93 zjm%0ljMAPym&mQE_cjQ)b3`H6TG?_p5Awsxi8q66bE6WkbE)Hh2YpyliuX3GLr(e< zMd(v^P;s|Ja3bWHSL&{?f1~8Dqyb4Pe%)z%*}bJRVQTSPAnek}#S=EUA~;)J59VCP z!`ws!!+Q4I$30!{T|RIKx-Z#F#dH#up9A-w+i7SoKvN%HsSSwXb&0z-I7Yp%6L^P; zzgy->7Lalk8X+51Lf;AU))kJ+q2f_HJ0HQ8tj|S|L}3ds0gOj1tHrcCRQb0;IH75o z%|?mPDS-Z7qCt%5pu&4wPq%A+U4+Dbm;B9Js=r((l}Qo#B)&cFL$YPFP=lSf+Q2jm zzOcu{KNLdKSB~mvqI?)2Zf++p!qU%QK;{2ZN60pL7pU*ArtI;LRvWmYBjYjJ3{;$?Fk7b<=(+kN?hVHcD&9FV)a8CO8 z%~5gj^E%1z56w+Z>^Kz3n4dJ`t|#x;Tf`FN@jL5GF%oEf1moheR-wPMLxh|8rWQwB zze>8J-3g6oDMiX~vvJYJ4U6a%*bo%W#jxTJ7tU4Jj#SpK>^5ryF!h~ zkbt{`A~GzDt#5}R;N4*M4*fmQ8wSUZIqq)N&!u45SOO0^B`k;{MQ0Wz6|JI-mTO7v z0WffBqgOp!_KS@iEg9nW>c}0Jgxz;qHZ#8bVBMO0lb_WEwRH`eLUm(%d`1PwFP5Xs zC|d4tU=8D>ChflC0oDJ&a^%d6IagOQwZ zc=0Rasjcdr*1X)xd5v->E+fbbo4fucA7-ByX-+23q`eB(sQ7t4jj6*SFoq!{c7NL* z&aB%Aw0;%Wldwx|n+NlH9*(+0FNEG2HD9^kKjANtq<#?yLJ4;I0b)20HCaca<=s}b zJSvo1)g;3{!*`=u-G!eT>RpRl{o|imrBh5%tXU52M%Du#+pP-mtTG#=s$3+FfGk{Rz!y824C`Q`yVu2rL^(&}S~IVKi;iurqi7Nf zCue*btm<-7v7T|DyzW82o6iBewY-y6|EEK=!$~|(+1~$6=rW(B&~ZSvQRHWe56z7C z3^~perKr)^`M}RH((ldn4Wk>=nx=*iFAi+`f80lLJJ%E&9R-~WA>c^OGi#Y|!#Ro0 zW3973vAmxv5gvavHD?p8y&jhyk#d{>@ z34kzST%i}pX2ftGf)Mue_2MGTiX=_MJRQ|>!$D?`oF+x>AWSCW9t{?$V-cy+^zvN4UvYkvY0@@w zWu#$QY3C2&dq}B(`C$*@!Ixc7=J@rjuybEzapBlKZv8JHqr@`0AH97ZLRoP-G_nG( zA`7e=rb_41Z}SbMFMOm5-IziTZ@u&oKAVUJJG1`wDN}c8g3Gq(wq`UKY%Zug@Z%ow z-I4HBE-zFD05MlRhBp82VGKeKlc{SZ(vpmZ@iM>8z0gd=%AUHb3d{>FcE7$Fh>0w3 zyILY!FI-O5>>$>yD#k8f#(1_FJS2V&s~6;mg(+UD9y*ovjFXz9y)#x%Gcw0>Xzf3*z$m&cXv2u)?7^^%1HwlZY-_Da90*+9N;7|~^{L=e8vKHq0%8%}8&A#0 zqlE;U?n_s!{6!5P!HY|xGn<~FLn)u?jD19LeeV?Z`l+(un&J<~^kYJ zR=HC)id3+Ahg@2r%z`#|q=pLk`7+?VlpkbS=Wy2uh6IA~gN8%5NJy$IF-=OGZhu3L zCDK`<>_U-UyTcU14U=Sdb;s5*MBDsxm(Q{nouj`0pV-(i+(MrA^u`1(y4RvOw%01a zYs{hAVWFbpGrHB)>s$RTYtP}?0#Piw2f_n$B;0S8AV{>Bzs{)tZkFzICFH(HahbZc zZpcqjL))KJK}u{Ocn|!Mh_W-NWoLht`rtIo`TE?weU%-DjV{!zde2;9R5yY*o(S%o zx?>zDIT3fYt5;K|)adZ&Pwl5ZSLYsoaIIM{n>d!e=o``a=O0R1M;;%t|3~75JU7z!;p;iXT3-@LEMGl&a6x1I5XD^_LUQe~!$evWqGc z2#`*}&qp%$9i_8b0>mfxWMW<;W*NcJ5#l6&qW_aBBI-HT=z2;m#mp>TV-5Vugia%* zFQ3J#YB~7o4%U<8)q$y7tj*)8$3uF}SnuJ4wD06ANE|Ndb0uEOkWNouG!S{_D%EZz z7p}6)E|aH~>>6D>@+;R>sBrgRb9=V(H*gNZzDK`Tk5 zUWn9B){A8p_WOE&NLk?R%VV)4CqFz1harohm|LyzEhLV;WqevhF-36?WRr68{)*^7 zCrjvjL-4Z0F|H!C>6?&h-zuv1Yr#QBue!s4RH&x*{HT2WL|loHC$g~4K4DM&gmY}Z z>(U_a;BI?>r2Vhd6TO=f{!uq3K&7ujusNiQ3!duCIFlQjS*m6Vvx~xMoY`hd_ADWT zU%Jkt9gcaAfM7FZPTPuTQ@9**&wdO*tVjQ&nJ;&H^;JcR+gmPG+CUVp=5V>cL%yYs zfZ2n1`L7I){LiOM$3&L@yAB_}-IU~*KJERM`zFrnC5`ll4DAqp4e^0K{E6;Z_u8MY zxtVykzS!MOS=#Nx+!ozhBi|^GSMxtDEi-DS@)ADCb5kUCH^;B@*I zeqsh_z(5yZ_iEbQB4oz2^IzJgUNNY9`I%4WqJaL-{cfDZMi-nLKA}t$>PUrY5iqg& zgy7gi*r(z^1_`OqqzuEqMx5ZGFDoS@63^Iy&WHEr^U>QV77r2n+OcEfd4sx|`npEj z2PNq)-2Hbu>Q4C>-JkSor5Y6_^s7ilmt=Oa7k)Mk1i5eLqHBWJ>yf{xHo}s=_JJV5 zuko@cDnDfZb)E3nWh}3(9amqH|6S#O-K~-HCu-OPZh+I3Jq=$0N4f%N*agppKWHNWg=@jBxv-2ZZ}{ljiHh<#?YBFJ`8dqOvjkI0j1IK$ zX#~Tx$&Sh8tyFB4)}1(2s~=c* z)Zt!tNpTi#f@bls8#x5_gcR?E0=_X-VjI5T{#Yw-$TS~fUIg}GSRmQGpds>s+>>MW zmyX3ct8dm!b&);GKhEk24pc>=JdPDbKkV0-6Vlpa+i*B|=}&)h3TmHosgH(*@EkxU zni1V_%dEt4)0zfiCOu!|&f|JF?{F4CzAmNk3f4NL-T??JdNu31JL?WE=90!O7b{wT|+2HLJLy->39B<^lFYCM|}S^=U=E zWd8$mk#+t6ULz_r*69XvFFUa3t6l%ki{8CoyMVdychAs z$loWJ1wUGwHlXTlP*4!nlCK^6*bj~8I0>4Bvv|yn#ME){qT|S#<PYOK=N#{S|6wBGpjP}x^(=-3)nA*18_ybJ?g#13Xr(F* zDy^sE+=P(!N4Dp&y+9g}qYFht+%O{gRXCU_w3YK;;g|oayr-@lYR=3g?(uR{3zzrK zZQct9Ab+aaq31n?#Kv&C3&*%GL zELTu(@phizy6GN@0p#)dVrJ%5)F;}&y8gs!w-i!c^lNQ3m8b!db%%#50lfmRDw63# z50{r;{6H&FKj;UI4ietIxB!$o!EIsFJ($@Z9e*qd5(lRz2veB}(aoV2%XWS1@Rp{vXX0W|oPgiQGj+%ykO1XRxzoKJ>9|OmO>!FDOA^*ZF=*)pm zwik(&boYM!v^nd2o1d~xLH$1~S3iU$*X@CLDH(N5u!wH}y9CFV?5h(DdKc_*GS=}| zkLOKfHSdwIFsG2`+YFkjXG*49s`Hz&YS}sl*aY%}R!ZH92y4yQ!M`N1K~io1Kh!OI zrf%=w18eEpli7D0bzX=2jlu2`y+{`1hXlrVbqBm2OFTcC@bv|}Yjr&z$NQ9SZm+RE z9CJ0g6ZAGW{#*s;_LEH!yV#qF)m693>7>g4=_g&O@GL5NG60pIP`>imNcQMLVeYFT z4%8;N`t*Gun9rT6Kpe%9Fu-%86MTKx+2s$bmrE9t%syQs{Eoy$i*A~pOc?+r8kLfZ zj#F*9YJ75xpI^RT6OqvM4J-DOQ_}KP>5T4lkU>5`F|8vxc3gsM5M@iRM>ofI|F~|1 z{3L5a$AxuLwp9rBJrr!>*OsD~=5emqUS}6^InRxlU z^zUARPHD8`Y4%6-%(Rh;O8JMg_Cp*JpEmdvmzodwVJehj(6x}G4iN=H^5<0_5Q4?? z)`G$9cjJAkDdpeyganNDA(_$qe^Hi@J1`Fl${Qr(83su>mVb$0CSo3(`PA84$x&^< zEh+c%pDDk(cggqJYj)X0JZ872av@~ikg(iqc6E`se$cfmIbLDM<>N#DOOKnEo%H^i z{hKBk^(%C|o?ok74r>yLP#-r02}*w0GL6%plvCY7{E&TKe|OZrK+COwM}}QnoBiFC z+g|$fd~`Q7D*Icv;|mklYYQ|>Cj&ZcF}j8Z(2pVv>wT6^g9PNsE3@}-x$$<15ZP`^ zqGGRLSg1aAP8l2WbB;d#D*H-dDzGRQ_R-wQ{+(mA?J$)s zqDKm!HwecMc24=b4Wha;QtGu{qwCOD`DwRj##v za)~BdUum2-3Ag-4@U$Bl1Lwo5$HQDs?Y_PJ-uK~9PP6o^^t zFLo71e;YSw8wKShP9W_cM&g7Pf&91`$e+9 zwEem@MpQYSp_qW#9n*0Ju#gBw%&IuB`nY~%H+-^Ob(V*i{hBHl>hyYdaotUOp1)7C zf0j5%0zDvTcZY$IFios1iiy~A@7^WB=w@cUSh*mb@DI6s&zPRw8sfjD1JN3H!mhb} z*aY*FjXemEE)h0LAxLOz4*EWjSx{ccuHJUzLHc{z7jDYczAtE{6;_@Gi!qAtH=mgS z5$c+l<~-eTkObL*&@+;J+lS5U1g1YLFJF<1hYadJ8~zh{;gDAQhtfY+H}lHqPKsG< zJClL%GLVSx1!!K$-jhvz^tW`9(|?871KUhWB(4kb5gh4KW~zkfXka>`xP42*0{hZR^^5qPZScXX*4F(b0|k z!X$+Nuv8hC9{%C7JBkG^w;TfPvKiB+#O;o)8>+u;8T{1$?lNVKX;Ine@78a0lW=*} z3YuuMT-&$%4+~)pv3U^tQ`EQdmebsP7qsK-J*6Z`9TZ{4TvW5Q6uY`{x zE|BT-go&S5hWW63Z&bb9D~d8MsI%GzmA`nBV(5B@FU>d16-6xDOuBsXj2KY_6T%R0 z>tw12+0~*vT3TP4`%A7rj+MC?HXugrA& z)uq}N`s+43bDpi+Jo`dJqn$}f^t<~paQco%Zg3xjGsL;u8c{P6N0#)i7rgkp$xX6V zRM1i!VKHicQTxz0qD99u9PS8^n3H_fkP9xt8N(aXAL@vO?4( za&FIeZzMCVx4{PB~^1UN|>gLDxJE+E{1ZIsl*aJxQ9qLZ3gqKMxo8E3Ag-`QS z*IvHGzUshwT%9x$un0OV?`n(S!YRH1@exitO~hB-;%;~d-LI{SoPHd4!G!0eO75^7hX0-H|k^}ei26U)6+;wq^prkwjmkKX!m zdfvBGI}ukBbY`;wQnOSOSbF{TAL z<7DGqY=c9mlr`(|yP#>IA?E5ks!0h;02zHgt%}4SR0(*K5389X^suHYG45 z-~a(rNDP#j*mdU~58vpu6esSd?|kz=Ci1~J4X%PG5nu)3KD#qLfA6_hmM7KS<6M^gK_hchuDj#~Q{z*w*RH|&gCWb<q|V&x$k3aKSA#oYOd9LAMWX${*m#4_|Wgy7oHoTCB`eM(!(Tg7zmnh<=iCi$GO$a zlhL7X;yx$g*gRp{ebs5RlOG2icgSoVJ7aPPIa z)dNZ6FQPIEdY71KR)o{iSJ1N$cRB$ee1KxYIVM?we6QSN%kKmi49#fC2$6ilgHSz_ z7Sx!_f$Za6(O)dOIsex}ddD5W3@c*zlb-HrDBfWXK|`xQz`yG)3lDAqg*b?ps0Y!x%T6(>RkxUE-!;`aZZfuND3<77$LX9JDi;Qn){Jzr z`-zEp>S;gEf59|T`CA`7gZ`*pee}Wsd2perH)vld*g06B92xC&i2!}ustH2kzg=+N?jY55v?cj}2(H721 z1AM(o)W`%?96)QAVcv^~64c|a{8Hq%D>En>kQ_+~*!jJ7pZ(iob@hw3Y<=HMLQy>< zvz=g(kmeDr&D+)o;7FLWXP!gmRMlDJ`mL5H58sZ<@vSYUrJvc}VvR?!Azlum*@zUl z49t)a2#C_mpjS&90*qekJPDDBs}@ zU?*)+iG{L9u4OtJCqnOm{6u{>umf^DWMun4g<8>8n zeazrh)H~6dkD?qfO9U|f!o^U%c}f_FBI)jyS|}IJ!}deVhli`iZqY(dZpyJ~D#l~i z!bKeM;-=L(CuPLZ(m|HG$+Gz#o_DS(HzXPv>v4h`TjoTB;kYZMT?cr%U#(3?3=^Kp4^x+-3B_5ebS7k`PZ?eU9mk@S}=E77)3LpPG=Y$nKM_ zadz92d)WB9nomBZu7vq2Rl?H`K-Y*Wls3XRFjxs9>UEGr{apY{$vAr&y7byKhkS6$ zyAPG??Y*}%!j9(vu(^8F8$=vd{RA_QSXBvu>|)#wsl$KwaYIK!L%$F9E2aI*U{UD{ zcn-iX?Sg3-iSsS2*U#YKko={_VEv;0Pp0S||4d~B+f%}>c^4^&d^`JE)yALw?stBz za_Tc9mEf*FHZ2|)24EUY)!D`fA?nB@d%E%g%ReGeLTaCoz`8RQyxo3~q4f`!L5y7) zIXUe!VqeNWhW9KP%1?d1QIFImZsgf(QW z>c;r>)TzH7{twZ9bUiYzsdv{F@8=39(;V||{YS$C%>K4?>~ZeE4^NAr+xNEh>DrmP zy4vVj)#4W#4kfx`4H|AEdfN1UhRjv;?rPvF`YJLW!40zy#36Y)F5mM|C7f~E>2F)L zHg;h&UeaBB;_KYtw`jdB>yXY6irO75vNa*yhXqX)McW;YTP8CVid`|>ztQADqkG4h z+ilC5`~b>^+}l|VmFKxX7aLO;9$6sbtT^}a%0DsvDCz~-MG!w3p!G-l;eep`I0*u< z8ZYPcSCWx6XAsxJ?3eSHA%qGj*s&}zU?a+!s7@&%Y&;+?QqXG%7Z2z+O4Kf?b-34m zJ#^{WJW_lA%&pidB=&ajrj2aXDu_)CzO+OmXy5>A;bCWnPSQQ<8?uT$PVNl2htAG3vdFFiX`<%KH4Pk?%8t7 zFl7U^ehE=QYJa*lou~V&Hr<5#{&%k+*n?H*8Ca1IVA7)bsL^gt7fJ4Q=;(H-B+J$G z#o>mye@TMtV6J;EIe)ggW#Vp#?`Pb(=SH*=l6hsnU!lD+@3DNf4wY23Lkk#gGEJ^9 z%)kHj$?(JQI&68neZOP+!0)$N=g1p1IO8PjDHg%+S%%}_B=#&q71rxGrDh%cy{iNy zyIuYX{djVHkM^QPw&~x-`tlG|k1>?RUG&G=5QKS6+1+AxX7_VQ#*^&*Yp)J*R6CU` z+-Q92;2Q=8v(8Phr#T+k)vOrZs<~3qsuWms=UOsuPqH`?D>mfI>%!!c=BG@E>?vEM zUZMUz!LSFI@er^4M+3C0-^F$~iKW^^J#4C%@g8%C(F14qbU1$bTpFxEh%|+j4xX42 z(^t#1Q7l9b97DlZqD4@eYfAl*WTYmuT-?nprBoxix5}o5Bi4oWjT&tuA_UPc^m_;s z=EX8U(X4yKOSZiXdQ&Ye%Uav$Tm7#MI>uoqg91U)^lD7p>Jy^oY8!Xw5_5pSu~(o{ zwPL!Q<=sm$4@kvB3&tBI5_#XMTSf6gs|W8uSIqG7LMq{3Arg`J-~RGL)o%o?U3-On z9E142t+5c1Y3x!7$6Hr7wr_njPm@~TWxg|BPk-EzynACxA~iR~ zXu!yq`Szb;5^bL=U!;!fmF#t6)oB;bs_juGXC||W7ww6Z0?glcAv@F#8}K>XVC>c5T{)V&v4spHU!$Ub^SGoMz3{ZKt38#JDjIortQeP#>KvJ^!5Vb3 znOeXMR+CH}8HVr3j6EpTUM)DtmG6!>@M%Q`j=q_R zhLAc@)fL5EmMcQ@vWnczeeCt)um+Q0&8Ej6m`;^aC1)_|;_Ct{q7&$ZJlaNLKK{TK zf3?1Zk~r7g9kY3FT9oqt2U2*OdSul30b}M^+vCzGGh@f5*8&kmb-dl(>+s|>eC(w!@a=Oxl2+mjf@w^^qCp+T)3ec(*RZMIE^% z(LaT$hwJ-hpp%nlI`diYdkqEVN;RzLalp^C`^+o}@e)W(qq|uT;xG(4�`E|K1>K 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

<2PpxGs0n7S_EhPCLo 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

0A`h;#GU}%&&#$I7%>C8|^~P@pcI~Dv)7e3X93f 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

A4N@KiZ>4fh zM{rzHmEUl#O@~Eo`#tA*Ll|7i(c7~zphv1}BvkU;8X;i4cy#ed>Al`5yiuB&^rPg@C1c-ac!opwJNFv0rxVW>rReE4PkvE1;*k%6G)A-nNH zUl+Rz#A`6&{6>>5@|3{;I@K>U_==!GV0Pzy*{MtFue;CLuL zlaqnnijb2KKizV_4Bo5mT{^zKEcPCfs>Lm7&^uSonys1lE}`Pe6ff&Y2YxwA1#a7$ z*%=AmySS6*;KA1zY8|uwi|7B9taUk=tS&l)tylDa zgQoFA6P-nPf+={vhzIJolBM|{Se%2?M_!-{!yS0H9$@+mGv?)n%Z!sX`mCQK9lvL7 zN?C4No)KhMUdA>edHc&>tZ8$sY`bV$I}~y1CnOT#k9Q=EItbSAGBgX?`D6bZ&C)8L z$w*b6UN>I|9%+}ln=+fkF6LewUBgUoW8`%h+o)$Y55EiA1=pT%8>-cu|^{s zv5rSd0xt!j{9=8$XaHmQ3#pTPX*y#Q_i~Ak zrW;RYR`)#DXV#am$_@IKq!J{L6b!eltqwGtItV2x5~4o-AUyAXpGoAgSjx@c^lU1Aa4{B0jk|RoYxC<^HrDhYd!y#qTK=#@c~R-lbor+@EWsN@Ju%xlvm>}XSMYogwDE7U!f4gXc4wz{IB%Jp zbqT>Ri8aVx{6mIi2Rj2w9mAKr{g_R~6!7;ia-cu&BOhP#-(rt^0b>CdpO=QCA7;6e zcD*s`$Z73i^}%cAJlS`jM!k8I1!6SGBuKc`vb@gg4v7gmYe0c)jhmUqt9F%U??^m}8f7w^^7C1LE<-#&--4D@aK*-GTLryQ4cY(W>Meo)z| zRsy;q^<=Q%3m($e^;PJgV+bKGe}2i8V`;?ET%>@zH1(>-CwKh=CE!9}7*N1;w0~AA zX(C(PLt?rO8$V}I3nU{E7Mc`+phe14oj4@Hi1k4>;(v{&)W8ScGfyB8`S{G&i^syo zl(7s@NVo(jq_b4R>&f(JeHogy`|San|K-28o_w5Gsh59r3xB{}#3;6+FdHwkh9l+M zGB54Z^i79OHq4U~l4JJ9_r`WO*1afgKN_<$%=bP)4&bAebUQ(6usbu@WkH0ZIf7oG zmb`}_QDA@ZsOWiVXjakq=lLdYNulLHHVQlJJ8b{6Zp_O5HJX^4?M>O^d#{bXnKQBQ zF3G#vQ~UEB4cMy_YW;q9 zqXO|CP6D!9T$tT#g%j+2{;ShvZF{we(WzNMXy8m{*c>88f6&jeaLM1_6*l4N)wtci zaN_1q7KR8guN>`lj#i5FE6-Xp%f2t%jUGb)QZfKF2VT9D!Wyfq`2LgrHcA*?Md1j? z1)B(si1taoSNK%*;?G+0H1swydueWX46{k$-MQESFHV%@ut%3h53k_d@qsX@b1>X zhQ|rji%_9;Z;LOR9?U6KfW1JRo+303F4}lu7`B(}4SWzg`xV#PCLje4=jU6at$`@$a-ju%WdB4@1H@oq)b{)w6v_J#&i0$>T^B+e!$MhL$@re|j zjwxb26Y>~r<)`dvf|LV@us)D&v=VZ*26;q`)Z9(a225$Ca$P&>h8(Fcd4#D8T@LI- zO)8hBm6bBCy4*$;RYpX4Mm>@D#@FIbQZU^T6u>q1`h&AY`nO7lMHd}d<#AyL_J7yg zvn0kDA~gvth|hUhmQXkP`^|F}ZPt@<+snae-q8nW9XbwEjkpZU6LqCdtzlQmq;=jh z9Z}>tBQMJDR>&S5Sk5%dbLxb2bUo&IFC>wd@iUXw4ST_$RH_$@FiBB_>Kz%@UFzjn+VKXEPCV+giN8t6Hx8h`v%p#B9V+!j_moWU>k2e{>ncv@( zdzjv`L*-4+{b2QZKM>-%a2rAHA&p* zJby_E*9XLR7)Q+)pn6mQv)iI?4z-gMnyz zqDW3NJUxa^`L&xYk6ONz3qtheO%y^YmEZz4RB`Ljs123Po< ze^@*FCv9PL2&(dS?(XGuI<$e_->kGuS(5=gna^#teKZw~*v(!lmIPFJG#O( z3?x?9hjLCIsj@!5(G>WC%j!|r^@)%NNApOhGY5GE)D)|J!6HBak$eAhq?*g`6{lMY z?6XgnQOe!_C_kg(C+U@fUL?TH6D(-@$;iG2>b^bK<9uVH=`g44flr0;TiOe**D003 z-yAe~WLUAZU~Fl~EFJjz`zPIIURdk)zmVy+x9LKSOFvI;lGG@caK5qPNQ9axgoHJ2 z0E9{u{>X;jFN&+p3+AjDxCreDX3hLsZX}jErLvU%1k2s9_%&G|eO~UCq8KcCUYW#N z{5iAtU*iUIeXuvcCe)&NDRg;;u}(|A)~n&(tN7r&Ye{=c;fc|k&ODonnvI?w z^kJ^mg12DSotd&D8fKqbImqL7a(`G|24-I{PBxo2-8td?{sknaTA-rSO{-IO@FOLqN7kjGZ;wJ|W-hDgcNcx9G zicSLSG6Exnba;+w^(3(7e~9i+%NJ=9AF&l7j}v_(uhnnXBbiy`br}jYt}sxHZk8%q z^^6F~G3ZSdWk1fg*#ETky>gsT4gMQ1hVU#s$!DIeC5AIYrr+r(qakvIFSdpL3^4U- z#Mq%OA4^xa90Sc4wzX z@(l-#uj(o%%n?`}rV)cp8^?@W?qi;!tTnyE{8{7He|f3qh<7Hck&qg*tE(h!Ef{?Z zTQ`(5{cLGztx4wULvO7XwEGJEagVC`426#O((wpQga5IRO{%=GF9>w&nkIjD}`xE36nZTr^f#Yp11 z1v1*?p^^ZvsSz2C8g_Lpo*e6Zyaw^Mb|k?k?JM6tZw0qF`flI?n;bEeGPDDsj7k66l*~x>cz;>ET540RChrxV8nt3naE@PVnHEq!;Y4J==T zb9wM*U#@{C4lWxhpY+F}rofsL8#RK0FZK&I~;*%1LSXsLCuSA-W`?QOB&z@ZLHfQ7M}` zHmf>-RTM;vd)2a$|J|1v z5+%hKDHIw_ikEKKQH`0(KpN0>VRxl%t6Za`hTH$GmW(ZvF7a>E^{S4>kDfXmnvi)!+PIN!e zrYqq_bqC!qzphH1v3wzGDR=g>l$c=-6!8Wsyc3wQG__bC2QwvnKY|=qJ-M9tDJdgs8LG55-*w;D|pAT zgT%nc*IC1km!5>XLg5Cx8x3l2QjP6*UXH12o&MuRDa?Khw~0au$sKk5#pVTMH?%Fv zQuS|=x>$Dnd-$7t)0Spat?WRNVox+f>uapAE?4)`Bwj=RtX4YXtV~VreAkTNT6E~M zEY;3)oe>?f(j84hjm^3?4JHkYU_`3A>O$CR)N_*qwYHj^T+0wzrZrm#8`5_kz zk!3DY!g*r*^;8$tdT^W)Y^Jmzm1OcanihkUPJUKXzjCWQi-V}8$oxotV_!xub?}k*0_GA! zYL;)C>ws)#O|?BakKi9dH7*dRIG(%sL`#x)IDYM1TP4QMOD7SBrv4$<^q}W0JdBZZ zke^g(Y5D{_>|12FW=6h9F}e+Ok~f{rA$5PXyc!9s<@Z|QxAqOK_#~X;*PmgQnzdQA z@*S$hrYxcwZ%v$cc1cSJSjh+;6|twfL#J*dmEda*iOtGUq|g;?=1FY0BlI+8Ulx|@ zg(!|kgg_=#;fU&j@WZy$PY|V{&UJwUbLP`wbIhw6cV^zWn(GdD?%cAqm1>Anu&-Di zN$yM%qsuyJEsMt2w37X3q0e_}6=I&3v^@^~)t2{3E2tN&OimEr!{gIw8=K%!4)29P zzQuw0fLsgEF4ahzx9V~uckI;1XX7(^v~>=37Z<9RQGu&c<_OlWZRx7lh|Rn|mTJkq zTT#ijwDXNsGzS|3acY?)Rggu}vK)1_{AGwp=8N;Kw7A~p?If`D&Ubd9;!)FJc(7$e zO4}+FGB?I8#K|-~wjZQt`{meDCLf*{C=xn65}H|(wQ zx+GQ4rfsnFx5+8))R>}!#Xk9GVAkogc<i$nNI9K~BT$I8%$pRWr$v50*&vNxpQm}LY zdtUWSk{h+_iOKn6`Pf-9mKOPdW=Ye={gOyRj z%H4bH>)?zNlOrb9pFiICj`!X1bzKfk3e z=P(q7GZk9(ji9Qr5^cNmk-@V5xpJL2tGmP$t-V`{z(IaJ(woK-?Kk0hoN%Y9-&%eB zdh5x*W?Icl9Tj89(r&MRpu^gc!(`kc%~EzQQkIPdT<@`#Z0>eG+Uw+y-eQepzTD zY8YW;&_?g`DvpDX?gBslnpeJ!;?+%v{E`U&2)pq76O+tMgVd8iqz>lW>Ki$oPWvWj zwV^G_&-Okqo9A~vHslHibzo%Hv9j^OtUkT;S(v8BYR~EZ_0i3j{EB?~)Dm}{2;P6) zV?D0$mM>(E^8!m(_<=6Me|`K^$a{T&^OW%-icb%(EcHJ8o-@M53Iv1M7T&=<;6_gLx8*y*mwO4LWefhzr9%_dV*3&pUDGqDEj)M)OXLZDN0@>!r zc`_^|US(XD=&xF+QOpej_l2!AY@i4^Hdc<4O+ z562kwP#99>9-Qt%QR)TqdgQyOysm%8T;p(bE;nffI0_!fZlDtfd$~-Ki#1mcykOAp zYR%mqjZge{aFQ8yT+x>QDqlMK^_g5n0v%p0j(zk9^ASaC#YXtsg$HJUOm1SmJZd=D zU+%4lMz&0A0#&8&FM3Bx>!j%d6Sxbi8N)@rrku~kmBQijzWwZb)blFTcOORS*E^Dv zg+?Q}plZl%me>3WI|=>EdHcB(E`W(jKM|gd?1ORTR78q|(>*VXM$>hgVK()9ARD>Q1KHCka`fDu{xM-* zKS(~1x|)pi8%hX9(1SWb4+yx z#dw@%7|U(>$UI4!4-g8|b?*~}U?#u~V&Am0tdx5H6;rK-OD$l+&YtMR4I4mz{X1r7 z;z`gZ%ObH>{{H=)J)i#sSJ&)(8wf+aeJ51Vqo8N7$UMJe09lY6CQhE#6g?gt>YEUJ zV?st7i&Lbl>X+YM*?g2aEm|A32OX_j(Rgc6KjHO}&df#Mj*(GVbEna^4Xei_C%pF; zE=|*PG@UGx)mi?-snruo#cqmNmE+NeCR2-Rm>;_ z6w$#QVLV;k|Is_YE1Jv|{!KI$Xrb+@X73?z=HbCAi#3ELV!}7f z$%QA%2V9&A72o3|>?%fkgc$-i+}sou;{=A73OtRql>z-F9jha8{oO91 zXJjYY~zv{;?dRt z12<8&t`sejsZWNb$!l$F){kU+gJ}>G9!_`66%veC5`o)hv|qz>aaf<;4t=r%3O?pt z%Ne7;C^g?fWfcMtEhgwpd}E>Z+?AiFhTA(5IHL$}6n+1|ly)Z@-gDke>vgop+NsH; zUE;JTgyX(=Dy8G#s$+ZE^zkA&euLrOUccbOIEMhvP9uO-!D@8k?|(Stk`Duhid}E` z%CQU##eD#>UWCXHoq<$Pyw;6T{Uy)unmd#KmV|t9a62KJV)et#rhLo?Bh{@a5=6+a zBg>qsv1@b4{J$Nqrz`}=oq6m#eqWDy2p^|hq?mC5nvP&tN5Yv+;myNRw|=J9k2JPN zA4z|FuO}}dqqd?#UysRp$DRJ07R5AG*Lx&K`3Iei?0bB7#Kn8IJgoH0+CHG6h`xaf zYw)r>LI+C4y`X#*c68fubHC40cb^I0{)cGrUrSZdhD8nrG|UwM63F9UQ^^+RmO2V` zx6k#8Ih@J7L$MZn_ANTN_pi-=c^DTfAo-N=8E5MmH-78-v2~rt2B#jW=2pTE1QNAstN(7{fzlu?9TR# zBRO!n3c47`DN>!JVav#wM7-`|KV$LK1S_B>sf%QnruoO%rGAO=ElPqcO1p|DCglx& z;C8S$x|kW#efD9*@!cqHsqcHmz-oUi1!33p;npIpVT{ zH?gnxefSp$d;fW`bIQsi$Z{V*g_A!{*abG;O|4dta2ft#JXiiiE-N=}$fi4KpgDfk{-#Wu}`+LcF^Ii3@n18)MM<6kPBw8X0L~ZM#}?w z8o>OjwOV=zRUn{J=g)%`70cvH`K_d#02@0y)Dk2IL4E^G0+6+pzRlnXBGCQqV;oJ% znZkbA1=u-daDQO#n}=vub}UM|``?Bm;&RLE+)c4#lCneaoz{;; z(W{x@@U2fP4_q;)mZ1ZQy=ILFKxo9}KOm^AB}|;WfvT5>71O4cgelUKFVX>BhSnQj zc1OixUConBZw^4h^LHl$KU%k^Ukq=Eo`v5k#qBKF{`@Hv*ZpJ~@HoqBRXcI_ye#g; z*UYWsm#ll;cY0)44W`I<29ND^A-HPjHjU>ib-(J6zDc!k^39{;QO^jrmu2e%H%W*w zpntM?qCZH{S`FjpjDsMLS;n6c439l=%HgCjr_nJ<9w`z{AIVh|i+DHLU^9Fb!V4~U z1_#BSBP4oAw{W97k>9|fC-NPC7A|3%)uqN~VE2j1+`5jcVw8@K(ja|i__O4(YuPr$ z`nuKHx@BVX-*Og_P5ao!mC=zlrR$8KbgfIaCqo;g9T&Yqg0o?@D~AYH%a6H9anm2q zCb&bm%)q)n-!nCsu9``c_2CY`Dp2#b&lYw?wHO_RivW`eYqjpf?4`tXeZD(UY-`Ia z2YL-HkXEpB3UL{Q?Yh>;EZ_Qxi6-{KV-m)Us6VK-J9;4T8Qosc%eB$%-cd%yr$`Bj zaA+5e>o5JS>MuIR$S}3ETQRm!^B3q_fy;fy`IIYgKTJqv*MVOQKhQ49yHF;1pSrf8 zb{kWdAVG$!y$dWz=@B;r9(8x6h&M!Z6JUSiB@C~>)WZiKkrOW(ja}DN9t#?yTr+h7 za~j-1I{L~waEx_1$2O{#Z}@nRmfXKS%asfq%M< z0{Pu2I$V+V2r(iZ+R*)(ITa!uETQuVq^Xik5&dF}Xg2Qmx%hmWyneoD0dUL45Mi|< zdG884sE)SalHh*hGdJ-0h>*L}{;Szi;8~gKA zXV4s)w_JG;Q-Q*;TRLo<3e_ixJy?HW-BpFv>mn)M?MTAF&d!y#w^J_>FLLl%i$0pk zY(-K0vFtUQMp~`@Y6AkwR$0W^&wS;wEnL@EcX=_eTa6K~AN`d((i8Bv5$m5C-6qGCSMVl3zP|vcA!ki`j5u zhvNvei=Xp>x!Etr&_&zMsNCVOKjr4Tz0gI3sX_Fv*E0EdKhdMo_U@&|(vE1MeIcN? zFNpm$k7~f^o^{5|m7}Kog<93QhpbzCKn%?|QG8ifk~Dr6Kf*=gYuYkDU3E)cML~oS zmZ~$L7AvgFKCLW0_fU+|d$SmzM`?$};2Xeg_u$%d@=Eko6V)FrY|7K9A>IUPNX*wT zSZ-`2{VFl%diO8r5}7F1-@!a6`^y6l4BU^_qP!~MG={>L-N9Km*DS9jTI`(v8~S=wj) zso0q;&Jlx*z6b33$&;Ev;iPUT0R&NW#a!%hFP&v1!sHHx8oaY+@QG899s9#s{*)|uWv<TF{nb~m^bd_s4tJ>oN3V{`EIE7geBpsru_NAaPx}@+@M6IM4JrdCzdNH$3g@{t4gH@Xk}V)z64s7l z^lY=>ZqLz!>NY|!KT7zODaidgnDt@_24W#UM5RJ7sz@6}7>bylp(9N@QJMIovrwy0 zCdJtdF55u{dUlN7k{C5pP*e34nbXHa6VdyAHT)A3!N01)T(bis~v}^%p8_6r>3EvgZc<&%jx}OZGycz{HoZ&H{e5g<}!Z{w~I|Li18@@r+Mg%bbmYWv}=LP zl{%u(Yk;uHDcR<+I!ZCQ`RJMf&fJld`a5U9X>eZ(v8k$* z{2Q&}Q1_EN%dD$dFULKD8q?lGEyih|Z8YX@4N9v;vdO6w4Ce#`C<^6%ASroysPf`i zmFEe;*SImzxUj`mkM5=ZET^*7b?gYT$@F~lw+fF@=?~{msYkOsc+sKQPjt39;GV&I ze9!*OcLb)41Y&YmivUZmoLmcOG6& z*HHov8?w|(!Dwy|umWViW?23?-?UN_J+}EIHIUD!Lo-2J@|*GZJG@1#38*)4C)Lcb zeese_$2E-WdD06I&c~47OfqkuIC*hC>0~VeDvD8~%Q&bfkY_dYOTW`6F5dZmruuk3 zqAb#Ho+5x?@WMIP|6}Gc{SS!AD<5@OY-YUFJHsZ$kJ>lmF?iJ;N{^9em9Q3Jmw{yc z&bAka^v@drjvD3_(P|d^AzGivv~YL=_n9M^e_;s2-=J1?7IzoDrSxdq^Zezby6;FE zt^q8{M;%jK6K2cfC!*+q3z8?4ZLNQJ@JN^@c>(tRV2UmxIxX0Gh|>3 zaW96ZwmJ&cK&$VBt%3xHF8@ty4Z+5)_SS8GzX5y2w0bFezU|R{IZn6ap-723DMp=I zpq6>0G#Y8AU?EZ`^;6p#S8sNFP}U^GTvW+QZ^W zlBR(h@%P`)EmTCYM!F2s@dV6kM1LdlaeTCmx>%3AvtI~ zyq$R07SvGT^A*l*!X6`!Rt$PRz@_%+&mMzK>Hd_{RZ4d>E2evI|La?E0Q!h-+a&fs z`!Mt8_t?MRkAgmt2f$-8H3I-&(H^ZI3-2UD-(kBvd5@1gV8MuPclxu#318MCJ;czP-|2TX&9CS?keKN>)D$=wQ~ zZu{#QlaK%r``l}fSbX=USOe+89%I}zS+lQy;ApD@^br%Q1j zc`D>he$bCQhk4UH9fe(FCPXf_Yr8s84PSYGW{Rm5>$-bpNF)!R>mDDbe2k8hT9g;5 zl+<(EUbTu)eo6Hc`LGKr68fxc% zAa7&zs~GL{3GahO>6~?>1pvH7B@6w+ECPu|8uy)7FGgLBhpN-J1UePK^#LB*NNgFC zs=NuKT;;v(J_KNgEBPv1Z~I=!bWzMRU|QeqjjVdG6V}ZDv$dOIUn!DY-y_3rNo2Qv z|7&PRvy>sw!z20rK%8L5l^iqi%vXP@7^iWYy`M^ z2U4@~92d#RD?KlX<5lZi-D_WbO%hR(siwL2&FQQ<_34smV9t`xD0Wd|x)AbfRMMN4 z<8WB*Q2+D8la#b2#UW@;_n{rkr?=7XFwmA2Esos8K4loNu4cB&mP zY|Hb8hYUOOH^}@6s3c*e<1eDcHPzWK<-Hnp2UYJ^+!R<<-dXy%zQVyP*A| zIuD`lNF+SY|6u{uSl$E#mNmz3cT+D{4y`}^t9&-qIXw2;Tjl%A{EIYT>wH#{Ub!A- zhB>g5Njm2m6Qsb3kw+Dk)*?3ln00@db1X1zA4v11?gmIEz|7pGYi!zt-s3$oj`_L- zmKWJt#@ODnXNA-g2kKF1)-P)dy=G^)qy5(F-<+dT`xrupQ1dreLKUOG8Jy{6Ar+#G zbJXUO6Go^<>FJH`7Fq<<+#a58ot);w;fliOc5hZ2aQ(1h2 zhBfw0NdsM-sNceluB$dG+V>P_Yw^CqPNV-Nqc3NP4)Ze+6Hc{{ zyP|f4NgM`$)X{J7orzQ@GK|r~lPMZ$K5KHv@uce4Do67gu+LXS_xQYd)nx8aS989p zErphgvY|k9A}DmjyVsS*F%UT=oTClOg??)AwVAfa+s*9g4TOv2IMX4`2E0gdeJ)uRl}J=eU=$alU0Ugf-11L+t%M>|sY(Bd&l3b2~o(GN&!(D=Kr zn$J>C9Fh*{{7T9M#oukQ^sq(mVdVl=nq1Im!c=1dlPUL}==YCeKFOJJlRYKi^fS-l zUo6tGk$r*C(~06h6H56?UPr_A2C-(z{dk95Q?v!L5zn}@qdi`i9^SHUEwD;k`a^(; zu3}wmT_2a}=)<(bk)smYM3K7Rd`XlKCdCtsx94IGO2EJ5r18D1yJo&B*`V!41w!ai zh|OtM_>+%($T!qKxg32VPl^ak7XzQ2_P(eoWqU5)db3c- z^lGB9nPb?m*3seT=MKmKUFjdojyTWD(?m;(loQW}f?-p$4n6{QCt>L~N{Y+?X* z&m?mdagyZ%ba_1HpW#`6M+KIP*9RY){yXIWZ)(B30*}Zwxq7-&p9WgF;}ba9Pu!&z znG34DdHgA)CYAXs+zN<|Pc2OP?b;?o;i_of+xz{}=Aq`t%S`f04Tr(!z_yJe`k;BM zgq+1Fm#WJUyk|}WjGWZzmP1R>O=8tMg+KIH^p+pZZu`Iav;FP|b@bR7Nahw2Y`U-hLrDS(0`hVAF=I_)X(HO5H2VtQ+u!4O?oiH_Vj`R9IZ$r@Tu zB46EO{uBy~Pb7U>>d9ro8(QS?FN)|toNWQvu>;yaN_N@iAm}ztN-nG*@zcyyr4rH( zB%|D?)dbAXLdqmV^=RFTxZz?SvQ%6&Y+;Cv4b^zuclH(b631IYkHp7bNvR|ClHAVr zlS*MvrjlSkXM_ZTL7Hq&Cng)-==+5V;26sW8fk_+V8!aGc=sYRsL`|kUTNZfzi}1A zGcQaTeND(_C#PdzPTR}uEDg|5AHR02QAI+%6>uWoJRZZ{I$>28{m z)J};s?Yy_OP%32myJ|`-j1}QTsZ6^2xK>2fguPzmMy4G$5&~Xd_|oNUEMsDJr*<_R ziZtaInk#|p;K?zWb}B$eXl@?eHzBn>f!-}@%&485cV4U2r=AuC!D{8kh5inVBr)>O z`O0|pPV-GsI<|NQ-zHq37nPeVY4JN+`(DjP(OlH6tv4`}S9wbHn?~!2#dD{ueWQ(P z)JEOUV&c!z*bqOn0@o=qH==Z0c@g~PPKLt(l|JR;h@Tpc z-}n1HOxf#gFW?(?p{94md;G~|*=0SEq97o#OLBF~huI8f zdcjXBvKL<4DL6w()0JFN_)AZvD>5njm|_L#~$Z9BU`=J-FT$TV!#)OcB&Dm z;rXcdm(rwX`HbSbV;4^onauETT3Jci>AfE~Cd&1i=G(DyWpH4(bQQe0ef=?#4IKYE z?%}~k3l_E_+}y~y=*i#B$Jv<|IpacAPCqt5ctY-_iCP_u4cScd|Uys*d3$P4FSdBP%x8ZCAq^QK3u62^Cg>b?N zJZ5j$|6}YgbnkiVk@q9C8L_{|;OqGc5^feT= zY-#dz6B0bfx?J~p7b69lNDE!iToa!!{8{v@p)sYX>%hiKo#|dGYEa2X$yo_!r#Kfo z|6RylipaLZuJH$z;x<>Fe8>AgoRAf|^DpU0?WJO!_y<-j6GT(nJpU^!7n>NP@AwbL zYx5SjDW|JZ>_JV$`(b_HpG8wncWV^3ocAF!EnIBq-fnLQ+Z(nk`~Ojc*#GT1!(agj zO8+hWKdKT7``=Y!1Cv$Q-nB8rK;fMpDLUwSdmy3LDFR8?vKSEDL2bq8Z2tgoh!d=E zIr|VTzA;cnxw`Y9gUyeg91Pt0wKwm>hgCG&P;uXgl5$8SQv-NZc`f8ebDnBxv#1@a zJmZgMWJh-Ir^j)SJk#vyaB&IR$QR3)`;58|A7`zZ)7I$HoTwQSx3L28Ky4yO&5B9G zy{=#V@vh4uGs?rLGd6zj05HGx3qt z;CNBTaryzxjLxoy$qn~aLK=h}+kc%lL3umU$-e(^9@T-}_xLO4nw{7 z|Ag#SZ?QS)p`yWS8)`7>8*=}*J&%gXK~dX^zc4IYAgqsJ z3G%~xpO5K7hY?QRCJU|$H?PSXFL79p5;aBx6`UB(#2?+{#T`v@3=;lb#8W)&-jE>f zV3E(OB(P?J>s8I4KOH|7N!S4oP@=K2uCPo?b-6D(uX{(nTa!LP@{$OTBzv7vW!8FY zX*RJS3o4k?U$ldmo$O25-cI@Kd1fAT+-+gb zq=_fTy(y8{bVYwkHhje{cffL_1vT~|cuS8S9dCHX6J%K(&!0w2;Ec7DRDx98E&UImjw1qZ+oa|HCQ%cL#)b zZ5%2+!n^=(*UDSnLw)JuG2B7d#o~q&^}QtiJkA6YiH<(saf?=(IoeS=qo-+3sIRh( zDf<1Nx=Z>@^6cGdme*di^;?UZyuxu(5UNq+LpgW%=0jmM7YztcORLqkpPbJ}z1mk> zI=QCYCB9$&YqT*N;~;N_hDEsP#BFMJ#%L9&E4f)!nL%^bR>_9|JU1C62(?#7`B6vQ zI#AteyH>GNtLo}W6+LuZ<^UKE|6+OKcNuv?A zw-YrC)GcJVi~@e-Uh80PwYFcqSe_=*yx}sPaQ(3X>99~mTHB3tCKc+pLkQ!kjat@@ z9xmX@_n>2s+;U6~s2v!6_^jP^G;cfW+J(f&e7~^5M^(RIqzJda|15Q{Ue>lb)ivY< z3EnkMGX3DZKro8dCX{4^LH3 z?d9d1MB?a@H1tHi-z>k5=VvYHWJZ2pJ-YaN_g;)c*&B-AYlu zoc3yk&E*Kl!9ueiLmbUKI-ezK_BLuSq$#n&T~XrVvXS%vBVLY3$OK4f_AkmGYFL7eKP(hkzm+Mw-PKaS2c;>)iCIz>o;s0>9{)F7zGFt8xZuNLacQYg$ zH#b(pp~r?`vb0=BZSe6tq5BT$+qccwz8Uh2_q|KfsEFT1@=z2u4qh4;AtgoGUXtfv z<&s#H37~)1`M-GktG714H;NOcg%*e6E=7yGTPek@KyiXwNP|o80>vE)6ff>jB)Gd% zyf_I~Ja~}uo&4T+=9+8%fSH3FJUM)>z3*r5d#(GkIAhe&q+B`e!$c1_9sxGl{$h(2ch7 zK3Q3&zbqA6{BD#Kp_YDErzR_acz2Ny58vSZJm2%{WT~G^+&cAS%DJP3**81qj6i|z zX$dp34C(dr&~G?`Hwy(9TlzG@B8JO87P%QtEffF!3sH)4xd>mhL~560wv>O$dC*3+ zE&|m}ZgUg&2Uy%KpbALOUx!Typ9vJ9dNB@AW=*R!o$&0|=vBlY3}toW(4k2he67zC z_h@idhj`d{0>Nf*Ak5~P)&LhRQf7IYMglAuNNtBUYFao~YETyvAzqZ!W3|Y0!PM5cgH?r{Hed?S%Yp@%zB6v?(8!aslds~IC3)b96*N`+!nqd6a z51y(37hz5cq+X#fLqSgFJV-CEa$_eGJ7{;RNM43)lI%3^50Pp_r?{1HO2BxnT5^Kl zuG`1fl$k8yBdx8V5Yp6v8_0Gwy7+~s`e%a?Z()X&DKHDN%O)7#DV6uAvK>>p$%tT`u0Uld z=%!08ToibBwm|lblH)@41$^xQ-r^W`Q8!ciTI}B6^(Aw?c6|<=^GaP2L!~7*Ida!Kj(Zi8TBq0yjNKQSa(`M1V3F z)@rY*b$OagMl8Xz>UYSFSoh0wmztdznDKK@wu zB$?iGDbwyLm=S)7g7oB|>cnXL>*XMp1zv9O^N6r(P5UYfm}L%|taI8vK_=mP~jMC8(q z_@o1H>)n`poW;}>?xpvS&{4>!XwwC zX15{Ni%8`dOA`+3G&NP4ukx$oD&mX})VJl2+77?~^(&M!O7=rb!);4q|JJrwOECV{ z=f!K4r?=&b9+nRnlJOhYI_nxK6C&%s3yeZDF>xxDe!s>RiM50IW1_VFguM%;_AJrH z?cY_m!JLK$Zf>MfKPq#oTo}>hF)=RGqyuYl_aMRm(RuYmjYKto=9N?cXGowL-50(KwbQn(_NKgn}@BzTz7{)n79 z2=0CeVqjkIa?OZs0X>*n4F2gtSj1kV5a(;i)sMzR@RrouXhn*#A!q+fQZkc z?*eu<^@?}D_kNdH zigPzEDMVfx-duM2udEBPg*Ze>{*wpgh|avT+vlFz5i7od!?Pe3WJlj7+2Zo2d1*1> zm^-CSA4ok%w2ke@^{Jef;~l;=+8D@Jbpi6P^LMh>z%sAPWV4!UZHdh%Kn$6j!=thq zueFR4jBpMJlrKLvjH^Vm1+6quurdu4o?@A#&AkQG0D~=?vG%*dwLERY)L4 zRpDXQcZB&=u>G>Sz$57$_TISl3g;PKLGY6Ep8T=V2k|sq-ypH)mZzybz1O>Y&OPBh zGr_$F2dTaBh-1wQ)`ZL|g^DA?)WahS-T3WzSiI@fJDxDXz3zS3h~M9n021Heoi2*L z^ekw?l&J zH~Mc6Han_jXpV@HVJgel`2>eq8V{^F>2H3RpK0D+V$ztYOC{jN$x}+))>O<^kmu*a zCZ4VIh$6E=cFtRX`!IqjZ8$ve3`W_i>Rw} zES-&=*hBT)@Z6Q|bj@Fya~SB`rRas$ilfn98Fy1(7*xRwI=knEJ4h-3Q!HarBE9@g zu}$$+?~LEmVe}z_%ET|>Z6=q29si*veTMM5czHeXJlmU5`$Qf5hFxIKj~~wcOqynm zI!zSkX$;y))Ua>Qk4Di!q^W>DRck9-isaeBE-o}1W-Pyt+*I#=C=(a0?FRH2NB5!f zggwaHd*XOJ#RFvMdpz3%1n#ZT1}4^Wb|?=|P4d_;6t`lEJJhGB$H!&N&uFF$)Ri*r zyHn`h1C~?7Tj;gZV+8$dVpf~DnyXQ#^o%El%hPb`A9h~DDnX4f2qz(<4F(5R2?on&p z=YstSr%KruzmG44UvyZR>VPc2v9bUh#DEfjbK}$02!Myqc6X*0X;UlL5@2b*Vr0yY zSwl;NQ>yWPp0ODasM{v@(OMcR)SkXOi5DHd2ry-HhGgtn`L! z7k@oJ)Djcx0+u&Q{zKCd0duL2!3$gV!T3OU@cyw11{EUjDoJ6hkZ}QS_c}k#@ZtFGcV!U?7 zr~5ihKgPxN(zYY<0cwz$5e8tw{-a5@u2EY5fcsG4M1cm()&0dz)}dQ%&b{C1o#%bd zQ7O&(zNw3g#icL}th*@CtE@YmZYj=u9De?(sVntSbvuDW_nn(F}@pP4f^K*)u zz7;|)s;BYBKUZm=OZ7T-xDm2MRmOdkXK8=AaIf+?F%u!jIgL1o7OBI5cm;?6!QgVE z26ft4*Lmh9-8Anwb&TZp@?^5%B~0eu$?C8=Qm1Z`*3L!YF3!OtLp|YHRM5_2gx$*j zwY`UfsrOt5hs1}jpIi?1F2&Nmu>9L^e}2uK-X3El`tux@$?@XY)P24G@<(%USMt42 z4Z|CgzuNFROjRpdbK1rW`dIvpx#9UK9F1k6LBPj&=D12ewB)=6O*pP)2(Bxqb+-7|mScQ(X@bnHN;`!?CyYilpf z(4H|If5v2UF=sw~?3w=ViLf==U_c39q4Iz6q7Du&Fiobdo!2EWtU5UuBVs>hAGGos z33w}?uJjl;QtC|*Q5hhCw1X2y=TUZFltrF5ol>*6c}8j{O^;N5t#5P@pD4f2mJrDY z!_}j@nH4X{!E76Bc!?e^V3zut`4`ZQxOlcDyW=KQ%A$P@L~JDQErKqmUHMxzW28ET z{XW)tZY}$JbBUKt$@gmO!xC8HwZV3#X@G7}kQgcmJsxI?O4#V8scYZbx9x)^l51{r z$fP*O`M@vLfdO$w1+cHag8Rz^hToPu61UT={B^T;K?6UvtHf0a^1F%>DF zCW{ja?96=PaV7radCu#F(H4@;=;1Jfl_n|=PIk@Sqv#N}X~I@%yChP?0CbTuPZ z1YeCuNCUNBy0c3+a0i^J;fi@yRB~041lZ6nrDq`SEV>qJrDdv)(oIAzQ6y{!)7X%= z6hikw&J<25Jsb@Fv^k$q+ySB|#n)v%<)RtZxATX`r6mO^$4(zoqC-gTw;Xvo-8CYa z6Vf_hd<I11iFL4`dcM;mwahOcsgY3IqtV{jXq?yb55 zm*f(07|ZJMJc<}2G?UTa&5hciIE3`yE!b4>x03hNr+#IOUrBFt8~C7Ko^7D`sWoMg zF!{zron_%Gko`%F?NwoL&pb}8>2>u4z-CY~F%;#a$2q!nC2xr9BOCJ_ob9z0#Cob{ z4F5F<%Mx6_sfo8RywHx~PSsfU>Zm$qeYg3s?)zl0Pn>rRP+I9Z(4WdWs64kQs0Tri z+WR0EeiKaPR8cA=zNcMz`jq5GmFedw9CCb%mOb(6I5m> zsE6*uoWslGSE9MkpHt4tTx@k^xWc|3dOz+hcn(zF1AkZv`3m^{d@B9&ayqQNibHO* z9GYEvp%`}w>7P6@DdPcpVRS33<)GD{a*qFw3!Ho@#a}-3sbaE?X8xJ*{u#aB+OZs} z!xOw6oB2#^!42Z2hvOUj0X+rOm(xclvWDU+i+tI6IM1a$uc9Wc;3 zl5b*NRQ_EJDkCqjs^V@z4WXrSt*&942pi+|5|03H4o$tyg|Y_Jp@^y{jM_vw`pUq8})nEQ8jZm88xbZ+r$Ls$viK#uPEr`o-Z*1GK!MRH!ap5T!T6hzf?jkrFC z@=?~u&u=KMq@g%)wigZZwk)>WOE-7%Pl`U}hs(F#qST)0KC$Iy!q{)c?m$G5lQ~kq z9^(eEQ1}%gGu_*#=?zq7UVg-LkaCeGtS#_tE52vOg<1Emmtj@xff}i-7x6IL)OKE1 z_cI3m8Jzkf<4w4r!_WS@E{`gh3ypH+(JIyatD)gU@dyKBn>gZeIu&BUyw72AZojWg zFo##ix0G2EB1?e}W(I=5|Ik*JhL6N;BUGj5OytFKDCHQdVn*jhw#W}XjwmEWD|q8X zs6I9*IG(&A`}EUpLZFw$-qd)SIO!NT4VlQ-|$MS!$afm&jLZ@(Qp+;dAG~ zshqa_WAVv-256y_LHdiR_eLH7C_6IAYiO5EU#1RUHw02r5Dhsa`WfGcumQ&35HPsT zh~)8#nAK_5*W!4RiB>vK^B!o=X2Xmw*8Lt7tCcHxLQsd27B*2E`qS2pwCU47BXmyB zP?R;7#BoVdtAc#uuZa5h#uqjzbS=bhlRr!Mk@cZHjjP<%I?tY+O0)aGRI6+Fm^!-w zigp=O#yqtd%mJo!@)*Qet^=!G%AYPAVxJ&iYN$7tl|zSb>Evr`s6C|C6^Np}?;9i; zSN)hmvaYW10)92^Mb&UAqQocn_Q!K1{j+@1e`rqd!)<=y^)jyFjOnRcRg=?`T92&Y zaFMNMVUxw4Kg9erujX{`WiZnxF|}^yk0pyuSjfG-3qIb>mlUJ@n`I8|ZI-gM&xOyB z(Xp-6^4f!{4wc9D1?+8(2CvNJ9pIn8*%~+EkuP08eN3CbyvJ8BNc|+^Z_nw$EbVQb zwBOb@>kIDYX!b=)E7f@xg<>Of(HE%r(pf#i1l0c`REkwQ7Bvt-zX4Bm*On=QU|`va9VUofjWOz5I!1e&Ol92T#$p;1!LyG>!{{tA@d!{}I9c7q<|Ue2p_zif66AOc z-Bz)Ccee?h_5(zfqG2;AGl#(FrPHm)+E5c=T8V3Kv$mF${dX&Uy_P$Rt@(Xr=+A>d zSyt3VBzcw)3t1ukGEW>(L%agL2piAf0nuRiN}qOrVnlgz!6S3|6%aqLY#`hGqxUdC z*(hP!@kHP5idvfW*!ia;5NhaOh(^2 z`w0yjIfTi#cccy)$g-r1ch8m%fk@R}tSXj@I*-kWAG~h2qMr795_=LsIq^KyjO=hr z&P^{+U$SjKhsYqRKgU^j(N8STY%N`j!VrfTB6}(#mmSxr1iMdElsR_(^G}9|6ad@K zsULmgzOgCrR@0p&Mm-b%Wc=AxKLlGPEva^&a>nm+uzkTsmL-diSK>+QQf7d6Pau=^ zN;_S_*7ABx+EBFwId3>?J`F;Z^rK^~rkJ@^xIwGxC&ktX8`5%?F?Y3AyBYCnc6Flh z+?$5So1U9z<&RueWIGdEXLDQ2_h~#&V!^(!Xz1Qz_0P^o?R@mwt7c2(B1ULSH#$W2ZGKJT= zIDZ$D#O|rLk;_B-9>t@zR9P@j#1Es3kOL#PGsW7zaGL0dY!=>n&BsyYjFHJ4Vu9qi z{ibhKZP3HRU`b6&W)e4m%Ryyw51MYLh0w~(h~ z#<5YM(I`Y?w9&}pSx6jdT47tW}cZx9Ftd_M5%jj_ZAE)=z4z8Q>3Zh-@Q8wn-|&h^GY>dsi_~P_(}Cv*-}uo z_fF)m-}w+B?QsS<326fqEP5u@nk5{#wlj)$=FX3b7|J@Yu5?#U4eALQ_;yP>f7{`C zgG-1mOfNet%?j_*|I&2o1=#K&zZ#y|6Hu0?`RVmG5~CJtzm11bQBxN+Ry6^a5*NFAj-1&~3Zj9$h%-=EbQUacKlYMXjPK-TmdNk#@Ug9I=ydzPXoN8+kz8pnL{G~*b zv~Ji<@#U6QC^0o!{bLM?fv}}x1uPk9c4VT(jh_6mXP1h;9dcI^k7c`SM1}p0{{4=~ z0P^tmn5G=37il4KWSLtQctj|Ni@A&Wt0y?qKTL%FQBp#e5yS`gM4V9!t_FWIG-?1_ zU#b*kEkx>lu?LI;M*p}WmoLF;n>+_YTM%J%Rdiv2m3 z1@wi53trhZdU}nEXV(A`D5Zwd{`r?-^*-Vg9nMTqJ~}nd)Mbep9%F6Ix`*K0$@O1n z*r>@jp9jpSru!$My4pw&x7#A7#@GCs&I2}N5~^vYRJ25WwQIeS%HN&9hNW9ax%(I{?NY6mBIJz2jjW zOi>xkH|YI~R?gVn(a^kbTBA?t)U5kw+>9h@kJG3Y>%LnjZn2*XfUV@R>C^Jp_TYoa z4olYt-$U}>P11{3=OKIn4pzb*idJ|ni8wo9%JkDfo}pT_@J7Cg6Ax9uqwgqNy|Y=Z~mpk_Sgn;t3Q?U%h-dtkVujrXdYFBkq) z@0dsxEI)D?bpb!gr#>QI)z7W10G6HoP?KF|?Ip(g{cio+$h0ZtOpW!}j*Jncq|@+q zraekr`!Ig5UrTDi_^F_T>z%D7Dc+!7iO~Ct+LE7u^^NZEQYUrR6UlGn%NCB1Q|F(6 z3MT&VDkCnK{`@lc<=?o5-qpt5pl-E2#ASxKqPr*qLddiKGbS1EEp5;btX{GT}_Rckg+R{K{s?$L6qPQhH@dEjqk}0u08O3)vz<69`;cR6+)uFe-;v`uTr739pMPt*zpL?bx zy4IOFdaZ>!jy`l1fcn>D`ORc>ekkGdP> zJaZroTdFq@n{>^NMM+!y=dWVf{vsJ*55M(pmLRZgNH{AUJ^BO~S@U*^&0n`w04Ss) zQ9Ptzqax_1r`k7!Swl%`^t#baV?W1N_=hkX`J4T?3pHTd>9KZZ_&084KcV$`O+H#Z ze~eG`t=Fad$!)@$H_U85{TD1Bb>_0(j~vYX7=dCOl&JxARwJURwd;i2&hrn{Jg%RB zUFDR-nw0FM_6f4zGlAGsR5E|pUmSFaF+S>884>vFdZ@W#iqN0+XK}n$_b$DiqMdWN zD*N>sSklFTeX6=uBK5V6x#q3-_T0rXDw)A40*&Kdy!5MNmLP|j_0xjwgRZSEi58p| zyNJEDH0)SCUyTGeVd^bg(rm(_>ervCs;N0yIwL!peZswFZ`?UL56~=c$`Uo^MajkK z0*@u|o>9X0cK+6-W8RA$23}MLu#Btnk*OQifC&wzeICtw@gJJc|LFxxwcv_+X6Z7% z{u2xvnSDj7^FP;L=Kt;b<6c%Z+x&k#s{@RB;{BBWLsM0jrGW+2U7>=63^{(2esB8p zLeU2F{PuSzSMf|8B>fqmPpf%tf89)SgxJM0wk;dYE((1;27oz8%@8A8F0zHc)mL%2 zEY(>R3nVwnw;=^rw_)O8$$leJ{Q9T3R$10*D zsg_*>#yrTwvKX8pt{Vf#`ff!L==sYL>`}<$v+45LsKI*O!dy1KLPQLpU(>pwobtI`TI&5Oss zA-A{ring1g{hn)7R{F^>Yty1I&YO6SEH-Mz{7+y~A{X*p~yUS{!`C+cShkL@v>eVRZ@RSdJNjTLey`H)E;H^T>5`6 z=4$37V`t{hbL*e@n&H&}eKbQ9fI_9SKMq} zuM~ozrS}>Y3du6`RVup${bhI7P2JUV4}Ry}pQpnS!ugiN=ircjWRXG>qH8)$rshAi z+w_c@HRw=N!&mW7;mqqQD^bDi&{@|2_Nv5TCt`1@K&U z^-RK`fz%`Fzoq5iTs-o1hNz-22dCnB3Ul$WXBOma`SG|cXemk|eyIJM+Y{_y44s4G z?*EF|8?<*Upume;=R3PAl^4g{hu49!^25)cFbh!!&9F}Z2d%EQo)p!9x<=uQLOGH5 z45EtyPd(;N>U2&tJ*2?)yI6UDf3&Z)`7Wi#pC}A2@ z5?0YCMAuWa=aRJqv8`^XN@<)b)w}g7GOIRig&{xkaQI9_yHBFD<_?{L@ZkX9*CS3Ngq*dX z_PbXfGNR*I%ErLgGOsPdhhdg;*Bj0p(%P(X)TzLTjc*_qR|UpLtrOooV`A`1979)D z|Ak>#jVBHJq9>h|*WYR{(z&`FQ-hF#_B7+7%Z91_B2q8>a0!bJKs~a=rcB!8IkRk& zfhaI^UOO4DlAF@xT{Qd3UCTWqucQN8+YC9RLuIjP_;-LDF!&YpxrV$3;%skl8TUc+ z*Bbb-svStfo|CM{Zknxrj?zjmHZN#6CLe`6ErXKEn8T4ChOeID9G!cuaH z%k}b889mhH;{`#oeK*tm4de#hsv4l|gLiwyw^op~`Qbvs8UbjPtjf~X1 zaJfy3C~@(r<0&SOCHZ)WN9|r|dLOsm_$JJkAzX5jnJoiY&0n-(?w`(OhGn># zQDDF)v8w)do{0S5f7#_ABB#~Rb&YCL#m*@3*KH0UAX>Jtc~GqgsN8eZqn?9=%&K0U zyP!NNUp?~p5I2$GA^`gF0lidxO_Ndcw5<7)l-`6vM$aqO$6i~HxpwVW0n+Uk^G^1- zqfH%P!By2P-=v1lnD7)bw78B_IW6d@yg=w|5ln zlv8CelL_RDQzq zg}W@=Rw%(pfyT~{a#oG;fJAdwZ$}4rxzI0=B16py}GbZO?Rt6;5 z7R-;bHOa}^-n5Iz887|ewLnXla)K@;fmIbSOifGrx0!{vJ>Po-S(1nuzj@?28aSiBAf!U|eMIfZyjvQ`0|y^@-p{M~Y7x zh=!3+Y;)_G9e`1Sv%f;a6RKg4OWUfio^m7A1892E=u+-C=bRPZsdn;n=gqxMWVOG7 z8LrMz?qtIF_D4wLT8-&GkR)c8cm^?{l*u{*WYIu3g<`{^OMnh3j{{lmY4wR+HkwjD zhXpzd+G)5<;_F)EIb35?2r=2tVsN>l?TbO4TG;2r1gR+@h?~cHx)s;ISGSQ~q>&>7 zlGBSJOJAg{@ujmV*Z=uf*{j@WaYhg zXR4XJOWSQa$D7RG^xQem@od&$q4aXu@uJ>_{b#^q z)^vn6f%f)jCv&xlg(`V+poeei7>QDKKa5P&41I)#bT9C4n%Z$`vqj*|6V|PfPvg<^ zF%X$0nHODj*dAEkvzWC7=!Tk31j_JGe-;>{qZ_I=#QCD(3c`yHr0xag;j&{lx+fuU9U?y>M6Xr~3b?`-vLR&_xXH&?O!4 zpe9?8>xb<(;^Gz3AUF@-AO*P_#BUhB^tNKA8bDKKz_uUQks{Oe2D~G^l$2)f$1sMjH_Er@~bj%`rrXmVV=()4)AinX$xZ{}yabpAnt*4j}>zH%hIwrnTcR6YL} zNY`x23nMPidJIzAh!wSi?z@&hoDWN+D+>>xlKaM8&>GLf=Un@R{f&t#YPfevFh#n2 zivA_Xq^(aiNgX9S*w=6?Fc{tb*JY||%J!84NC=+Xs3?(|8v3{0HT(>Z}?e%xg1w<&;p!R})n5(Y-BN!vD&y?Sc} z)eLXU9NW)OBJ+%$XZPFshS1(OhqpqsJOjyhEo3`%LQ{^Qc4%HvFf7+X1Db-br__`% z=OST~zB0&dOr;+(?J;Vp_@&aoyZsRb=XgS=&(6&C*u0F{u-2}e^qBh3o9YAyX?enVTIa&6-cTykcz3U`w(@bS8 zS?u4Y_3J>*H>m;$mG{m)hMW3tZF*t zwakZyix96Z@%HAHuR@kD_DQgPWF`!!cBku_1~30Zn;E%hzRnZ-EL)TAul<0T_?#w` z*gh|e}si_E1nT9vpS)*I#E7tCNXx@BeMb-4V4{+!re zQ*LVbE$lTn|EMPJoTsoq!6l(&AfJy4Dd8$aL`DTL!{i8#u-zQW35hqxqC{}zvB`fW zU->#BahXZ(A~)zbhP?uFvH^gO^RjIR1Jc0i&nD1}($#17yw!EH*yDKezdw7LTm5y( z8seE`C&Lc&ZaUyyyvqYAN8plUWdj$&P1cAi8WU)HpVV&9(T5Y3PusOB=9$A>1mxZM zs$t5lT(?r7h+M){;tc_ooX(?eD|Q(%8<9g<9%P7D&EE=G*IPTUlv~`j?Cd{0>K&$d zO7VL?uj)5S*0ufg>4J<*gOM&B<$6Xzz%3VKa8H49?=#6l5R=n@((l?Do~;RA-1YUo zmtQNjFmsulr9G*bE8s;?1Vx;P3$_t5TOopL?0Ku)7|@@QULyCqD0*aNCiC&u^2CkE zBP98Ar(88Y*U$vK%l?J8EjL@js`zQ2Vcf7c6K7!#{XmTZY|3e7;1nhW;^K;IL^Riw zTh#p+r&p=ni6+t#$LltpadxUFu3p_cMXpzKyRO&e-aXbl+Z6r|F!q2&1=xX0i+~hF z6D+zSi|y*1ea|i9A;rX_ddSd%A$5P)3{XN;l2DVGn7LE~Rrgp?mPQVD~x$vfA@9D_vu3#f%S{X8OI9>9rs#MjjjCFSz zm4M&^q*a%$CmqMyp?6-Acx|@p>Y=93RaS_~Qm^~rkH=f}W0$*=eTQa?NFDZhUf-oK z#5p#*%FPL!b84*ncWhTM^)i%pFT}ivEV*tFWlk)MlIC*buqs;rfi7W|g2pbseRT-f z@7no?IrxycA|k_dCPVT_zNV2nN{-N)TisOkPT~AmMO;OW_LVDo;ihWJ!u-OZ#OkH- zA7o14rv4!RjGz*Q<4Yx7o%)3WGU20TcS94<{o!GO*I!r><^&IlS`miKe&#`d4m~ChV9Sg20JFnH_lyUp^*@P;x9dl-ZgZm!?GRzBJ zMS9+BZlOjkgWoSOQY*(vtHR3ne79%MOPiy2hi^ZAdHH8Y{5ZtqF3UUI3KlCza=f~+ znU`_rv8q5;ntMBEf}v!6dZ5@uw?j)kvdGJ{7~lfWe`6f+NMBJtCH-pIhH@nV+kAws zs=4{1$Ot02+>bbFzl~?@IvrY*O_Ur=#ZD2%Wn^)1o|c6I7akgdLE1<*>82nk%QIat zj~t0Ow1cdxIYJF(lefVye5+TEB*TX`!aJ|d{oftf>ro3ippUz^(mIfR zd#(@m{ueAGM|emVe3C84Hv~fFoVG*=cjOXW4Flo8VniqTux(qcCZDIh`j47TW|spr zfdl5(o9<;lMZzjfrV1eu;Mb-l-HD?S`&5JWOo-`@i~!q|I86Pqks42OS@AAJ&$n}F z3dHfFvU2$8%G)mBpw9Lnd=IirL$ z{bVzK`KEKaTZ0d;^I^j6?O}r^CKe<36T@>#`#@_u-U14jvpd@zv`habix`$Ukz-B4 zeGpgOX&3K;@yGG8TZ1SSGb!||*J#NheYJ)7KJwHvVO}-b8d+y91gj$Plgbm0tYHBu zMen{m!HReJSKUmsO6fS-|%TYTo6Qrh^)+UMc3=gt1Nbl<0(pTT=g=C>|eCpIfQ zQDc2}{?S+X@wlZuR`v39w^6F-k06V#mCS@<*dI@_Z}pV1j_tB}!Mk4&e-ybqyq|iM zd_#|KHF2~Q!rhWDv;IN>VrGa()8;l1UHb+ZPL&5nqY^-&JnP*?ad=El2vY?!6nJPZ zdoi539c%GQeh*g)5>Sv?+Zd`IILn{db0=_WwiPW`FXBQ9fE1}Wbj19JX4Fa{*6*W$ zBk+kIQP=pmj!RrYGhKNm9W3btL&64qr+vS zKBh=JdD9#v?x0sJ1&CAw>sOs_b%YI5WvB#V1Ehiw17Y6Qy`^2BEUC|)%0}D;a@h?60g&I1ci3a zxwy6+l9b6(OSXoJk5{!S>~1^kn!+_F6q)YhW%nl(lfQPT#nEsRC!F#ShvwD5fKMKxJ&{`(`_iR*bBnn`}u$hFNQ& z2`ldizJCU%O3q}CcxM@wEEH@8xoyCt4VKQ)csVE8!A4kO{4SG7!ugR+Z1 z%40{Pl=jMG>ahnQr{vDR!TsBJTn2(EeZ8H~y;xQS^gEAyo_yG-_CEB~*EiV5w)-&A z_3`)cDa*$?MwLpHLnF~&(B@nQTmqJ#1I$LV;S+^_tR3}Wob9Y5jC6}p9Ps(#N5%gIwX!ZmDL1ueyYsh6YZ_k@j@ zlU6_4XCAahEKwF=;Lix3CD*E=*+r=K?*9s#A{!ctU$^GUHdtYbF#P0}6Zt_ugfll{?Uk#$7qmiY%Cr|H3M};0x_k{`q&+F!uB< zg$d7DCf&_I<~H#l3jH@I->GIpqhxA@mZ#})8|zjsP5$7?;~_K8sYo4oVE^}(`GVJg z@0-Mv#8!yQBcW7;^nlQ)ElSqeKU}JW1U*4O#oxIh+wEF*79=)~^kYMIipAXRhfvrRCQX$1_Jw(5-IH3^ z1?C_R1zdkG8mz9EoBZbFGH(RaiL}o`KvjANKhLSF#(TcS@DAv5TsE3$7op>Xa%$e( zuX~qeQ#Jd9E|ZkzQgpH8Rqw`UW?+pUYjp$ zATA3o9M3tSHp|lFm|JV`=2z1fQtqz_M)Xy22rdm7wdrY)dQjbUeF9qo9Z<^<{BJF| zPih1aR3SI;PXUe(T?f9-4t!Ms<z;S{kE5Q>*1$t$0niY0|q7uS*gcFAc}_0vHj|#UMu|;PW~6`svFB?YB8xb zsQYNfe`u`FUsltkg^GZ3ekdCed4R+=%Bz?-?7jI~&}e1TY&SZgz=4HEp7DFK{ht%8 zK7H$fxwbCMfla~Xh$@178<6V*bG1(@{)Z{B4X}9ppFIOzuyIP-tRkE7l5IEthLpL+!5+@(;D`oDXkFlh@ zW>w+aDcY2^8s;|AG8E>G4XRrxhxcB?kzc0h`$oLa{Nmzm3)!lyf6wo{0`a@#`k$F| zMr4a@xoalc1$=F;1iytE@DGirU=hZb(UJh1D=JJNesj2v()Ge;OyTq&qt$h_6&CA~I!nbLOBBA~g!MfdHQ}N`Ng&=%0h8cbHc@fs!W;%9@oo;UsDxx^d zG$NhnTQ>z%CK;qmGiU(eQxrbC99JDS$d%W(=Clk{HycUQ=uK9RY^Fa&Ut1JDYH#`( z)5*mO^AWFtWSIaaaNJB!Q^;A*>|Vl-pUQZL8%cM+n5T5|l(Pv$Xh{22&3>{iUrpKk zc8@)fxrNhVE9#TRkzFd5%I689)iQeKY>mZE)f$N0>8d6QOZmmJ1(>2kqabW2M!CYj zM#;CFb^Qw2R+-DTrM(coaM%3HBspNr#mkL#5a+L||9AJF5e$M!17r^XYA>L!PH7Fu z*c27K{L~mXI6JtQW^&b8;=Ser?vQHP@|cQG@7(;KirFnAoy$YvpD+2=wLW6ij(o3k zhf0gabkl>rU7LadE7WSJWt-<#gU;%&Z5C}bvPP!|ll58AZV7OoBZ$z~$p z$Nk>zT=CjE^Soc|Gp$<&)ZN(rLz5`wIPl#5Y1zHl2T})>|23y>QYi(fD~8n2rj%lS zt9K3V>R>;NIgVGG24y;CJwj3a?HP%=0VRUz)3OKC8yR9$%fclFEUgH4!3r##-qD%; z0`Ckd=x(|+lVW5tmWC(W_*^s`)G3(#nw`!CR%q|UwjdDmyt>2hlVZork3Mb^k+JC8 z{ZXJGP{Lvs-XaB2d)JpkD(^Jv+ak0{|Cp|6u-dNC{{BApLE_y9PwB;fHYT&3+PoDY zIE_^6^<(tO+38dxdp_yyQpJ~*jmtFG9vx#2Yo32TD23lw0qbP>rp)3;`*~=9YT@6k z;Mk_$?WNJOxwk8OS5^sjFAd__O@*T4Y|yz$ziP8JK2qmutc zV&E03(mKzO@b6;3Op!R$(}cfD-_-_NaVk?pI?OT~x@sNT@z(y?LGvP8aM~h1KljQd zG;D?@oAe*yc4XV&H}N+P5k;M*yjxOcAqm}uS1i;~6t?%kd>)LZJzZ-69a5gx7B!RX zh}zD2kZ8uI#8V#G75kQ$X*ljs+GG_`9JFJ!AVj)W0evZ(FA|kMDhyUOxJdZ6w#et=u{y7dkfiqHBh2ZAuevkj^}zAbUpT|}aI7aOxmbfm=W8QeBKH)f6jhw&dBHUy>`BXQ3arocrR@sab#ycGx26!U`ltiPC43KQq?7Y2N#km=car^QE`HA3vr$R zT=0Z1?x#7$)1`jNJma-&jyFoduvX0_%h~z%f6#W8Ur~o`yGBxwE@_Z%q-y|C>Fx$8 zX^#X;?XPt&a|H5Ze3t^A)A&aR1r4!jc}i*3-?$b-nNXgHSB?7*5jL9^z;7Xa`K^< zY8-yE0dguE>qDWuL^BB=G_rnqexvFy-lBQK-Vu;$9X9Of|A97qqkm(m_<@_ce{mfD z8-2rkqIBoD?OYQd8oPK#dF9LNA&``x6Tf21T0tXrG1&*IF!WX5U=!?hq1Mc;oj`*4 zbt9#M+DtWcRwu<_7yi?yow0D&_%NLy!7*^8(W*Z9{m6tgXNGAmh(8E#=!ESP-mH7S z16I8;EV*XS%rp1#g&3J9I{fq)h#x?(e9l3+Foj;cHa^D-K3*|wg5^hlqS60kkw7Ra zUSMVV>J#?PJX2NpqpK&R{D5{bj^y;~5y`6Csx&?!->LpKpz5o6yTfSxJwBwB!;E$j z;SE2FZZoh(IrQ?4C7tTC5a-im4W<3=#V#X88(kB#7PzyhVw;h@2EA!55Nafn6Kq2! z#9Jol2|@SfCex*vZ~1Dh1DMhPiS6%2J#RLHwSaj*_aB$p>wBDw%ZSKy5{zCnTH~rq zI}5N+XP`^=FZ!m?L=cxLO!LzMc#3S|y#{~ilkIjKKh5=V#c9t@BxrtU%lJX(&w9Y3=U0j(?)_-MW19IGDW~5RB=|NOsoimdW@X5~$=0Z4Y-9B(i zYS(oT)W$P}5WT7lkY_=!a|5WvN7nbNVQQG84tF+_z?8Q(rQ1|^_L0RKfV|YgkjMMJ`&i=kdiZF*0O8Bi zHx5Sn^AjZW>b5xDJ120EPf{X>-4BripW_| zvAAo2rv0oQ(g{-1Z)?zeNw*JjO)#voGpR3{JO@PHKl9|Ue$CxO%oeoJU2wrI8-2WB zQzt>mHP=_hvcsOvw*!nK-ohMj!rl`^tZv(F{k9)EcZoc!E7_TG8Xu?>bUD5YVKYz0 zV>SA*Y$icASodPoWhMS5uMDxgqBqYNfBCApPSlb6n1+`GT35|Ao8v&Hxke!JiO(qQ zOdVl1hz~aFZt3i8XCK)7BeuKl92*<5b>g4EiF7Ebhl>I{d;pU30{-Up0`Tg4O(7yb z32*t20@9k~Jg406{HRX~|HK*o@%;FrZ@!mMuqP#oMp$9DP%W4{B|R?c8>>EclLXh#QH%-d}Y4ff}RR6^xDW6>lZys>*B5k#(u# zZ6VVl$0oLXl~sCMHhS?Q|AH9`XT3haoUP}e?3Z)B9CRRv`j)GSD`UTJmc8PFuzG$P zj-%oTuQGvbzV_@4Zy5->$GBY^^96U-c6N3qSx28z`uT~r{T!MS=cwLvTFYLg(qk(6 zW0)IR35=2}miKZUBM1D#5>%Zokv~eaq~oy-jwIFcdy&9Y;2W5OPXJDZN1^k%D`Zfk zrtRTli8JH#RPN*&xkm8s;2_@xIhzZw4Ye7IDJQNomC7tG3fQSFl-v1nMP1vOD8CGw zAYA!vzxfF-#i*H*PbX|%IF*W{PV2c7`al!t&sAmoDAIyRcalgpFffks;o51hkKgau z-WB+z(iQ8t)O3T~2$I|6IX)UUQasHK_kyxt9ksnwx%g+K&*6ZOC702>>J zsXDXkem_Fc!6AxAkYjiDQySlQB5v?EDl7ao8Q&$uc;y_^iSw(BWs(igP<^ z>9iy3)mlH4KBOMkYu&BY-3_m$Rk*;`Dy(ZjYHOT67@8i(u5O?0BAYg$Dn~~(59T;C ziQT~0J?podPVrcDC#yl!J#RVrjPwxx69>yNsd}FLXC7c!wm~pSO0yq(y`h|JT>Xi+{ae>dG zSl9)ct-tB45y`75J>{ydKNm zet!~=Pd)g%veLv-2@-dK$mS+Kh?2}17s5pf!xtUk??!Lt61#feGVM8ugGgIGbdtQ% z5sM`>sHw=D{*Kg*U>^z0hsxEJ{mNLMyfXW_s~ETTPz7YH(L$UG6J995GcCPwOU+AU zY+Na81Y0@t;jCaSSOiqH*)Q3%JwH0QaQ!t>Qrr0atXl3Y8_ z;^9pdv!0f?x<3ov1SW=cGOEA?Kqtjphwf5NaN}qlbbhnc-T|w7l3f7u*gQr@hrbS$ z66wFuq@%Ohn~;`LoSP_1nQmRVG&`5qKEI4sSt@^ad47>u=r47lTDWZ@)zyeT0K3xs zL}*#EG>jayMSEAy8{3jH=GS=JZ)FG;A85=NsQ$TVu(Dxn3;SM}Ou3@0xpKnI}77xuq z2VT>{PZ-qsS9RyCc%kS2WXWGYD@FVTE*9CX><_+v)OXctQTi0H9)aeNZ%?VQ5wfqq@@!(I=WOOv_ zvot6dPyMrL_y=Ex`B*;Drj$9U(ImnU{UmtVs(7#TFu(YqWU;bdW-tXh$NmWWrB)Mqh zF87>RN#wfk7PNlDTW(Tb|77-9&s<0)X2)jsOg56x1tH{a!w32l$B&>(AfrKeJa;nK z1QgE3Y|hQ}*FgTEJ!5t>U+#3s@R5~!p#Ovit2IaTZ=W5v3oE9YKS<_$qVu_P%B2fIU-*%~PrG`L1ru~| zmk^kF#?+6MBw-qs1X9Sbp&at(GW!6o1UuX9OgH-eM&Kx+W+g^9#E#mMP>wNNC;6?y z#QT)^kY*gdQG=|BwA&MRY~SKrZ8uUKF7aml*`0VJYG1$k7E^?a%!Td`xD=MbI%C>$ ztnb&0H8HZmbh?z$1Q$k9YBj+bTNPNc@gIUe{|aUf%c(P;!hH z$6a4+shG*lUjIGYnYHdrUSHq(l=jNqhwaP7(1>+ZI_T>svP<82l8#pE;)6g8uwzkx zL$|l468Q+FU0#}{I6B52HXcefwXF_EN7`J#@+vhIpA!pUb>1W$3~h4+>6mZk75KCa zlvU0reQ-q`7EUY7{#ZRU9j;#NmWE3661|?NYtp7*vJ5e)89+^V_)5RCHZR72u!$#`++MBXA~new7>Q{In^j3tP*xe|Ag+^pw{{ilJtYk z`Z}1-U4knCa&MgMl|p+*d-$GGrou9zrr;-y=tNb8ajZh04>~sgkX9gxew>`v`JF(J zV|H;&AOU!?=UyhK(Vk(BE{R2(N7_P7FI!iQ81FK`Qfo>e0 zA$a=Siz71j;?sbv75IedRafHzes>9&`Psy6>5Qc&%hWLLZO=K*_!}Q<+qMc%FHfX% zSA$zqoQsb9a{CsvhO}P~ALDFVubiRq5D{8R>xU0RzrU~0Mc`_OCOT;Kc8Im7^uMN| zMRbw}0rnBQ^AINIc5W%`BqN<>I}? z5B`x%SAyd7P=-W8kVenOTzP^3TD?Cs3&XOqvy&l#ox6X@YY}n-w=GW3#X>2O9=&wm z1Ad_}aLN&ac%y{JDIi3B;Lfg20uaVe18`%%A(6qKjNnKaH+;&&u*%Df~ylEgmFDk0@#6rG1rMiQ%xAjPtsKDKf#b0r6OlKAqG17sz>ts0&>l zsz(W+!9>Hw?qK)IfL0V3+SGz#!BkgmfjWhTsj0JiI(MEV*pj3gZM%S?eUoaSddqLo z^8~Nw9=Rj0!fcW22KGAcO&BA(%xK5lyXvj%>>%<(Hjhc%<<(I*4qhL+Lx7&j%x*_R zG9b)?&$Sh-3xfhJhALim%ay+ld<&!P)Aemjt5wX1|LS34$l4*NUczAT#_fyhoK}1` zz;>d+1z=R+f}?%)b!`Dydm~0fdwc;E1=A-s+dCMbU|8Wq?OwvSjwh#k-xRVboFgAQKLzOh3a#|jT9ERR<`bU=PZ=2 zF+#&u_w7Q%Fa7bs`+NaYXM|3xKy-P$yHUpLSP)kM*hZ(IPuvOlC^wYXssS6r*p>C=94ztqWjQ)G@Mg+;C zQm34yz_+U93|ONyy?72+-p?JEcTpWw;ND&uo|<#;15L4e$&#E8d3{G+5`$c<{GP7D z4~vhJ8*XKCbnqHZ5cRf`n!#L~s9j<4ZFm%ZQ!Al~Q9tSzA))cxh{X5P4Vxb#*rida z^rUUJPgqnhpy@GO0tchRzlxFqfoSR?hA0bzHwGl%p1Ks9`pq3AxpVb1``zTZnJI2F zZHnGsa=xvRv9%odv#ArOXa9*k7AwKQMBMP<*?A)`a1i^Zz2S-HKAX^=kBcR@Y$r|Q3$aMgY^J8v8?C7Ok{m0JT}U(Ucw>(@dj zbWQCtyybmW9fF@38N^CNj`a091TWMc->g%eONN$h*!g;;yLgkVsvCp8tk%z-F&hxa zK0@pEDjYgk(@mUwM-}VA&h-ZZ9~-=rv_Ib4a-@CkdJ%(+`;Pi>xoT`V0P+f;Rq>RI zT>cHDKQJX|&o!H_lOm~GMm5~sUX+&l)`-nbsPPH~KM8lvtJ`sV2H=6wRWF|NNuEVS3%}K6@av)IKev?~N&Ai&2b?&Enwc zvG}6zb;ZF#*rQ?Q zrmLRi;jYwR%sDWT8BOcPAB#%LJzb!LJALR`=y-L08k#Rt5~nAV=0ju9R_pu^$-I`SYI>#LMKEd)vZv`i}bZ=(lj(!)}c)fZmTHXs)7D=}%h$3~APp$M*3`1GpUtu^DSpKtW@`~n_1`YyGl8E2SD-ck$i_Ix${tiMz#0#9xVBx3`1 zd6c-#x0;^_VUwJ@viP(bSGDHD5dFs^mvr$U!z^?+8D=x*|E3K3D2^4$6gvVSAp$?S zHKA&0maOH_)U)j80T7d+p7RBpNOwXJd}FtIx; z9>!^r!LDR%5ITGN5t+%0#Z?FCbM5Wi`e^>c$@AxlcnZFj)Hy)Z{0}KBS8o0Su?;Qap~xb~EU<(zApx4kwO2zMh6V zJWrQBvcm80tc_gWDj&xBu(x^lt8HyW@gn@XXwM@Ifa<2fq2z-dpS11i`9}F01M4IT z`5|og-K3;`_jwwY_!TomK)I3=gWP7xN$Sj(r-SgFi4_SxT+uTH@a4BaS-nQQp zu)=#??IIiRZDYqf4%bCqe*essjzbqV7W$;G$8>SIkKT^bOG^XNDV78rg_tIDx1BV3 zevBoJn$V%f5bacQ4Hn!Ve+OQWiR|TWZN$*f&OEA=BVtTuSY`_{B*Zw_a+ZfiiM>R} zEQn|mqc?+7jVnb!vuyJ!CLU%1selfyX&wsV@8mcFY#2#)h1f)3KkPwaaj zX}y=Ne4Tx0?u|68FwgHDbTZ`ztGzDphrZ56{BJziQ=kOG4luj_n2;1!U;-Shz8#7QnQkB>=jRebI6+$ zjtj!%ie4JzTI7@O@}{{zS75#pHt#OK7miO+sS6))lGqC|QPOK^K^rh#(T*1xVLLwh zopoJKff3n3Ybzv{5Eh-9lhvOt@WJIMJwph|M=pz)XPoMO3Ps*HP%aOC!m00!p?i&y(!gLep<6HtHah)`Wa zp_W@a87@80*Mo7csOif$XGxAphiS+n@Sab@wBM~)z5RQ4mma}|W>EwChBNh}YB(qseJ7Xv5gzW(!^3rMy0Bjc4WoaOSPjOu3p zZwxJiK*p&@nawrpI$C0VN~3OUZzLH;Z`P?XzW6E>F?;}`=j#V<>=8@vGojU-;&ka4 z=T4vr1*a!}Ahl4^u&e^bG|=lql)lrF!FPvRQdgV)SZDOc?nl< ztS*&gh$R`5CRK91%+9ED4=~vUY@T0J8COjQ+?(AzB#6#{-uRTQfs&^#aC(YtFPu=9=Cu*WwvQLHv8A4>Qg;bnPQDQRI}6)DNx4X376o^h^VGGo;5 z^>+Nd!pYxlvF&`2#w$*$Lyqp1n3$A%JFYN`6_$&?t(e02=P2( z>v7_&#tbxkp?l(YXjWJFBQf-ZuLc&syc{AY@$R90Ri8@%-sm&MnW{R;Gr_5<7QC5j zZ{=H-V~pnIOgiP--HP5}70<-taDc1zQ@`$a1p|2o>q$)CBx&X<>*-B7z$9Pc_|;*J zG8itYapTGE!iNPG=#GkCyo#s1M{il@?*AvtgwAt5bOV{9A5) zgPrldy0V1;dQ^Fa6YaF_0vw@L7k;~<5JH|$g}zVfwZaDv_ywO!cOo2to4Z{ROb+wb@lS^ zx*vaoU*^eEQGJe?>r8Q2sCvYco*));LAWgJLb@7hd(@(N;1c{j3V9V7IlogR%9z$_ zW%pK{e^6+!#nJGNGW<3lkfTqN`btY`W-$9G#U0eJuq+a^#(CF}IFM`4_#U*7YH6{9 zk7S{+4fSl0dhIXq^Jv*~GNLY~*Bte}{;8`e2^;EN_WpNjY&@zhfoqPMU^CQT;aoV* zLygoShqJb=A$Wh9l+wW`|23aPTRN`3^Je8soP$KWk$P(qmaCbGjRkjk^Dh$zN~XCB zHEJaw@Ze;wDTXb}H<~a*Wk7l7FP;Be%Q>6)gvhMs=4(1ubY_Aoliqg#!8N zX9%vdWrfq0;^tb$#roewjp?4y{kUzSJNb~1SEX~lF^Ger9BMRIPm9MpFPIsp`PnwV z(I5J#Lcx}%!^e`|ig-Ecd!cuMUFEXL6pwxd0wyMjC*P7~+mesyyvV*3ALwA!XV#hk zowAv;x7id}T*VvD7>6FfXbT6V57H<^7@0*0lzJ9qEsroo?^4*RSBJGl1SIbx<(sE} zVbp&6D0ty{?aK?ZjD`nj&aTO)D3N4&9Re03y^JlG1PtG+(#;RW1S774eHbo9Xe&1H zEv1(ajE2&d;B)%uD$((uuo@z?tYmOy)}z&V_C`XR0=#t+7bKKk>Oh)_AHTkP zV$mS}Ludj@dxrVY?Iy0PQEIYO54`;F5Rfjn;`b*kPY?hp+_{+HfZ*tBN!{^okfcBF z@8ccE4nq}5R^v)atUN!I-uL2=1e{nIixV{m6Q=X9`CA!XS^D)mo5W4@;f+0Rr2QNq zi#9z@AORmxhZCH68c}}dd~|ZVQ)g5TmuECkt`46ZkyWLn4RcxZS2>5<{zG!Cg*mVE zr5v=VW^;g|Zr=$MN0y@voMl^(PkvA(u`e8FDsLQBfe`Mh;cl?|nO*5Va=R;?Zwv@M zznSrufEo@KL+lD*6?@>~Xx5X&VbR2%vue0>bH1Yp+0b6O-8xp&g;$Z|Gnr{Ffi#uh zUfKygY@X1VzFgqS$HqrQEtl)SWH2(2kGl%)#IM^>|D)oQ-#!o&0ENyt=f4c`j9nQh zj8c9)OMx}V5t$$y@(m6pUq9eNg~CcQ?Ft}#OwB}wLng>ULdS0+CGIJ{&sH4oK%XFK zYfHG?f<(m$r(B+Dc%VKB0%M6b>U zQr1UwkJ3h7t-il&Y5!YaA1$HU#pzns*IyVpWX-AkogIl4-55zmLAXD()L}k{u4Bf8 zF-BwFWnI9v@tN86Scm+1-+jGp+33Nj3dL3uF9$=b$iA`ha-@KI*prGNOdS&GBbRc> zo(W}gYIwZiSlWW0`JMe0r%dm_ywGWkb!@{U%`(#MXbjiSS1R|5+$F3$ z;PMSw0p8%T5x!2J0LM+=lp^WN&^jcDY52hs4(GKho48_3tj$M?>~mjkk$<$?!t9_2 zTk4#jq+Us*XL*PZomqv`ohv!d7fvsPIn@wdeZggS^6Qf%KR4@7z=VcK%%}c5z%1Ti z$w(O@bfs7Bu=<$)RsoYzmA_x9N<$#s=(*uZ@?75+N7T%!Q|YFtziRLQUsPC4BGGajQ-W`WL*6EGHmM}p-TaWxX zI^QqREi0>K1u_Vk`9d=x=l!mHj zw9d*1O1Kmzqa<4$tc`?vZnyn8zNIOCcbjjJqGv`W?FI4Ik1R#1681T9o1J8c5F@Yv zR8W7-5PIyU;)>QL`Jq|nbDG{q&9RqQW0(?Xo`z=ek5blE2nUna>egfU{f82$0~_ zH@jEY`IoG|^TF#M(iDzI{kR?ElGg!pdjdSZ$35!jVh{@jkn3;mTY;~E<4K5EQ*!}; ze`v@c`8Ph;LWUark$GI&@VD)`besq5B^ zkGboa#YOQJ=%apkR%z|eJp(X;_aFj5vLBSx*VIcBu*`bE#WH_0qiq;yM2zAywHT#-z4IBuV}uElNmT zTkjgxHjbrwh;OMq<|g!QJfJi_hZWC721-3o(Z2SVt{Q!i1VdEPdnIYUb#Z%AMe#7H ztK$DH$^SyGgY*gCAovq|ZqJ`Imz7$0tV&{4zE~C&73ps(0y74LP4_<}^g!aH4*GN9 z6OKIlsInzhtLm0oA5=Y3L{Heuk(Z9nx(8PzPKB=Tqg{3)yaJv!H{&QS-1R9g+R_d;gOO=K!eX4E6d>gnKe*qx{87 zN9*gJcngK6WywMmu8bL|KJ7@Z#tn&fpF8*VEX#G%QX&R>XfbavV1llM5NlJ@_P*u% z3Yp+!0o#eLQVxPTq05(=ilJsyN6QgC(hfxqxwQp3Oy~Ovr>B|>`R^{3tM|oInULgH zH+jbTBd|lWSi4OMf`<>h0?;0OHXh%#rT5eTh6(AdC?*9*C3K@{@I@%SYGEW+K3Nyl zK66Lv#loN(tv?Aum^M0)<3N`PJaB(0;_9FJpu$hu zB9@8dHCEy}>V)#cDAkCgyaen=`=@Mw;m&aimGB|kV%3$SM?@FGoo2cgC%<%~N>V+(0o<$WJYtxAUl7Qe; zX2eb%Fa7agT%J6Hkuv>H{kMUWPLy<>RAQgP*vH0`uGq`cKwqR9fKgmb`#HmgA>@g@ zx{BuM*CWoB3uYipSW+C(oo{+5iE^RL`?a|yDb#392Gte6UtSa)bj)Z?$&p-WC(E|9 z=v40!pmuJ#U6oL;7DZ9qZ5Br%(U1-=7YUqml1K|2cYpaJK@XB?w_2$B68ojDF>Ph} zmHVv&O!AdO=j(~abH8q}I$y6a;^GJ2%UfxrWc^s#H_qLm{1_r=ml$K(pY#r>t#uf$ zxp5X*3koY@06I(%76AG|`W4NJ^eZ_n=)d{_%E+?-Lbx0FE}SaUXvI8p4k`W>$i^iG*-26$HAFt zgm@n$`KPJ`;=lud_TfC;Dn=jAzb899_`GQ&o!+U3y<4&kqDo1~*2}6Xq00E1&P8<~=v}%0^CYC^?K$L8fFMPkhTyVe;Z_aw zl>20pzH_{^e|iP+ZX6#t1wy`Q`#S5n?qb?7OTd8}z+dgYMXfo0BG7C=eX{!NB!-QO z66rm0FxA&mvv2#Tc77fADGs#G{-ozFA^xw^eSw|Gk+e z5wS+5(beyp?Z5QEjKJvY#O=Vc6Jv?K>xZ~AE`%??`(Rku>#Q#i3LhNnrQ&~kVy3Xb zdG~qxKTD>R>_6ZqARY4^8f_S{I>x_+|59wE&x3zf;IJu3>iB>1AGP`h^gAdH?hadj zh5W&g%p67m{7%<84*`(j0>s_Z8gN^w>efGA9F^L5pMPCk-RL*xOyTI?tB7T^jdBVd zm3x!mxapg*Ggh>X4a)zAB=!0^d9rr1L{?+$Tf;rz{)fT8+)k@_GwR-!SZ7$2MT(_4 zBhhM^NY(?NsHXD4ev( zZa?H)!QGaDZjg#Iwt^}f+POz|67RbZ0MD$#G`1r|2)|o6xlsT#(PV;-ME#{6m65v^%a6WQP=CEp&MMfguspO`0yCxCFeU;w9Woowhb#Z%#%-hmrLHgza;R z35C9JphcDrLr#mY+#D|dcs%K*3$`sk54pgsqmr%}F zQYl-4T~W)djo;>y0jtHH-GW2Loe5C#ENr@9Q)2+zTu&05CcY?uxUqAre65ypy81nL z2(%r=+X#e|&3;i$CoF8Qw8_vUVTg71vYbMrN-_P1RDLAn?$0#q|1P5dW(Upxb}9w4uB>|) z_RArb2H%-v@dMr6&FZF9$Eeq6=3e+o3u9e>UC&HcwyGjK$Y%l>))Bd8tqr+qO0OJ9 zB2=Oee&widJ3dr-EAHXlWEONYBB67Pr9H2kp$zA-#NuWK6x$0g5Di&0O;k}GeYIwd z*xS6|oWqBOQ(J1j@_6+8hr~g}v>pn_;nAC`rxp|fT)sIC#*eJAGuAiIFWQO8Y%oz5(NVDCxi3j~d3JB_YbXf2h zx0Hs&bnsp<-NpY@VK9hLzuf!50~`X4AK2wy(RU5<^2crTh?NSa0>{%INt$y;Uy1$5 zk8(2Ung&kv;a)1{+~x#b>feyi7^K><-ie}sbk+OkcH7b#)b_pIibHSOKzFDhLOQtR$ zQU)=hwi(aSo|r|onY}Gcsj00Pm$1v}pZsX3=86EHP8ediEtr`;Tc7zV->WfXL#4if ziQcw>EFBFO?oD#q8)|}Ir7t5#n@?+)NZU`P#m6R$g%l`fZ#U?!UhCffHY>+| z_64B}`As1P%jyhYcF8t5wOvEdk(KB<_1Tj@&vm66O60--5KO)3sUI*g?6n9 z)?pcue&%X+bMY+y#L_4DJP90Eiy+8#X?pN=aR_^p>jsoHaH*lwIWUO$B`=Ywb@4!V4`WZ4eIr%<}OieD&esZpsjlM!2_#fyx;ebLWtCvs?8fV|_G zPoDzCa8cw&-iy_^5eSWcoIRhXaxyizkONQ z&ur(Lfyy4N+RrTKjDuDS3!;p-?Z?P*kLHbk@oTe>?0!00>qHu)@ulf|KMQh*-*v$- zL$FMjYMtwhKv-$kb|DH=*MJ>w#wQly<|o@10dd@H5u*DU(=!4GH*9?xxKdHAFZ3;{ zwg1ed8rIb59$)iSR1w7WeZc%0nyt{v*yxz41Ps2431r`fo(UIv>)kE8gn5jQ4`7V6 zua!Di0_=!`&wyrE`cZXmi=wDB0M4=P{^d(Mi685A)erzL=+n=!0dIYJc7rUe9YRbL z_3>~nH*8er_!Xz;PD_jLAqQ%Dzh0})^7u;!F6eQps*Q-VUXxVWe5RMySTcM$MJNh= z*GqO{RQ6o+3*!(F1L*&wkRF zR5WD^`c534*dQtZTiV3ZBfC&i4+s8nABpOBc-v5c2bl99Muu{(vN|dLO;HvWc~dt$ zpXKH!rbvGL4epgv(-dl(7Vo$~16%80A(9l$tXIh>p%}n+y@vUz`?@P{;u#`&p;2ji zjSGK~GDOn7KGLWMHL%lnWzn#{$kMio56RlUDrv+kL}TRH$}kt`k*1n}!9MX=sG?B9K#GDhYL+hy&)0>fZASZXRVx}<^V+$joAS2|D&bpJ zuSLvWEN|+=ntQD%1`PB*^VPjH{;~>A{Mb3o!~l-+w1w(Dg%lo@3k$jFi34z5ajyd?Ia0XW$*8wIZcJA zGr}}ztSmDbzFx2J$UvH#+A`G$t;YTMfxMsC@C4{bHObkqmKW<$*qUo$Z!;`;snm^< z5(+URg@X9d+4$c%hH50sN*^7`b4o=!;M;{Y4HZ@26^I8*&Wz8jFuR|~t4e-b&Anv9b?xWY zSc+$~jAv8Wp8t`EZkWQ(pTjQqS?bH7PXQ8ckXFF`UPzaf_jOTkdedXh^-RJ*h8Y9# z_|pBfMKW>fqaCh2vw5z&LQBRw$;2#e^Y#OvBeS6L`?vdF7(q(tJq#Jc7sY+JUADpkaMz~oSanhH}aFjHRia!NhbDYR!2{YSCMtwW*nQ`C7_o+N@w@+ptkvdQ>6V-UDZTlSy83hT zK6Z9i{e=eS+X^V(uP7>{F{IUZ2PywS+|nUoKJ=AkGyAtod`KV526L>@h*n+ zv^=;hgR0m%=Hedi954A>LB|iWRN%qiO&|6$FF8tGj@;b@b+?P8kh zoLaTir{!JSaedC9YjVLS;=qmEOFJ4s%IK}~4{0W~9wO!b!3979>J#-+6>%t7z5i38 zF|Xar#=!nXjlS8P?Xnk-drsb>?M0Bb9Mm4#-+eyxx&G|0yZ?_%ph)1X&fYz9K z!$ExBRu*EOxYN(swo(r98*39P9y2GmohD{)UM91kdC=(;RsLgBR@+_+B&&Fj9oIOiSrvi+0!qrKR5P*{Dj8*7Mw1W z-ovaAIZhxp!7SI|0y!TyU3rF>6N2|J#m**XCilh{L?MePPzzi2hWhiB0Wr_akBv3Z z>=8N6C<5@wT82vD5Kye(s+nt78_#9|4UcoD=TE==>%TExL&};5V={H_d1Fx@Pba-Q1Zr0CW}-Lo?_fFb?Ve|?6gi;`*@q&%H$K3F89S{d5y=5pj%ah zM;YA3B}TUM3+IP{Z|dqkhP>qZE{Cd8{FFxwjOF!%U|)PtlN9w=gp!v5f6d=?HO{^0 zbit};m7EvE=PJ2up_Wr~3)Qp9;^Ij~t8Emq1MAB=VPo#9yx*sTdZie4sTSNX_+{JE z2V1ObdyDMuVBkPXpp|?1v);!tZf)eRSu1z<7vvjfG~bP$4IuOPHCx5mOMgxQUxJw@C^xdUz( z(@cGxmVX*-w1#Yt1vS-;MAbJX)*CtuVt|7|x;g00jg2w783Jic4lCxUNh7V(Y1?C$ zf#x&&I~Viq7;xGM53)KJQbemtijt66T2ekVDBMjX9NuVDC%uzl(kNt*09!lr^Gz-w8_8xE1n}mL$`h{cNK8u&UYZc)x4`J z1Xj=FIj=|OEAXU2$E-*8>)!PH?As)c58gPbt!rc^`lZ+w*cf!1*Mh;EJ@c0V;u=xM zXIv(~GBBqGIi1@`tR3;?@<@)C$JZma7N1#JB2U80G8$@ayzOub?|WeVj45JG;JYj& zSy?{*368>0;~9VRpJs?$y&4!@W7~uM;i#L1-_Wn{Im?+o9-gxpYagrK%(RN__|nm_ z`6!P2rPo^A1-m1KtzOrX0ar3jjcp19{=?+z1?77jvcxHM)Q6Jh;dCeyc<4W>3E-3{g7TS=Ef2GZ`G>3k?p0g)p`-!-31dRh zas3OP*v;pesW!DDBRiqSc^bx9CvBspm6w3ea#m)SoPJTKYt+dL^`3^KNThsRrn2Gs zKLAI541f%>cL14u>?HPKn8YR;ckn90gl6ZbiJ}k6X@yi2CrTgUaF`hULbp*bE>a0# zbCS&UyD)<@m%~5KoC%8VE7Wx(+l^y40I|Syin=(vyvZ9A#xCBS9BzMBX!u_-Zv9OI_~SR5rALO#Yn{O}aN4iiH70Cz0}X9_K@9lW4kO~E zF8AJ7UY1hK{AY3tJDGbXh7VQGW_}+>dE(MJTUhQ^?yo^)rbZ-Jp#Ej?z1-Us>DZBc zZ&evTyx!lT`U?dYYYpF@S(J<{v4yG+2GG^s{%z8uU+^)uq1rAMoVIHTjGzrJ>IhYt zK564|uRug`8y+2>`KDbIe4ZC!a#?>X>&TP7u{e`v1#53HX4_>~gCByiXP$D%xQk_# z{L;bjZ>0onNkJw$ErfZ&45H*Tb$6*2-(=6oS3{HwA~!iaCSYFzq<6e4fpfG^l4(H_ z8EmA|;3PVuIL|H9=Y*E!%|;dF$d$RL!GLc4X^cNn(mmLu{W%UTp??GSP;8dsJ;e4; zAN{~B#L~%7-23wDMVEg)jo0`}kk&@*+$bgE+Wh4H`D$vqOuwOtq!~%)W)3|MSM=GH z9<^9>HdEXz-*0xmg-UzF-H33Dxq8OhX{HO_`f1n&T6^8?u-lbL?rlZ7Mhz57W~Z=#fN9o$5ZpK-DQn!&?l)&ke~e2 zO)kMx1vrqkvalxXlrgT-JZ#K0WK6@_llvWNVoU_McfJLU7AbZqW7 zSHHNsbz&(W*9yPmtO)JLF67&ZSr z5QXb~l3s+ke5(s8_^3zKJVA#vi9UalqP)I-)=i%9{R$;kK`f1*9_K~PU6zp2Ws2JQ z>`6RwhHGrjiP>IdY}1m-2OhfEnL*$D;-+Mn&;TC>iFduP-XVBKcj}1t`J?#8Jwolk zxKHGALE@;u@gGtT3gERdd-cTeJ*-29CZxk5qB9DhbX6r>${k3);kos%dhXu-hg7Is zCr@ohSwg3!22y_k@A$!g#it{+ex$mV)Gs$gb!jJky*VdbIP5z6opcrJIYV6i9yx-I?Ss@<5PiW-{no-6c3mD=Y(9eq_+PLPhJ z^ye!qd8+H;YeD3czq4h7jl?f_wp6v&zvH>94pC(wdAX_Cn|(Oa*vCN0nlhRX*8d_Z z(*Nf0Wl7YI?@W{cS`-+CczPvkM*8MRb-&~%rwBZeVCq6elnS@b>Qd!}xqCypCSJZ-VEq-qX>L;(NF6m+ z-3)imuYW1wRLp-c_YbM-Myi=_ooZNwWI)7YKORcWW9KHrw0fF{AxQ1k4$D%!fw1Xj9OyFIbfbNPQj{>#i3}R&G%@O~jiFUQ zSB*vxjb_Xt0p^#o}J;iWcTaBL{WXSS^FbJH?2-1 zN}d?M8dgiWDwASv#S4OreQ@ce`a=^Yk~s|aH7~fe`HBKo3O)E3X9|+X5>Q@3<~cqy zBYlygs46h=PYSV$X2tX&$DNaScG2{;XerFqyCo_mD`xzMRN2yzeY51~bB(}2uEo;# ztxf(4Uv`mQwlmXi)>QkkCRq3_=jheYcZHo)@kYnn30J-NRPal6lG>bj=n(nfXvS3g z_kbcc3Wb8W0zF-?)$bFk7>>TWz4Ai`4t3#~yQ^Y9Ifftk$KBwF7y_~$_2@aFXQP{g zIcMJ=a^ZhH4eJl)OIb!)3mpQWX_N3J@s-lSemnR7!`xd%we^K>n89hW7N@usclQEC z0tH$qZY^#Bf(8vz++7>m;u0u@;I74;;4a189Vpf3S6m?6Hjx%ANyed1myj4J6YH(qpf zc8}0(c!d|smhMY?kXIy>`|z1$eo3AqT6I(rwAUAq;Je3Dv{+csW{}F*`7t61nKE@& z%KIVzW3_o^xeyR}+RhyZz0OpcRdTvaJ=<*XK`!pR zEy~zbq^Yhscj8Zjxi}epL*GfynnjsaDPuV9k*V&{QW9ppJxef*zcYrmH&`5BMf|ct zLsP=4r7@TBE8X(ZULuZMoUe~2Sg#!6k?gSdG&H66A3$+OygAghK~*d`wUE6TD6*_+ zR-Vpbwm+u7RRiodYJbY`eaFB{^dzixR#B5;N<3gon~OGVF3i(FHgvKyw|icBxJad! z98*x`4a(UIj6H2oXX~=q*0tESCi6UDzfv#rUM*^J8r~CrXxLV@dPisW*;ywS|N3ot zuOoL2PlI-4w<|$EcI}^?Ilc~j z$3lo5U|;_xV7rWpm&pz(73eG&&KcY+y21^D2 zIC@x&1nN+(MfFzIfYCiU-;v~Yr{VNbJu0_MR>>;I=#H-)KIX`O+RyV-i}1VsMPtdB zhDeIB(fJmr&Y38x z0B|&R)xG4SvjkuZ>^<(MRs4>V8pxIj=Cvf$IT3pG6wpA1#J_m8H>$}6G&DBlYgb=3 z>Q6EccKf%%KB>!+QnYoKmLbwcUFj%1<<>S}=rGLjGyAQFYoPLi>R!^K2re_g-FP8) z%EV;YQ~>0{`;w2QTCTL;D1T=!AaIQB3AOn>(69bB%&C@Y&2HK@J2iXc;P2(q-Qa6b zmRgGZ6zx3f0Bjbmgi{nw>3caZGPRJWEFYgMd~a2hta|>YWCWtSW|g)0aT3Y`9rCnY zlIFgvSBQy^b#uRh!?pcvEb+%Tl;T92U|NIXUzhYC6`HKN-@evX6(WqGzRSL}5;pqj zS}%+|7k5K}B2vowJHCNOh||4*`?(=h)4I|Wuy9AOMTgP(q(^46Rmi3}xK~f*wp=i6Y;;`b>)1FJCwVN-)lVWG zQ3}Yth?jAw_3|bum}K(*&1)Rm&tQ2l;{8$D0ZGIiOK00-A&N$udfc^%@f-6#J3__V z+csVzk-FtiXsmxL{j@%5!>^_=pc|aW1$m{9l0jse0x7-}8ildyK0G4#XL4$$KoZ6p z`*mL4;!%gY^`C7`a6Wdo;}oisXLw6ITReTSe zi=}$ku@~!6lFlcjUh{TPy}~E=Cj(JVF#!4N!XFhS1FC0_9lMk#?Edq=geU?I)IJw- zD5hjV*T4z$vCG+@1*Dcrb;R2oAJYE-)&dl1L0_1kcI2rrL4Wu32&2jh>^W*Ph+ycV z$}-Aan#b5Fdi6?*uhb!P<;0?49}-nN;VPU*w8?!9QJHmyeZ35{%RXcy=5iJJ8s!d^ zmsb~;GBgx`FJ)RJRE?}u;@zgp$QwSy$*)U2TRaBNURtj|r}Ar|d(n~ny#@{OslwMV|vbn#`;?kc@h#Yco5GwJK z+OZWFLigJEmP^yEkY44E1h{sliKd^YCTZFn3(eLWKf2n8 z@t5`zdwm=dj;`qZjlIf187=IBgOj6A!2a1X!GHW-lmnX!*|;Th^i_oned2q1i3Y!4 zV95AtyHNG(T$Ths(_9s#*o`iIE~>-~>yUVUqp>f6BSP+N8Ly=#G&Cq*dnsrh~i zw=3mDO;gJ(@Ov5a&HXOQ3ZQn)4(`Xa!cb3`&7~3JTFH7V3CG7`lkJDI|Gg2R-h~Nd zlMOhbl*l(GT`cQOpKUQPvy&p3i_O08v$8tH6{A4>GT1pefr^y z*4*Y)W4;5w85!;;QakjHeLpeN1Zhg19ef0NI%mE8#-!IpW6L&~)2NEl)pA7rV}+I` zg}%m5isl)`#MYCf#J?mU($!*Icsz z3Sk|(tH#13K9r8i=9BI$wxAj=RV_4QlJZTTo1YwWN1f5=)*L z6C40J;KgL$aX3s)($1QaU7R)n@pi2M|9mACDw#lQMk z>Lcbz%D|omjvW7^EQ0b&b%>x!@m0YE082kn6WhyfB_0)51oE*tUAyq13pryQ^jc&ac_vA zdG;8CYEZuH0Y9(n;4JT z4p!g00oTQ8-1!6{k_i__s@UbkDwOCczQ@d^!V%0{Uu1l)3#8#a%3lcG)7}~& zS`?KwEQwS;>U(jdCg!|Q{5I1~m6J$DNR!PSAipFBcR+ty^94vg+2;h`X`NYOPRD94 z&;Q-4eVB%T7QqWa|EuV*OaF&HNN)Lma|c`N_pP34p=L(iOb`s$&c*-D9z0N$)bk(U z{~>=c$|*dKrbLQ@1hVn~{>L!<|L-qU)|E)n{+sxJ$=&7t5SSbI_xb-E%EO^DUNPVe zIdppbz542f!AVd95EonZ4>cfBl?p?=U+pBE`INyGTlu{T-jqKvAf7GZ{5HQ_eL<%b=Y{fP<;bdXl})xO;a(u+a^=#9V=91X z4>}qr3uKJ`CNnZ+O`5Tu$Mdtd!95LepBT=VWOlF)T0pMDg6)*{bwro zNP{wuV~jK3I{J}&X*dWWZ$gS(BJ>ZNjr=U+4t`WgK8Vwrwx8sy@$`GUj3$)WKrdg@ zKVm%NLS0xGN_HtEwzQ#YZ}?O)g8j6bx7eynMzHH7C%Ik^Ch| zePxk9JYE9Ipr=)Ql^jG8h1@da%)p3VM;KTTcSgO;FRFX5*--x`%i{w>4O`t5IKO#S~kzJw1tG#mv z31x04%W~af9+PfE=_yrXf1m?9z6p<9jzRJlAh%XoaIZRw%Xfd{^&dbM>0On{agTDJ z3&-#%cEhOZCfUwKq%t2A~4<#rU3 z0WkxWiz8oXZFPU&*i&7*a0{W?XF%vz43P2|s( zqyLy}FQ~x-44lrgA#jvKy@|K}bf`yG5H@Om;-TwRulqgG^u+9)N$5jfn^(MwQD#5? zwPw{US;md@(PLL>-1^^{Yb{<>)brrt2)e~iAC9zj8or*B#%QrjP)qffYo>jRKzSjY z^lW~vM^-()UL)`wQ_UXS{5)NG`H&7Qemz=USRJom&0AfQMQN=p(@UTy^FZ?B>4gg9%uLJ08$>mYDcFsx5XVw4(BU*ZHK=7k-`dwZLR~u{Ax5drF>s zs|zSQ!T6U-Vy;yaTO5uD#j(?uBSqoDj6rn3!-7d!BrfpS@Q76sx`5qBlumuyy&KQf zbLr?fPz&T;;h1*{q96!=n&7E*xg6NcZt`%aLQq7;BX23Z+A~KhZM##Ef$+LbYbv9SB5;nCx!g%-^%e&?lN?v9HRp77NK=8VTGe~ku_d#TO^6&2`^}cbyuJ`cZH*yKI z=IB!t=rP@^jrWYTID#o#gTDJ5o-gA;H&u~J>SmB z&$tgRgv!#Z^HYw`?Q+kGb;V}>WP!?4V3&mZf1DU=Qu!U(NV%(Vz4qu9+V$;Qca#HC zC4zfXaL=pGedVdhc#f}SpvYh@jO*Hv5wyyezVU$u7LE|%q38}|zb!v)SIFreD%g`8 zX$@-hv#*p3Odddt3&(em(e{sw35)NTEVUaLFRq=tv?p#RtXFEf9Nry4H|2O_iG-38 z)}MyI+_^1O9yfrs=+K&9)Qlh&_geIdgHWc0710dyS(eF-38zbW1Vcmf^xJ3;pO6>y zhXcYZ7R9Cw3Mi0m!Kqf~7}3G?{YFcOhVZ^oTw2)H`AXpwYeAqz=8tINNagj0r8I(E z0Io=$sHNkBC*qmt_HCZ=`q1KotRLzoLEW?ez%gq%TWES4b7;?Vr9>f~tR0RqV3@Mi zK3CgWan~wf?h!Yk&qyy1OdsiV$WyuEf*2f-%5qAZ?#(6W+K!;*7cfb_mFpfl!vQ`| zPuSK?R<(%8ma-Pa?W z@>xrW%j}h1AZdyUY(t{5vRDs~+PNzpHGa}2mj0Qib_r+ek|k*lA);KowZ0;2ov3gL zkE5M_&@+yyuS-hS=(8Zxaixxbu^YYCqEk>Awwdt8?p~u{pwNzD@;zyK;$m+L6OQ}X z3Zt8E_T?W3IeoV_Or4u5uo$PA^pvnusg(@QfYEq)cg zB+krLyDyZl)an(-EDv;Wl%?e9)vnE>jBvu?9#A~TYGu#wDu#8FPRD9eec7Llm6!Jm zTzu|cY7GM9dM8$EZM#t&K<}ms?w@~7+}Id_mzb{qaN<-LTy|2 zp!eK%v7@KMMrYTSA?@Od!ENtdBpJOr;j!pZdnR*Sl@Bamg}5r4up^&zpril3h4kcl0J4${w$CLQb^Ph;r^Kw0K+%VjzR8^1&-C^2 zT>WVrQ9+Yf9J`cW^bL)@&>}33jh@cD)tu@KBn%07U3mp1HeMFKk7~*mLIcKrYyp>^ zj|!yY7c&=0B2~^Jx>m?e1B>myt^x1f0&@S`WmUG5-X8cckXLPnSeMv?EIFM!s>yIWf2=HRYy0KWSnJ8-5TnfN6T zkJRI9xSmc3u5<{21;i*o{;)RZV6NbNw^21+yMS5pH>>-UcxP|Giw$}rh*%$#^eXGu zRQNZX7*qpn(Zl<5o`td@e%lT&h0)BXNe^)hkqKkU{J~k2f0(Czcre_UUX6#n1Z5nf*k z(A6q^I0+}le0S4r8UtqFRX0MvU>dxRz&($)ixe?eYkk+HWt_?Y4m}ThmIy5CuxQEO z-CTT_uY6xQ7Bx)^&p*F{I4wY&SC7;y`$@B-WjfQad!#a5=EMS)$QJ(zjhNkbcu?oP zy3Ow~qn{W}^Kse48xgfg84z2TdF;QSGD(CtLT~|{eNwY2edlB8%g{UeXVp;&-97hW zcojCR%XB(jK+V$yqCPOpV zE0wIIcwDyZvbh=ZY2B5)C>S-byM7AljTzFoD>Y4H^Svdv;!CreYd*MR>y!)n>9i^g zBulpETaod%X?Z(W-%;4&ZdVY|pE}X->M6?-!$z_&jB`T`o1>7rQ1t-w}( zLP+JV!YMQ~uUD3Nl5Ry@xC+lcX~YScydvxiyL(MA6%fao%AwC&{w4^3mY`#@*}2wE z^sh}|##kLntW@ksF6!$r#xqieTS>N}(c$Bki`7LAxMcAp;}VLi+k^IXa>Km6{NyHg zG*#fTtl1m7^|evQZv)B2r<Bc;+$!qDjN(L3&VKFgz9T&4KG6z%5A?;hTqJ&$OFUG4! zC2Kj_hP?O_;jzzW)okf90`)HbHqBaZAYz?HV$s616lz)4sgC^iHyohri|ZH(W_+t` z_00S@YZg}qi)O7lN3A2=i}S@Yb6>41#%;mvrU3eN&wCo;CRl_B^-?c2rWfE|d!Mg( z|Cyt4>XB-FcZzuQd}rP=-lx)R`}OZ{6=9L!(C-Bp|G0ktiRmt3Mz6Q-k!9}j2<##CI&L2vPS`RJnolfLz&K;mr-(dsypAZD6W4Q+e+eOOS=Fd7kbxu7jw%+Zawe z5*}=O>d7bi>M34-m*bDu;Wmww-cV3M@Ezg&{ft_)X!WM0(e5q+nxcetL)#1*v=>q9SYss^MUKbadI z8W%|c*rXcqZbC-1P;c_lERSHrgo)d@GLhGQS+uxXr1`{`295Teue+z`=r)3YuGft+wmvA-57!5S5 zM0Fu*aN33O?mVT`-_Z^6n^KY0iyV)D>(;``X z>`3gyJ0#%L<(SA8RH28d`&jdo&3Hk)VU%SGX$rp%Tge?{tdPu|6?2!1y=dx=t!n*3 zxXa4DN<=x?Ef;Q;cD$&kej?G#C2_KA?Dw@X=Nw}iS*df9mjk6KFR0O$4>?}ho|}xw2FlO8hr!t6DZP z&Gl;BDE|-e%G^0xFB+|jDOvnV{NzJz6r+NbKVdOjJKRI1(~N8?ki_6%%9LT|!~@wL zHEs@cWdo|=x>i$vdiCjoJOwH;t>v>$PY6LOOkR%R$mWZ*7UlU#|Ju<{w=ay9E@QNq zAnP2g9Cwr4jDBL{D~{;y{gjW#TD<%p03XV4JD>hFa~8%&Z91sk462`9nc z$zei^vPM1bZ-kUkUtk)4A{JF3*9=^m3n#iDrMHa@nP)f!I&v;XL>T?a)5ukgFB@Q* z!;6Bk5~K;h@)NOKCl?^}?;`r^H#JgvynK~rVtp;tQR_aMXDEZyoer5vVquVYCo|kr zyRVCLRMc*1ftMl+Z=MfbNq%;~71efNUQqP#$??HPhUz?5)gQB&| z6X$SZoeBk)9oGJFIab=^H66T*HwZ%CO^NULh{{i`T5`wY^IRxDz6$8vsXVB= z_O(e`U#+$ibPzny8}Z>S-xEAg*jrvmK*m-v-@om+oE6#MdC(UNyRyg)g) znGjRX$9fz36apGfRyLkeA+!U*+f|C=+3r0$O(somk%bktch!^mk*+z{2`fhXSH|-< zi;rVm#&^?i*_>2vl#es_qQg$M!pIg>3|2m1@7kwO_4zvYdM*@4w6KEUtE>O6Lq8kS z^7<7|!sCCU7LnKL+hK;h$<*O!fwp){u3)1!$_W$_eBHUK%#oZAB%4)1ouh#XWJupo zw0RM!wkrPlVT+_89@}?T&gj?1*3Y_TpT*}FP5Ci9pVDk^+K?w(8IJ-x31xUdZAAeU z8V;PK>}<#8A;*mBws=ST%Fn>4#C%Cld@+D3+hm<50jiWmmgcm#DvSYg8E4k#6eR^e_S3I>oplcihEY^oUTGew&Sj1uSXbdhWLG67CrwsU6zb2Ed0&{R;O;Eh*+y#E0hz56_2vd z!c#W!C)!gdgd(6v zuqWpa4r;8hP~~mSqgBBZ+7|~#pNARkxy+THk0!#nD+|~tMr=K+T!hB9oBT2^ic*HX zjx_|0yAl5Ln#_~{Sm;8N*J<%Lo`jJEc2 z0yS3RgczMj$e?z%$}0rRQl>MRQJ^MgNDe1yOq=VIVvz@-9e;(K1X%z(lVU3$?iwJ|RI3hguCO zKGn}uY$S5t-H?0fEOHkKBx*`eeu@|BBd|G3oXUPvxLtS%zkT|K*_K%UWOwekb_{`)-N*H9mjT35H%!pCm5j!G%r4 z6=1f1hba^EX#WG~nB{Lc%Hi#|*J8t6${_Z_Pjssq55RgvI;3}AT`9>xs`(2>|3Hx6 z#mH9(wd!em^rba2qsvVYHREZ^oEdCr?g;2^L2@}ZN1rOhh>MeX>N6^-{Mc)&#`?Z5DCDZ;`25QoBUEnAN|{p-mE zb*2-gEGj&XXVxWbm4;I1t!Zc3S7!5f7iNolbgXf@e@wN>;*fzjFJJU&eLz}!7UhvU z(!V^s%E=1s4j{e$v(zks_prl=7t{mp0FcCoRHi|CjdHd1&5T6cz#g1>88W`{+BGxH zhewVz5i78EIIzueUiYK$c^!Hfb>^A}z;mM&>rrBj^fso*r~v&$Bfa}m_Wj^Q)kcuI zZTrycAwL=&;tqmaOpNi3MwHXvDB3|X9Z>~wSJB{v2vg5H9lbx-{B=Z%{%2tSpa2$zjez)!kg@HiGe?UWQtI5EW41b$@k`oNq&2tSnz2&JZLONXF6x?>%SF zUN8FecWJ&#_L)H+y(NX)(s&@^%ODy>28td%f~?>h5!vh{^_ixa_Mb z7hxhoi1X*B>B6R($jE{=MNhIG8a7i59@GR%a*C?_%!Zm(irHe) z*fjJrc&cocoA(n0>dF{ZxUhpcO^`Ad3kxhE= zb%#a4Z}!KO1LpwnY4K^ttgFc@O*sd=fB!EB zS=3MR^ivwllcKsuy6Ic`SD$KM#-}Jw({y59iMht@H3>_yk2rcO_69wnBb2CCL-a&> zvCIf!^YAhJ&Nfw=2eYR_LHfvg7sr)trRhjW2YD38XHQ30TW@=6+JH!(VUF|>>>76# zylaq?s+N!#wQoAz0dE<%&zK|Sn>;7AuPbNkj;|*c0}zet-5(vtRq6&Z;f4@v~AMqSvn0BR~Evd9_10svRHgCr^+DaQs>8mN6HFZrPJ*>evXrxS8NDkq zEEIqL&M*_!L0G)dYTRqY8|rfE{qD@MZvj?AkXtuoK|4Ozb3^o;!a>OwdLBGZ-)bO7 zd?iO}pWV6|QEM)^+NGC1n>^Pd5O|oc4Bw4o!51J^Cmv8ydT5tuAYdS zd6)V;{#95X+5+hfRb}}GQLsPB`kaR!?b~hEc4N5UTRYjM6L+kIP%h2$uJl29Ikx28 z_{Ia>tA7_p%i0f<&=QzFm&@v@bm>mC$kHKz3{zhZNj0~&(W*xa?RrDeqv1Q$4I^!; z4pn=w{$;ixwYsc;t81Lcv<@fM?OpaY81s@C30*AHj^IGcB7-sf>jR0cR@mZA9(5ebGZ7*lL z6B??wNlcDpwU#v&cz1i}jg~BQ5Spu}8*S3-;wM-!HD_ch)3hW=M!EFf?_5^zi%_Up zx(BUT4;|&H-<*92t-?W%pnVGEaiLO^783`CxZ57JbtEfEvBTc=UZ;j<{l=HyE(?-w zdr%Y?X$#D`DHDj`{pg-#c2N4rak~2W_L=PTsurvw7VC7y1+lEnJ~ZwvF-m3#Kt|C- zfKB0@3R*+GQmA%SX_?B~2jg+p+OXPY0%fGKeWkPW07=6OuC6+;YVVD8YUAtp@ZZl< zG4oL1;$zz-NV*b?I?9;OJa2Y+^9s$@6i!#H2PCVKK8()}SXx#p=$+epz(39_iR+3! z8#Py*7?-%$f>7b?t2nfB$KX z-07JNd(pNw42)_D?7edz49aRX+Gm z^N5l~N>KH;3dBFMujP$qr}e10>O*9FO1|Ypq(l0(stQ#QZh9)r3u&69Ad}!unSZ}c zaP?W}n+Sz8Y{g9@FsAn2$6X{6waIV_dNnGjSl)hQQs~L!IR_3f7mH9a=CQc8YMG@j zk1+d06iH25rFK57?p_v=lXY?$%75BBc_yvs;7cbmBb58jFB_=c97&(1{5op6!PPx! z@A3y(4|6y-w!y`rh5`%K`NIWFLcE-XK{4g(N(7_8c^=@OpL=t!EgaKOI3KR{QS$YO z!XMhke_FTR#Ydz6)|oK_<-!!2?>y<*#ryZ#mhHTRh=9Vi_uU2fs>=>w=q5toKPYAnS$a(z3GTBzaOM7VUku;tnw$QEM=4zGOjb zO{d|a584;RLYURRviz)si(da>x3S;BnGoES;0z&sJT~n>$HCZ_9LJPWzDP=%wuGxv zNO*O~jsEga$AaM8wgw_Zw^8vqzZ+ELQ5fKTCKw^#kD7NI6`ePM`iy_x-0PsF5mBP& z3)*rdWKItRa}h!|=~AP&DklYCjU(?HabQwGe$Sr^LR|j?@XO`~illf`cC?u@i$BS6 zev0+)*~)zYYtD%tuLti33|&e-|-P!^5L_sTWn~2_i3SxEw$>MN?`6dnm9{H!w*cxRI3XZv<>y}CEfQDbXG=#V3eq8R`78#fIxXLl;x|YWVBnra@=%0fsT@z zBr&n-oU4k1k1oJ_%mx1L8~Lu3)_y>lCvz+ScT-NYv6RKM&~FnabTpf(%&+>eI`wF; z3DoP;M7y_qw0|Lf*MPGP@SRaIdADxxjk0U{N2$gzze?cMD@qI8&VJhXfV~1Pk_-R7 z+E4E!OcG7_Xl2fhG;B<^fedFIS2v~93`d`WWT1siP5%Mh52o5_X3!{^X~)E6XE;NA zbLq{EK4U(g()jxN-A@KLy5fvn{UF*az6SI^3W^22`$vHi@~2SA`h@>!KkSr?a+xRT zz>M{8%6QJLEI-V?qft3y-5)DP37&+qk?g;s|EC>u?Eh%ToUmz9+qwL|el*(eeRQ0Z zyH}GqUB$bC=Ag>t8a0CoP$79)kEe<@uG@^Y2$aWeqbiz?D0%^8uJk~Zm|-{hE^x{Z ztNG-!hvm8ITsI;3vIONjUR}Ubd-~NzxdFeZ9se{?P5sI_f0c`tDB)p+Oiz)k*` zd>We@!l(U_RO|V}?gq|N7xePL-i+r=QGOO`BS#K#xo)}*uxITLM>ulf5z*Sa60+1= zDJ6V*zqg~Tx&#@r6em`ju{R}NDW0ascosmBGsSwLv4ObAbwq;JDS5f0b#d+MCbfwV z$wE|F-$O+|ekn0~)UQI-ev${0L!f2}T?;p{wIDIW%ysTcPdtdT!9DKx?%mU>(UTDR zS4=TFPgQN~5bSjkr3jnyA`LXc8q%p_o3NB=Vw_vA%KB@|r_?qqe}$)P4-N1BK)Sj! z(e}-9peKQrVKchv`=u43np$se>Awqt;524#G0_bmvnq)k5Dg!Ll4TzHF= zNLYQOh@*xeNc*dcO@>ZCp2?`M3a%HnirlhEp2Ho+^NWj(IA~8^P2hJ>)e5k*F1W;e zPY;=#+_y0~`$1t)i&fWn$m9|i$6+1n0>#^V7CEEuaKYX0?G3NlURWkPPQn|TD|DY@xz*rktW!n05u-L5O>87z`#)`jz_`*-x^zl zw7`0^3(=k#U&C~TXN;*xU_ZeVJb7`9MyEhZ5!*sBWhnH5BshMv2TJHqbQ*Dee#6_k)Ck`N;I8a>F=TSDWp>))FJep9fcD&-i+v^Q)aOxwDz` zm4i2hxhbYgnVZ3;$@Uar)tT3>Ws>Y<=Xu@ccO2$*E)R9>_s-2#7-#L#{-1t8JB4J) z|G_BL!(GT%jqQ*Y3!g9(9Ho(N(q{IU3|aXGQOwSi;eVynxAF4e&s4Y7C?-yJ?mb)R zBZ~3T6jse->`1GyvSY=C|FRp;AHZea3llrF4Z2(}zuh$ZeR>rm+MD;WDSGy; zldYY-p}cH&_G8Fx+G$>Yh8fMc`5LMN*6;MB&y2jeu5k*H15(i<-XOUs9Eg8+gRZoe zMVaiIYeoMPYM&5B|N4qA-i+jSkD}YpB_@!3%0sbu>DFc+@;+T(P;F9X7_9k*Bgc_* zjVoFIN0(sbXA0Sqy4$GJ=bNrc$j(^Lg4HwiH{$zT@82THLGM^AG43prad@VY-=0V4 z5q#fJd2A5zFnL=TJr2~sdp_)O2SSrah+&2pfFzDtdL-bDWpXI%>y0$t0931qQ{ zQU1>8Sum@vQo>p=>fytGRrbf2dr>iRfD)IQ)a{r;?wz#anf^NT8v^1lw^2p$?_Rdp z?Opur`SZ$(H;a{p1g*iA$3~N>F-k-p_ptX@soeK!@=8JHPBt?cPF(yYfM!_e_6l9K zlMRQd9c72JG@n0M$LT$p-$!mhdqFReeQlg)m-lhArKJw+Y>mWtrC;#yV=)vFmN)`O zQc<0KfHPAu?HQ8i7Y}EP9s2q;4!$lyEs6m@_PmAUGL5rW=}>8E`84gdg1 zDC{0!=25m0D{WAlzfj)1%B+09xIg46+}Na-Znn)`IC>!eT`Xm8|xOe>}i5V9C^AN<-d|l zt#4+|2GVWqC=&~O`B*nj2%x0gZnrllyRGkk!*C^^M7j*vqVBNYc$Cn#%2Xd((UpP=}v;tz*T_2MTJ2q*| zEvdy*g`_>X6|#OULK&x3yZ*l^Q2D?874{KQn;^OWCjVcmHW@p5LdlA8o5$eu|Ea!2 zRP8u9Gbe>Vdp-#Su5J;{Q}p+SEB_yLm&2tv&bZv-RX+!z;ZM-Nrx&QmAPM#^PJ)rE zwM6G`p?t%63r(*3!GWeOs6Q8$>rtiIpBp8=i=Dx8ar@cOT3no74Z^%{kD<@16WnW3 z)7~~!seSXIF@1DjIyId_plLTWoRX;o{lzBSqwUq{1BzHAy1*b9%um{Cd$~dGL|<+Q&9qIooiqC4Sdk zN(?n}VnNpZL+vacX@B|y+fEbWc3YckG8CF;c%&0JzAMa+uwPKIa5cU}Z#D=4!?<>r z1$Q{$Tj?`f$&2njJnZO-bb;FIZfN}`Z?^Nzh?8R})Sr0*?GAgsIw(s|1wh}U{zT_7 z?@|-{z5F4lo5#N#U!%LRt#Vt57rD05yJW1hh47V?UeqWc!+ojyb%+F=x2{jvSm62#CkEUea_`q=~=##c={!i=SOMw)+_a`3$7`>_Tk}px>ZD z$*Iq5xAL-YAongWj2-yO3?nfo##k)G+Xy|7hY>Zt@>n)~O;L)O!b z5YO*S$3Aj(Cnu=J76WDi+8&^@L4NV*zJDje;4CzFph|tF4MmZ%+HYbWD;aYsGAB@7 zzAMLDdXJ)3E!ePEt8Q}VayAmcensgOeP2LrvsNj#$xCx#KwWntQcl#7d6f;w5Sb?6YIU+%LP_R7L}ucsH)RQ)FnKZuNL! z@?8=lC(ekx$MVHfZg3|m0YK;LReD`}T-g+;WidJpYvSLQ_6vy=L(b~UGQt8ZsF{9#ZTLQi+exJ)!77hIqhI2$bn9oE-@88c zT4r!TV`E5$b5*mhPRUB}DVrJ0(}{ztF3u3JGa$p2dz-KpAh$kSUeFINlUat(4A0hs znHO7oYzPVq;w+LnlnKsHI{$9zDw1hsy+yRPSxtB90-N_7AKR<`_DoBE-M~vA**9aG zL{MECieN=ErRxm5GWeksFU*b0g7s+{DJi|H9?UxYCje1DYB(PY|32@gHS9hnb1;b$ zb{Tv|-$O?ZQiBT8axc%TU#p7#ejr*99id;waiLBnf%#sT$rPHiESLyMbXud*Ug6)x z?0P+(oRm4!3@Ytq+lwDbeo*LZTJ>(tCn{G(Q6~}I|Mq#t_iy`Nrdrq%ZdduW1t#$` zbE)ru8QXBNA}BO*m(~}BnNhrTFSHw5hx;BM8q$+3?CD*TUniAU{s&-U28(oJVzM-# z{vGMdozf+Lk<+cFL%n<6W^djsNAI#l2jUUuawdYeBw#LupYcb3aX+f=B*3RME_iYt zn65<_?Wx1ayLEE+ADh8DT zJ0+G;{NDa6Ef-_HS5Ne7m5rMQTRH1#!`Us#MvMEejF*_YIcjr!tNn?^xPDU7x^WFX z>NtYd+bQAtCtZOU zr&V!$BK95x&es}3%2>Q~9`zy0?tJNSROz}J5$O_A)eG=j^L5(YNDCxL`PaEd(aH)c z&#buhl&n{)@lY=voXdao%0d$MEC0YFyV+C@{W;%M${gNin?;4dt|QkdG0E}#_)AXW z8l+u7(AbYf`iwDIWquIIIQteTV(NB(_e=l%MWt1W#b40|>dZC4Qx&k;&yBIrd$Wks z5c;S(GsZ^PJ(s#)bB<{6`>!m?^DMXze^ewbK}!H)%2Tw()pJzG1a8}_C}b*;}RLKe%2Y`tqKFxVujJ_$8bgoq|oAh9ByUc63%y<@PH zOXNn|ox*Dc&CQ z(48AYc0K1NIdic&g+c;2f9j={v4T7wuLMgKS~%)fvY{QMW705-fJ$u^gNdM|-+IiM zox8u1lJ*wz+(nKq3NmhLmNHj;@BY*rxj!nhRmnKcpdc(nAI1+`gf{%jlWhDo?2C;J zjTe+F-e{EX4oUo72>cp6lOV%MA1{Z;ElZ+aWcttO@V*y*HJ$zk-h+q7{B{j@LDTK? zB&31u1a)I2?33jh$4nyc=s+&sB@5=jvU&3*^T?KXaKz2qKj6Y2+)0JYs^5y=@+jQ#ikQH_^_$I_kfFWrJySv_1{KS}^)F1WMe>rc7E?J88tBKKTMxfdSGfE{3w-w+Xhlt zdHCpO5Bj=_|5{jGq$eljP*I8r9c}c*E&}wg{iV&cM}XRc&h+YsWCDc)t@?|R7Dl>R z0cMeoWF>K_R5VJ zCtcMxrbA)mItAfo-0@j0$hoI>zZipb9z4{&;D7s%AkL?i?bEBN7SQj@Zg6*C&=mGtNG-mVsmSx%$>rOu_rB}>Zi`)VV*Py zgf!UbIIRs?l^J~%e50~3vgUK?lFN^=ceLKP`}dgRS?vN{!e2evbpz9IoF!$2qJQS~ zUWRTsP|Glpf9dVNXXf)qeA4@$Aj0bUg%l`x(u&>~KcE*m^gYW{AXDU$*l#nQ!4`OA z-pGVKr*B(^5}HYF%H#TmQ$Ldu4FN37GH5M2JTP|N&fJj}8D6DGxf{QVT^3#J;%q1n zYrk4h-akVW4lmTV3zl;ga?tJI&MHwQiw9pJ$#r^k57RlKT-7EwpwpTScQT)}0L^ZyySfp`VgeZG`mcHniXT&1S+OnRi*EMk zHO`bv9^!Y;`m6`E-5_@kjXldY|gCu|t5tQh&n^s3Py9C-WgKFRI$< zq>NmU=Td2xb&cNuuqr|xIREIFF1VVuuv>Cw=!uPi9%M7Ubu!qy zJL>2&ell-$<3H&%41DF^wXN#9&b|63hoU{RuW<$4cvaqgpYphJbEBj*a&nf{Kpnno zk;>U4)-xsXP+pgA)*K6hOfC|R&K9KWH7CDb1!tX#P)gY9DmBe=9}8C5>b9jUD*v^Y zndpIjVLCRJi6A5mr)j`R1d}fQqoY0tMIp-5E6}@I8|U179Uf=Znw4BR-(50T4~(qE z2W0)QsL03h9FDTnVY$7< zEzNT!Pr@)ku`@Os)8>pb&=K4B;oIvzRBz20T2lP^GwW8P~n@-;|oe!8? zl$i2?-)2D0>lVQx?o&EDm$s0zRj$B4snCNAm!&!hDBV4fqE4Fd=eb^ zo*5syj73e@H7>W>1tNABakGpl^leS9`o&o?M66rS>^Ac;`0CP-%gRN^9 z{M9VQ$VA&Wy9@u4mqksI8Je)1C%ewL>W)4rY+mfNo_J|nu?vmToa5p$t)~e3SZpO9 z%6rL9!%df*OS>XFE4N3fZsCMoe*z>RZcILwA0oXzIqU5}k!id4^z|$>GUKwoqK1OWsnN)$Q#s`xkNRE>})Nf`1V?QDd5pIiiIP zlkDrU7se+gRedrF5EqTIskJ}&h~S4-?U*wL&1~ZfW6$jhYrhzX0#{W+uFw77?Vbo+ zC(d>H1BdV?bRu}(5mCWMJgCvoy0G}{_N*Gdd-E_y(pq$H?HqwqdvFO9S|sYRloVfL zOi1}Cbd3Pq!q+P&xbmDAim$%6Gy`z3EfcMr@I>hJSa*k-ROr(GtlTs9t?boQ8ne{Y zG|hYgp|y?>W!mQ{N9*z80BG>oTjyU%Y;-vDRdBCsi*D{`PZze#)?;=Ao~9YxEvdC) z>e1}rkuBRm*7BqKbU76L3Yoj7IB_WM&sX2}8j`lChsb_O_}nSY-X8#X;lmGIQxY5t znN|0S+-;P)yXuB5FCXy>lYS#Hx%OO*j=!gA#Z)5j-=jtK^!U95e3+sW>gRW%h`;q- z1086|A!LEKvxE7MuZAYhEDY@{v7PCnBh&TJLdB7zgAf3ARi{2y-o!cqeGq=6xK!{K ztW4pvA?F4P6nnns7me68PdskvG4o`)QYv4wp{fl(^2(k+7TOU2ik~sOR}c7X_PKjg z_Ye4P%lY1TOn=c{kk>(FVr`(PLIEr8xD0&G#8#{(QP{iVlXh2$&;#x@)$ct^tUCpo z(hG6RWK1PGu5~Wsgyf^kdpT#2no&uUzZ?u$s*%g;%$7ghn(P=Uw-J)fKfS=Zy5Urh zdyjjx3O87L%}v_!mV6BYUnKv@PU_OueQ$)~x^k6t%6uqi<^D z&(~u%Uo>w+=Ea4#^_ZfRnmfNkZa*Xx8}J~ghF@7VFYhY-v8*NEUVwrd$E1Bdfgb)f zYQPe;9obotOXmtlX~%$D)S1UsQLPp)lZ!fAuCfWH5=d^a6(jDzNyx7Z?btjgpcU^L z6R+|Jb*|3$&#XtX8q#5te-4 zi0DdCz6_1Ya1WQml*76_9&^x{^wxE&@e%vm9ctvHa8@7CsVa32O^~@0- zHE?dnr4EM-qby}w@R;MLh5fK+{Nnqrt??YzrdI3g7jA8zsofITUd$Gq;TQg?NzZ-j zBmF7*uRJc0D1T-bMM_n+BE9LDhr;8;wKnV29mfR}ZK1P2K;1)*{aM9 z1LL9wFl(Q8J{g*Fr^|ioZP&TrZ2r~jSi_NjgVuUlKt)QXHt-k9Ns!crqMwz!c)~Hp zXEe{n{<*H^xoclh?i31#Cd^990CDAqbPp_T_2tMK>1J+;ip$k0zOtNczp~IfYnGOx z=Z!^~E32DSJjYYw0O$G4J)qV&8|yJ5NX?P~MYKgoJ-NAAac^^zAFd1*GtZpv)ko4) z`YENsFoF~e-72fNuwy>yc0k%Q)(`DrdkU16z8l^gc&gruMtNWqr3>=#n>g%yF|D@F zx$7S1G8)Jxb_8eI8)94Hpce6~0VV%JpiUhaQN7bRx0iC%+x^Q0RRw2^xLlF1zn;hW zGy9V6zhz$eiFe;gc{A558@0@pTgU3@YX~+AyyL55v)zt4w1}n5zVLvGE@;s!WaxGy z++Gr9i{tbWq8A-1NF@3mN>q5xBetk;!P*mZe!1RzY-8&pkg5Q5ak%^pa2eS1OyF=9 z`$e(h5h6O^ zFA-wJ=fKZA(4W{e8^N-^8(l4m^YApZx&TQ6me`QBLdwdGMr=#0Q;PR?x&!x2n;FX4 z3HTIfa?somu{3ew?Kq4TX~CEIj)B{bTYJe}dpQ56Cdg*PRd47~W3BjX0?;XLp_W$N%=pdpZcizoI_(*NZr02!pMJb$^-!~R(b@uAl>$k$ob&j zq*eJNV!RL(B9hpCT?MyIY{a(U99-JdMXXir5>{poYiBXy~|-IxLj&r*Nl*E&iLi34$?k75SLB>^>P?#HqCC#~%n ziAOON>ym$>>bO+;L$yt0e0K2d<_HTCT;4Y)Dsc1fw>TRZ-mJT%Om%p%20mjH1D>_& z&e03G1h1#zw%KMB>X2au6W;BByw%Mb_(SZ&B4{6Gj*$V5~2g7UNsf1&e!^$iB@{ond^8GCyx z+?@;9^2lBv)o2f6($gVwH^^ziUGm+%IeOq$wsgPyN`=0u1GoK;mPDld^^fg!Sh%cN zGYN@2znm5txg=@y$13l=7Ukzoxhmb#b;vugaZ+|y&{MJM#>!C5iP$nq;iYG0tSJks zi?fL{IBT!!%9nAP#V*Cj0n2w#?>3=L+l7B?xyj$NQmslx>kzxnyB&n9b>>B6$QM(tW z9aoKQ>}4mNqXgv?t4JIC-0yNQl+`&@1id+lK+j`(Zzo4J8tl2d%3_TVX?Yv{Pq0H~ zi|j7}599CMSw$@-v8Kc$ofR((IS&m#O;P<)5Y9d<=XsO0gCw$DwX!;7b#A4*_&UkO z?MC}`Ryh_(XmWhs+Bgn>cjB=^&X};Y_?OC~w~AUdZY#9HvrvsZ*O^!+$9iq6Ir5Y| zk+b|ir1u}lp?-~Oq?j9P$DKF+qr>4p%2mfQ3;Yql0{(qlN>HWb=!$Wki#11-z#Gan zRJ;F>Jd=Q%Irm3uwas$^ndxWO6JPfhE_douzWgy zcg>%KYOGwGtXbuMv?h83eIUO;P6Q*n3Z_FH#(%H5F#&7fE7^wT9y9r{%6TSL@5e)#t1=4Uu%JTYJ%KPtd7xufF&J=lG5u#`hh@<%a|z_u7FFR5eg{<` zm1#yZ*3hHjeMit9c?xs!r%Se2FU^wYiI&{ES}6_8;Agr1=I)04Wve%7z+|CJ z&TaR_4_yh(*noB8M*P(C?9?*WyW%M#ha#FVH61nBOG~DUTAAO73+( zFN)K8S6V-2Q8};NXX(puReTIp;8YgnMW%3L^^okm_O;1Q$&k8Ix}o7AHF`PGMQk%^ zI%+a4O(oM!T{xSnW{xvNR?A2K?BgN#;a7bEI!N}B2Ct1n+|Tw3zseF82H*GYBX=Cl zK6{?$(fd(EE=b!wb7Q=1wy;+vcjd9o#9} z4_JUSvfvLCiB2b~wQZSlIn*(1n=6LWxtyt#l+%*w zZh~b(w}eAP4k>FC0a10@3QXC}5ha^jVu;D;oh6Qe^>MA= zv{oMV@uoDEj}7bc6!K8?nBi}wGV=7~pNt4fPHe>-uZzlz2kA+3*wF+bmc+EDKRf4a zZtZghVj}1zt{1?5Y771rJwY#M3TrAMkgen-Y@$UqLZQ}H|1Ui zpG=;+bfiib7A_a-c66Zz#YO&fCFO^Rfa>@Cu@ui7xj0s-k7n}wIUl7O-9&DL=NC0w zR21z(TZl!{c7DV>tj)$Y!H$fPYNy>!>W@@c#0}gg^PEX~1a}x#)IJn1S-6fRzTItg zt5~N#H;#gHhf>uU5&`2%|5m5z@Dyn(Ygpr$cMYAjLYb-?;_k1XjJDqfTU`2d zeG=M8k;PfUX}>trxNtStgUY@d$hPk$AXUZw-H zv-BHz2@Kk!#`}cxhgW**+sEG-`BPqr067`|`=j#nMV5 zBxY`5;ZEA_Vu+D#w8L?eKF!xgzgiIMl7M|R zE<-0$Y-akmxTiFKATJ4E5|XqxMInz>iOdruF9_eL7d>%?PS)D*c#9TFkzTZPRi z8-BtN80#~j;T1V{@Ea;d(iy&N}c6aG=H1zzLFfWy092MzV749H`dacy}8eN z^wnl%RTuiH(rf*-A(H!8BGD3Nk;1owRWxGWYpUUC-=*g z5Smy7LJ>P$n=BG0)j_#kJ5zF7(<*R!V?uC9^_yxkuX4l4WMxO%iFrJZX_hc{?ML9ZtB|rq^5{wVnsZ* z5*a~TfBDk+#CfTMlxjCxoBxnB!gS}{l-k@c)qN_Q-~+egI(DPK*v*mknZV&iZLPNjHKPUE;G8SCNWY zI>XKttZgKlX^bnxb*phlxy`_ zy4T}gDi1Czt2JB#7~6tC<`d(!pZ8zCHqLBre%Oh2SugqCibIet5ySHvo<&AuHIscO zhZ`78c&2O`fUX#*#qj;hgBt5ha@sGmdy!YV8u#A``wi8wdr|vWLUn7)o+L5FC@2JZ$_+v?J2ufz!5Tnx+P2howarohT>yA=8+5fr@w_`x+(4Y9S`{e;ZadLE1b{n8LToWY!qxnoB zYcrOIKh9i*&_Trfm6D{$z4@)@!IVwm>6>&^o7ex46djL3oQB1z!thEQDW~4yj|N_n z;ql0c-WKk?DI4=y!#WxKfRq#>V!&6f#7&oe=v#)4wZF$2{b;_DDuTZXs4%CwQl)-m z+m(2+A!w)QdG|Dvisa~(Gmo_#t=z&S<^WrAqbx}m#*xK32A9gt=3t8dhm@p|gETel zbU%NUho9b~5zIu#n=?0Ftp$e(bi?9L;51rA5G%*EgoQZk04LLUr>xDrWO81AM)c^l2)u(+6$=$pu;%JnNCG?6Z-7(>up+|qIl8Uh?rzmO5;+p@w zv%psjXp9s>Zex_n#=`y95>FmDRCe@zkH-UNHfLZv!g z5L&Fi!B<8XmdozIxXIt5YDq5iBGZhRy{8{TLp5Fu$1 z`O&<;kX9s&fcz$@Q5)4Y#$)X-;az7i=TRy+Vv3+#716#)K*0N}M9w{&|CmG33*0T= z_uM=;D79 z;3!gqKZrMx|KLnnhw<*$Ij}=^?V6SX2fJEzeV*w@?Z}T734DK>AI@hjvCe8N#Vhhe z=m&qj<`PUP37$7EhMy&XrBg9cLq2#nOlf{JC{UERBXz5{9ysvXJQ8HCtu|J=v?KLo zgbyxjwg48ZDtfjM#OG%kUwC!%2W>OH?dnWE4}}0A7EoOO8%=9FIS#c}JhFMTTV#aL zBq?}_-uyES5tvqOA$w19UUSmhOA~K~H+nzmx{CU*+%@b8330zucZ(cA|NnOQMbL57 zCi3fK5u*Q#z5I!xQ;G0d!K>lmpa1{d@QC_!6;)dVv@#(IfS9v^UXT`*3>Q)nP*F0( z+|6O*kz-NGNUtl#3xVWRkdq|e@~gQrMX+C;v;Rkd#fW&FHo@rH@_VMa!00tO@jvE1 z*{Js~e#t#C)?IeUN919(_hi~UlA&kOR^o7s8O=T_i&r8O^rP=XWd~;#99)da$gyB$ zKh27~;gzF!IdK|Y870+CpWr8f*WpgOm(0bX)}PsxLT!2XliR|vliM)UCP=h$K1R}n zztl&r8JMQX!4#c6#)iQj9V_x0wYKO+}B3!iJ@q9+x6Qx}FDFnO3=Dls~xCXD>- zMqx!l4iL$W&PDa#vInPbO@((s{Oh~6OpfYo5g!tfP-=S6dzn1rlj0V6y7#}cUH=F) z&xC)Ve81c&i2!N==XEB_%iR;}PXteZXf8J{dIJ+(S5beu+VkxBkM zUp)RNF0pMAz5X823mkkxe-8a7J^I8lg~%sxtpof!_HxA}9ZnOjhkT-8_{&PYy7Ucn zxljD}W8ahXYil9AECo0wmg%he#@5vgDwn^&%gtzM1el_zk&opmu|LTqal>vE?+0pB zimmBMI>l{XS1%(SCh$n;#CNW^C;Wl@_QNq;^+wLj$0CSq*e8|A#b0$9{2= zI>7xam#C(b9vr&@n zPkmAZtgYwhZ5|$eK7x|DSeMnOkmn=M_D+Hi)L%dRKtE2%csxx>MZ69Q>#;o?!M$5O zT#@QFiS`QXJ3~84#r&vF(JWY+G9nTT>e3G>(zc$N&Vzhy9J)R z7qKJ&%llDRvZj5jJ-L*pCpi1dI^P}d@&=5O{J=vZy(uDjNYkg|gm3(!KhJ!|=(=Zq zsk-HcT3;MUfiA@-+8DL2d~$hRvRhz#_*IUQ;x8$U+;l!4&xHe77abbA2%g1)23-9K z^lFk_Ku%UTPLw)){r10Qv1RpcAL{u{G&N?L_3jsYq#D!)xbM|mO=zA$_kE?{d$4JS zim)2AE=;z8C4M|Z~sFQc}ZyZ@n4?x zzuvVq887>ts83FDC}I1LFL$fUR1D%IFb$8e z{dTj6=i{+tBKSf|ACk;;A@n;oFS$AomF^da^bT}FyhST{*V`%)`#Z(JuwkPVxci3b z8^LgYoeT$<-c6eKAF|-)p6Hyl8PL=aH02Ab+cCQWnMV|b?RH5S5A|COk39jXgzdfFHSk)l^~sY-PcOrtNF)j@x1m zg7*BHZdIV-O&QcJTRV1ZEx8aud?r6IIVENWeXq3XrPHzB9~Z9sI8ofjRi3b>*XM*I zsZ!N0k2Z6hy-fI~F_9bR7@JOXyVOP!Z?5skS9o%sL#nAsE6Cb4=JEwP(qbp`UeY8H z1xILu-+czl;OGQF4$f7-_?fCyj3`( zxTfm{(S4@uv|5az6_RkVd6bBXQzY&n?kE_AOEiw;#+7`M1CqJEGrx5P8_q|hA~eSWp5Tl2N{xh#6; zX5hnURLR7xf6bhx_Dc;%&A1SJNRm6Z@6ja02S(v~LS^b?!!T>lm?IiGKFq53t1Z*) zl(~M1asqfLMdHVJ*jk-lVPq3u*!u0t5LPMV{^wNxQGg2vS1io~=ph{8&A!ewPJ zgflEmoU_JYeaQrNAGEzqa4vqpq4_PoL0Tl6G1~4587CxgWIytd^&YTo-DFQUxbc0q z`uHZUF%B|##o1BT5I50$6`jCt_TklRx*|yOK%ns78l)Am@X3lqF_X`pFTP?dpb5BpTV$>93YX zL+n>%l-N= z@0u~S-knq`gnF2%F(oU35{VwJKEl|zYDQ4~cr?KA9YEEBx$ zd7$RC}0mr>2|as@0rt zJRGRW>C9Su9}pSSFiOGR0Vlr{uj~v38Yi1mx-EuKk-zb;KQPPx$fCGnA`Q{9H_@TB zO7btZbFG9Xay7Q0c#wFX`DFG#ap$&TTtdT^ueMc!#dR|d7X5*<@LUm&mXa_nn4%c$ zyz3!~=t#y`O$qUHcP9o{3<$HCWq!XRe!;hCRIimbd(%CV{GhM(BOyrk%Y=gE_7vH{ z;<=5xSISs8x78f-WdYkFIXgsSRM(chwC_EdJe+g^4BZOND^x2vyuIuxI>=~(q@^f$ zFg~P0J-!!A>>yPoM?(y=*miJs0@Y1?K3<^Zv{~iDY(J^yr;?Mq&#uD8gcB^8BRx~m z-NErEvEnTssNW%QT67C>#%Jdn*UuQob!3KBD`S@kP>YFYW|5ELeOx{`5k9aY*}U+n zsGkr<1`VaimuGmNC=l{pHWG7^RL)=1@Rz0yFU%%oZ>Q@P_K+HTSb5o%zL(S-9b}Re z?WL>je||NvkaA^w;3pSuPaw_Y4D7V)bZb8xZ(wM&*+Q1ZZGMBfqbpH;a3@Kt=cPo6{U{gZqXlh6do=#maBo;PyG!;rqF~8` ztua%0xZI091*OUOLO6)~nKy6gs5PaoB(F1+IuQ7pu+KycnRk9wOI(^%ER5dt$47@C z7Q|3TgL_!dI_`ODq`-^A*}z|zJ(A%d!q5ZLUM>m}|E8vF8Hy*Pl+9)9*3s#XxRRK& z)o|~Bsaqw_C?V&kRTcMIhW{a%jgfJaR9>tC2HbG|WN(P`v*-2f(6-YhPan_|`uK0X z@YxG1%xy)mHs}0nScb`8OgQJUSf|5TI1iEDV^X}S)bkOVYvPnW98$Xwitq%PQ5dfE z-l^aavxoO9=Wk4KOmthyN-w&LmK&%cqP--C8!NZ|*7c3^u2B&uD$(`wvs0Y4YYaoX z1#Ho-eD&zZs<=vJU#h(Q(8~3GKcC$vpZeItGdJ-4dp~+qJglfkyyFodNaij0x}~t3 z$dBr|pXHskG~ezGS8={wc)Z-3q_nGIsf(y`JyOzu%nnYsTl5hTwduh{2c1Q$rU-rL zFH*z&XbqmfH_tVexwhm5dH#q6s>~6NU$7hykHRj~;~@*;bo}~#-3BrHF~{wUC5K+> zfC-L6^$n&}Z0mD5>d_g|48a6|4NXkicDZIgOs(kbPVALjz;Qrdtm~j=VIxo~@Y%?F z>qK>;iBl1L{UpX*s};Gha&*T#{8NV45H}{wOqxE8I38SWqPN)O>tS-?0NDY^Y>yRZ zfFTvMIo1if?jr93vzxCEjKrnz+(es*9o$~t3zXCcJ#rfDGy^CZn&Ss3D8nXkEHs*V zqY0;I7D>yO*8kDHUl8XkZ`UxkUL*o2p7UB~$HPD9z0tZICV?MYs!O)MdcCq}KK29K zPD*z3DqObJh>QW5N}2y5@ex4EOQ&VO`bNDdap~?_9yB|Dd6$R_xL}QHw}SugjQJ~9 z`^jcS)xlhoL;<3qzu`M--sodP6^d6>5|66>Dm zzFT7&+64vF<$B#Pv+?UG#5-rK{{v2jFH3 z;$9+|*2@3i~I`9LyyFbddMP|{RU5HxoPuUkQ8>#HU4tU$$ zhrh-|F@hXEqwZWy+Lm`*#6`MEsL9UXu+Men_TBc*^f>$p1?Pn*y!UA#DEN_6iFNRg zT~&p+m;Ss|poSzyqxZ^Sp~<3=Ahc5{++36H{x4<8ADxDYjXd^QXeCm8tbUzFYd7s_o30^iX)QWE%x(A`<9PNS2VTBEuLqy zAoi|#KUt1-o{N}`2oS>c>Spc6sliXWWb|p;{!}}y?y9Q)>qm;D?D4{Vnsc^})bM9b zE1;((?G8Ta_1^9RKn$q!F*2DC5EdOyjX@a&9F#!QzQ9W=y;m}XPB$t1XpMcH=eFmn zYVW}vXlr2EB`S30qNS8~)l8VBV1+i^gFBhdLqjTnR2{L|l^Zs~w`Cv?ro1zsaMJ{a zUvku?p!7~FwhnZc$Oze>lJSD#jkt%z&e}s~s8!e2s~j3~D|Gg_F|8P8YcARn;a>f% z5V;w`=Y@wg)MQ6b0M2&9YSNk;HCnaNu$d8wKrbpLo54R4jU0iXL7boW0bl=JuM{fNNT{C`NBq3LjXvV)9}(;8D3 zAj#eBku8&L$%=34sN59^Hi&%`Rca!>WlD5Hr|E(`;>WyOm6!K$brU`zJM%-%3Z+># z05vq^?@-ToS@Ixc3WioG+eT-50ON?>bfRd)JDlB% zA)k4B;;qN~f?fle*NAOS9-F&QkUVqzjJpr5@z6-t7|yxAWLD1cKy*i}1H;iOJQ*&d z#m%+Y)X@GGMRAza0Q+m$N3_!h;1+z#);0}MPY`|JQm+Iqvd9oOg?ngB=kT~EB=l@Y zJ)M|n*+S+(;&A^`NQKmJurK^{JPSFl-A{d}$z0FIX=52X?{dc(|GS*QUEq0=Vx0Q9 zfQyY~*A_!#%cpWx7(;!tHQn~&-k0W&;TU#1k3U<>M}IS(P;ETdJaTvM^bNM}9F*Al zt6hmh7L68<-E3dnXZD2J=Y^y4INSNZ@QsYI_b@anx9p=i;tPyPfch7pDO5p+$OzGp zX^s;jO~j;26ryAKInOBsiAyNjkzG^v-n(YP)2(Ez>LAV>SX^A*T27tL$Zb(yW&T?IMp zTibc-G%ZH9n|u0$O2LHM|boSP^7gX9_O zNk3X^o1d7o`x}@i`T*u&!*Aw>`@39+_^M4M9-Ah_Jwm@B!a|>ZdsPiBQ=aU^Ggo!1 z@f21uSyP~*o`tv4(D0Un@G@20Sd&O@l*bwu#pO)BUy4AUG)YYvOT(;?An-oaq_shg zfln!x$B>9ebW_KorqoE^SkGt`Js1H?xZENWkOAi5lz!Gm{u=rYt^2lms{56A+D9K= z0m3u_4=DrKe{n$}y(a|;f$#bQU?4oqk#$56PmNSg<{UplTzcz;-{8Dan2tHt$jtuJ z9$YO{HsEUzAG11>HC!xiU46EQ=9;T<1F_ssJ@bC6G$Q_*Uf1cCMM7IIzZb41%yfJ{d&`vu;*|FB~{X%=4xktNvb~lIn+-j zv$mo?nR2OGLE6W4ffEN|ST>A5Z(ybODl_{q*Kqa-dwuha^;peLMTRAPww z72JMJ*`)`AkAz7m*07KVN8ZSTXW`8lY?J$mO7I&MGkScW<9eeb<@jZtQ48VV2u+FO zQEGShQAWj|dt@I?L~%_Aale<>bW{#n`~weBTDeg+$C zEKS7`Ic)HCK-InV!YTNdld<5V3~u}PZ>bze9A#AHf!zls1mlO5UPvQmk-Q~bCus@e_w!@0LpM%jxxF1MSYP;TdN zbE4r3hKA1BAib9Wk<3n`4|m4T(}BN&MB+%A5XG(O3}oH)?3qxMnm;)?4*tJX?{Ksf*cHq%{te zB#n;Qr>3q$%du@K!jWxTH45zJ?fGL=Gs?@3q3=bPs3@I%rMLOC4K9(MW(hW#Z+ueHKi;jsg2yPQ zCiHzwV2x${UJ782M-r{ZGbb&&S;(fn=`4g)n?I>k@PA?CHi|wh3;<8^cw4yWs=8V+ z<+qO?YW8c!iTRm?Jy0!t`VT3*v5HFK?cg3Uh*<2~?8+DM+H}1AOUnexZ5+?{m=ip< zIV{9VE_B5s;*UW#v4`PUZ84Oj_r{q7C%4ngn$&_Xep918U3GdL!v_@1Y;sSOZAr2N;>aK8$$yJxEA0(HqM!{? zpB9$_ZAE#W-iVd{Onf&`qB2I5e@ER5;XOpYt>z!lC0`f%n{n2+$lFu2w9aQ_EWZQ5 z)Oh!l{+oD=202IpPWm7ON*fcYKG&mnkM{fMv3PmhdRF&h+iNHswwcvomA> ztdGc-?cMVC_O^lFhwJ*!rJI63jJ8$Uo*A7HeksnF23~d@nfE*vGjL}-cKG=tIJ+h* z#tewFr#t=ImwJ}k0KP%v1@s&e_tg=SU7Wf+Mltn(+cLLoO~3{HvL>K8)YMu#d1eK( zI3dBkLWRS+<|9ggVH2<>%rz=ElmH>X`DF=#KD+*h zBtY1^O|?<-Q1kcd$k)ZBae?dR$-8u7_mX0ts0XK3iSm0JQ{dd-Sbzxo*>{-=Z~|-O zZcVZ=_6BsYu7|{_s9TMLROT~6RsKLNSLB-V9}>Q}G)s+B@mM+V$Pz8dt^K$HORd+)EGRgE8=~~d-&X=_!!WAuG6S( zEz}`}pkJhi)%I7w9%r8;lWbP5AF1wt+BO%~t8D3?{P9kk(OEyB?NJjXfQo4c+*Xla z=teVj&LV=NjSNaLJi#O*K!>C{OmlSoZ>GDwiRhP00k9_8HRkU~1@rY#9aHo? zK&>F6r5XkPVdch9U6a7df7_fFuAqyCXXRnHI6^@G1>g`wtYCGEkw8kO1D$lIL|VJw z7lDgZq#Cnc*dMR>Kn*GsUCS6!R@He5c{U~#h%YB>aO=1c zCi3o6)i!baPhO784?d+!B`8fqJ{%b;Vt~emg~}5*Bkq2DbF13}Cb=#MSp1&`FBRWI zY#|L~e#$G|>_e6P{yprM?WLqD<{f zKCLrn$B&lkS=rPgU6Y!rQG?g2I8PYn;K9e8U&skpx}l(3Gb-l9op;Ie^7QPybn~i+ z0@zPE>08*`WQPXHF?k&kd$9xKds_uD&k*|~!T^Hq=g{x_Pfh*<#zghgnLW8nlB{(L zTqXUEH-xi{|?)OOp^MA z*4duEkv%3SfdZ@S-$7!i#iBgb83q-^AzJ$#>g_G_ZUQOn2XKNh-BSRe;?{~=}n z`nR2ZclFxAMKu)JZd9X>pmc2Xxu68)txvA5GVr1$iavJ=gbgj7<~i38r2kn1<_g{1ou8FXZM84dJ$IvNU&m5OCWn`$Kum3T3K z!fFB?j=Yx6h}bFTXCvOOm|qxmDY#@HV-Q+w`)}<6PdzZ%xUrip&yYMoODKcRvhFn zTw*jwsZVr6EMp)dn5N`t$<6Q0BnucbY^*Qz2jDet!MQ3CH@W!wf~&$#Dj$|~&ELzQ zX(!D0JS;N8idc1mqJXYmo^F_?7kt@;k2UugQuZFcmRnW2y>%R#%Qmm-3CtR(&b?1F zdO@UbN5M3N!O^-(<_L*B+`v8GEmOtoiYCR)f6SN~ge(6Q%%sj1x_qS; zjwDY}iXuy)7rX13*2>NcUpkq>Ak{3BQJt`bZgx$eTI)LQMO`5%p~G;gEzS#z)?QmE zRHtE81{oSlR@?N%FbHsZvp9C?>3|sXdJ59kgg>hRs}3bu$w!3B$_J+n+ZWEqND@Te zn)9?$+y)?_IH05R0{rNvU+M3+@d-PFy`A!hik3&%jBSZSp`-HU%meL!Ii9K0+ZM`0 z8wsf`XjFCejS^f+v&Od z6KWHaA+`SS>3&<5lafsrqN7g6=EakC#k=ocd@W#GLXec(;c9@Bdn}38PxCP{)SMfQ z*DJmGf(+cR;P&*h!NO^25Dwu>Da*tbqm25{cXTnh;M{#??>cSRHt9x?_cUzoP~X^Lt3q3*$q z!opJLE8f8y>|2<@)2q9SO0y}$>uVlKtJnWw?=7RE{=Rr&6cLe9xLoW$2-#XUGAD@%jGhUu!)t?^^f8eZyMJoHb{k z9p~(`&)%OsOj4N$UFuK3!k#B%&_V+oD?0 z%keB?iM?@$!4?yPK$&za;F<a9@KGp#IpE*(_tzDkFL2-lCM zKp6d_)%{Fx)OKsEgOSlwSxFyw(R=wzyvi?lMR#I5H{!*0edkb$g1`wK*j&J(p%^3L~VK16131Y4;t;HRkqC zwsD88Ud!8LF8d5D@lepbaSNRJ?&oOG2_MLZeZXC01A?bb3t*RR}ZV5{u$#Iu_!R?&i7BBV$2S2V-?|UtwryzMxf|hcFJpn^GTT3Sk)qNeWqHjyJ(X%N^&}R9i zz?f}ayJsbZw<1mBIUxN$)0@><$Z&ReS`137XxFvKUPNzh_o*sH)`8x*H;t!lK)>6M z3`?CP-dMg|_J*Sln6tVdvVnT2MBMVl5A_QJvUMooxsBoMy`;lI>V+@Cwmk@YBc2Zj zrHN@qaI^de{sikg(1}Iiy3I0cCa+4-qEklwbBwu};1vC_G+VTy^b@_J$`#;PdFSv8 zdeQdT?<2BTrQJ@b&}|-Mr)v^zve~%gyfaNv0p*EB8Ltn{?o5@`2gZTNZoJZb$eQB# zM?Cg3U_l&oN)-27Py`+JB5%&B%QeGH8h2e2M`>P8cbua(hqckZd^DE(?>8aF#J?joIeVqKd|<$8l$f_9@L0|D38`__iaS zy~K0sW0OO8c=%bgE3r;XjlDNnAw9ej ziEgH-)KrABz#{ihrlGb_;z$jpjilavcP*FU;&THbpnOQlkyP(;8=bpR!|i%}j?>^+ z5pnZ+uWXX+se~pL=4D{uo%8CL)xi~!hTLt>GWo!Z;wJrg^wi1)4K%KnH{NdyyR^?XS3`Dz-fHr{B*^ooID$T+d;X9; zDd+REU!tXaJ2B>>#jieD!Yi1UE*UAqQV)umYqfT2i$|_! zg~@3r?8I2NOE!H!)A7pOdhwDFk>Af;W-|zF5nFq0$R9jCUu?^4o^Omhd0vv-j7JB~ zku2|Gordgd#oA(*!Rof}$Za}H!ahRi!fMM*4w>#GUTM;1)DB2@;A_iz*ew;_U5Z7h zePnwB{%}R5I3O@Nmyy&0^|j3QE&QZUroJ%YhAkIEaaZJ99&rEA-%-06^f&{uBE8Z# z&xh#Fx?c2RHp^_jcG}aw7cWC)BW$hDAVRkq&cY}*DZAu}Cb=uu!@c_P4dK2@d|T9p zlxSITV(m%!mEx-$JBqkFA)PB{SQ!ipZGrKD9`2|;e#)X1(SE)Kj3tW306d%Y)6O3s z$ypQgRPK)Xt-K~1-h|~^Yd(yvJ_=if<{R?AdL{8TS0+WFZ|LR@MmCy+l;V<=f?)&W zdWR*~O`tyBf9Gmed+^EgQMv$&$RU$v(E~+yIcvjv#gXjo@nQR#$4oiBJ}b)t8aB)i z!*&l8iv_kBzFs}wn1QA7Q>-`PjfrA1PD3*qqB#d?u}R+Wt$!65l0mnICsz4UU&>Pb4^L%TbG=nlK?RqfkTufb~U04#F@Qpp`e z>3dDmWQdj;6lf9nfI^VAzgcXMf=2VM%0WCoKcS|$j$wM};q=!-G?|{K}(N?$^9DyWWExvxmVaCC2o`X`SIyHDa9%=Vgh{z%hD@SCL;WNHRlPL z(ts!>4Fca-L>npw)0=Y5OX{j*gujrHVv@1y!J}!`H+)T{Qok{%w5>Gx7bZhsg}7~4 z&CBs@M+K5B2N#njAwoeVjdHUQ(pnK_GS}w}c2HgeZ<&#NgAo^~vedWh%OZ@osj%di z!%x-jbjiFe5$7>E5jaEb8qgNe!`3{9#u=4Jyob!=*;rJl+t`a+c33m*yhm*le_q}% z)b3ilojp7VK);TVmtpZ6>OR)BlqZqOscmA&GfKzk4waIa<)<{jlB78+R}l^>`h!kR z39`a_sHxaB#J=p}_omTBsolh{HqadxH_@Hd_~7#XPkds#ldsg(W1S?$2)Z`GYBMgY zY})*arKH4TCiorA1MJF_Y7YK*0h?a5&*bGDupNn%2dB~(L!s|eB*E9aZJ##WFX+(7 zV$J*1rBmgF3e`&1K}pNl8Y)T)MU+w)0}{awTnnaOc*SYHM{O(wpuL<_6D)Mdmbm?7 z@-_~Y^jY1zv=&$Lco-h}Pfn8(JfCC=2-tp)bIIDnLMqDu6hJOvj2VV#D+=DWJRG0@ zdYoyI8Pt>MyJF|0*i95V6S9^EKCv^y*pcA|+PnXe1kc_n3LK8$ zP#G%_22X(l=-R!drjBE)mcB2oLOPESC3?+J5W_*NlQ}K3X#s>44;O!9$Y<_yWc~O5 zjuqsH8l~SDyU%udgLr1qP5`+4miXQOU&vh`=l1j6m}j5=uN(0<7VymfAuM13+#XW~ zKGX?x`|$-3Af{8QzyhKJAO@dpNQo=**?opmfKeP&xrGs|}8haVuGex0I(>%d5$a+ma40I@niPKWi~3-ox=m}}|dieQMbz>~hl zlYI95Qw|J|N-^wA)Nq8Mc?!$hSP#bgdS(w8^Y6C~&Z68m>_2}m>TH{%&GK>^6%dcm z3~|jqG{M`gZmjU>2rbA7{3NhNx%yglu=3(Jo>`JuC{r;OoYE+JaMUN+44rxMp z-%!3Hpj5`*BjA`Vq|!>#Tv-iY-S^ycC#rh35(om0UB<7oL2s@<*c&DOSffIxkk;dr z`zN#%e3NOt&(qfY47csOz77D-=Bm{&?{w}=EY~o-UQZR9aChkFo2i4lzmA%!hqNYd z;oF$?>L4Llpy|^^5b&1ywS953#d>9Z(c)`@#6>CveiQawBY537NCEulr=PAMWuDHg zWva1(ZlSbAS8>r*m@^i}=v`UbejzI5-A)Mn1*v0Eb`WF@oJbzi?&`atK@R!Qu*|)@ zDVzk2!j$f^T5VJVD*u9em+*e%`%PWS!GW5O^fM&4(|PEq-xP5tDDCm-;;3u7W%<6z zN>j08C)qquB@yFwCD`!_i{Sfc+@f**TB5#lyBLgPpYTwX8i%!>&c@or8N}u*y^+f+ zwo~JvRjNGgg9PIrUxlm8=BSRs&=5HXR|@-$o)B7ie_q6T42WQ*;Y{DzlQAt*>rtAQ z?H06$nc~oNC#qAzFRag~I?-D=k$ypN*mXt8x+T+G(kS*R&iZKHa{lud;k&sU!{?@i z22)Em?4Cu*9Wn zNTZ~Ge0M}iyD$|`R%^Ilt$ZqZzU}7vQ-yw9c3=n zM)Wr&y520jScD2Ah#pmr9eBR`Nhu|7M@i_OrF0DX!$g@s_8~ioYs!+$Y`a4G-liwl*ADLUclFT#wanDSjoN7?FBUeLg z64%XA5q4~wQQTB~hvp-7R+Z$s&eU698b^KQHb?I(i4bh->L57;ebj;AqLk6IO>rti ze7+BS!g2Og0|PP_)(>!b)M8^K9bwCgu!x3I;kxZ!r?F!$0Ih0Fm1$8J+Pw}-?ec^}-TYa3;J=-PEu9pqVPw@Tt&Lu_QPg^XEI zeO8Y&-AY}4K=uJhPQ~EE6KG=a5!%ulc6Ke@)8pO!745gSxV4ZuD|ggWd$SZ&lsh|{ zaNjSy;nr4;%6l-Yz?k|&dQe*})L$bSUxcp}d zEwSDgvy(c=FNoZf&}7v0PSl%eu7*H^qvRK4G7F0t(G9{;WjBPEHV;K=)Yf9uMPOuA zrK`8Kl?=IT0A|NEjEs}a;QU#8#yXP+Nj}5lq4C#qC|npV|5=Xkit7Fy$5OTFk7WhU zFPOcftWe|0q1d9^S>QE#x?Q9Gh_Eudr2gRYA^73|C3R5Z?K!hSILB<$g6>+xSj5G?&$t)$O59pW^2bB3EN$b%Ye3Zn z%8lyuQWw#YmSxCm^hTEmJz?d+OX2zQhZ>Aas+@iivJ7^gEwn{!v|7@VC#z(!i>Y0Oy%`>ILDbgZ7p3t>fjM*K!S zXsnJ8ywVTvIo){`3>48R0w3sD2Od zR5Pw2O#1dqSGF^&*am5GDblw;ASWE1Z(cOc6ks__+a;kp3)_}|*e5_r@Ar(!w>(80 z-*syB>8y!LuxlV{VR0{y^!(PcO9PHRR&Tt)YpjJz#vITTxjJ0Y?Hqgtcg5+>N(SOj zRGF9mTA_(lOX^i=p2EI8v_J=Il{UxTCJuWn#kVmzFDSreO|fs<44RVXwi;T*{5fX z)4`_bLojZpVDdePF|F%clsW(zV-BB>W~?SNi*R(k)9n5fP#4#JAyf>s{PUD2M3~=15MO)Q|C~e%K6CX zGeWXaSeBIZW8!gvs?_b1RJOM|k6ha^^tz-RzrlI>$2Hb3!dW6e|N2q%^kn;plw5~F z%bg;2kh@26OOwCtn&x_5WGxSKA8_BjJqwWF5yLswluUafp*ti^h{xF*GyzXT z!upq~tvX|^mcukRbi&jHlo0q_u@iX<$=a79)5f z&hv2gpg9c(++H`%3VS`!^)3yWg9**ACOb49&j$jhv*uQo zM?~WVyvk=Os7X#udj7fm;>vOQNSb}WH6h_MGOl7lF0ew@-J2nCxi_5~mo(B>Ys*F2 z*qbziHMA5IK?Z)z&Ehvu3Z^Ztcb6o{r}%99ky0d~qbHno9y4=v7DWn+qwX_`_&}Q7 zq=#_u4e&1NW2X*t*uuqPExE7!OUhoxa(s9ebfS;qH0i7zp11%5*x<%#lKLBttJKIE zWdwIqi2~vm)c3+kQ&sX?1|yLwgFmxp`&a&e3sO~i1b1;EYj~fVA|Jf}b{r)iJ(l2+ zn6EeZhLNIDU13`1`?>A+^HIHT?GNh0nGMZEe*M~00qV>?2E60=n$ShZHiKd=MYi>( zj+b%H`=*}u6_XS|2Wj4>dEVWOlwp78CZPTz*8Vg&4=J;4edst++&K%u1t%I^4z!|+ z*KTFHFJq2U9}hl+KudrwWNG8Nj&D{B#Wvh&IlwSb-=Qn)wO%8+Fq<`2W8rNPpF3xl z>NlIA*z&b6qA%y##LmOvNK*k zgB0ubTpbp68%;;7VcnrbmvBT=L->1IBnvCq{x$ z3BqTGqePDVhpq)}jmGKmS`nUR#pFG%L>4S-BJUiMbmLy+ zkw>UeBp8sB(z>h`$K*)WwS&u=X*>$mz&P3;G@AQ?!rs6K%OU6*N@YpF@YsPGcbe(G z*fA@mk?I{<%%e%U*t@Sd=+tDm7N;hUTtR({c|{~?x zL5&Yz4R%k=bzfAmKIES={f$AtOF2;e#F1__w3V!aJOX8G4FeI{eYL;1xW~V6Rr9ok z>4@|2)Pq3u7kg})wscPH8y&C+Yolr7ZleWw%$U~#gw43X*2EZsERKP_+M7e;BwUx5}j>-kAX+2oEh2> z(O-7&3bM6B@_t|!X@+CrWof=Nn)DwQqLXY375On!7Ij( z`a=6`Nm={A(MGF%#<Gje<+J@P(?Fs8a3(!GMV-7)Ddef4eXX2FY|UchWm{G9oi7 zf`s8`yKWccTtt&@!-Pj&LpE3~l4mBfjxrd?X&rcvLn# zLvOf+&rXS>+2CNvQ$QwFE+Z8At?ly)&HaWE3zAK-14T5UFC%js(ldn$cQZ|o@~e7F zP4<;o>Kp8GRt$U(Rt!U44-c7~_nRU#LQ&Rht3>MgAp4U^yrwBbL$x;QNw3YjUj{8n zufG1=$V~w?Yd4`pRMEoZ(yS??X!@F6cb6b@|1t+?&RlsZ^{t3r?G4s=&g|ytw?3xR z*U?L9+SQoc;ucw@$kEqz@U6ix#;@U$QvU9o(*{@Zmfl_uz9@^0km6O%3#GPV$Gqy` z36KA@p%YK+rV|u+kFhesbj3hxzm0OE%2b*VK{^36Ln$>@7P?tkG8%UhK8)sq5eY`9 zFS>`#i(Zyr_<7Uw0EfWi@)!@`;r}gKfyiXJCZe*P-%=rB>XxTBJIV0TXKisy(%nve zWLu@*fCMh?I5W}2sCI%5yGyC-iXu*=m7z?F8ck?=clC>#UMQ#ZV-STLtl5;#-LA1< z!tzNg-&L-GdG~JIGJCQgXyBuXy^n9EV1@A471L(zs#X)1pNj?uPM#6ngF;0ev0Ju3 zfSo@Hg#$8pr|zwB1Vj+a7nXuRh_uCHA$+!wo=ag~`*o;4YkD_+&qR+h{dRJQ@W<62 zcQbo*L>_e1v84B&QG8H(ukfO_0UsawB}O3CFDrONXQMR3jT#SGabdT!pfKW7wh8rz zPfbuNVe{@FxW?13+YK{LLOAu#F9=z#7aEnxZQ zT3A6mRliItpr!&Kb&!sNeuQfuaoMto9eknqB7sxm-j6mC0T(p0^_H6=X=J`5G(7c# z`+9wCw3qR4uAwt-t%6>HMLBDBIdz!#xP!e64$9Y;rK7}T*JHuaNIqM?c2UJrOVCxk zPub_qXZ(c;N5?J@VY}{zdeAQ%bFTbvEoAc(i?U5YL<$sfeRs0C{kA(#*+#zl8AV^%}1>(G%1iGC{bNZ{rE0d&!bmTq7!(D!YD zo%_@#LZX=zY#e)+$f3O#K+p zb7{t4eue4w0a!@E{fhS7`1sa|WDjOCwSOC8%|ShFzP5m36wafC4F{7I;teS3zk~7WGZ3~v zOnM+R{|o#GrTEz|OCMoc{Y% zb0+ED^vtCjXuA~+^1#da>zfH5=x%a15WG09ENn5}|4}B-a-q=Fj8VOXd5mAFfUjH) z>TD|cB}{BENIDlu{UR;quvN+MgOk-wJKuz(uRPo}%6k>ZAy>fIp6jfQ8uvrM*~huw*7ZpE_%rJAKmHi6YEDhF4tF;$`?0;HVJdx_ z_&)c^V9q(@3Np4!*hgzI+3#z{^SyGTY1Z|uVR|Q1S6Neu<)`p6 zDld!Ep6U{gL`F(DdTommfH*IqNGfad+fvR}*vEe1mG@Uv#qXe&H&YKL>#|#-c|b)lm8a zVJl%2^9IKx4l=9{sJa=pZJvkvd}OM@9Iy@E8BLM)?%oI+D%oEVZ$r2g)_{-|a!oS~ zbJd^QmGGu&65a1{+-*)~&SHn~x-{5oAOkD*=}CnMBSL6e$XQliKGyKiCf*vR_(+CT zo0$EESl0>IEhcs0hk)O16kKwnI!ln}=}eQzANu6fx8k^$_$RlzIdi1@hv53N4v2tV zrM8D^C&U}5rS^n7Bq^?q);ut?|6y8mnF#9T2J|eK`Pl>9Ui_k1s$Ic6dcQO|gkIH# zl(6Wu<}7n#8djE*&2mbpndbx_a!&t3=*zA!`E~U-f@JS66;j2OiINHIjLw|>3XjOv zY^&^$-?~D0;4U4vX};!M<5jag#tUOv6yr=$l&=eF*_9+Q<2eFp>bl=E*po23Kmow1L)@ zEYd*~(*@wGw$z`3TvHhh29I%g@;=gJj->Uja3p}}D3VpZXN(y6foliFd<2+RxC>h@ zb?v+Y439)c>?o1@2v4b&4}PH)SCxl$zc#+w*5IXW9t_%`-Dg%MNVzszWjVa6OEGz! z1(In4eeZ9AoJf-Uu8rmhOEj0X#ayiDC8ivfMyuTAyBwHOz;~623zEGp_N{=*lXlFm z6uXwYP)eAs$Z&EK>b`m9aP9Z_=<{?zq*+sf!7Qf@r0J_2^OGss1s&0`H#w02s6{hM zX5*gtOJ5_wAH!93#b4f4P8{%mdCpYMnAl7pRz+N}IS<+m$$QHMvn1r%MHhcKbm;rJ zPv!D<{U(@&%jJH|TIG~-(hluRT71rc zXY>@8O?OL!bl4%2CrZ%JWqs!0A@#G^lR9OZvf7BB#vTF$6l<}Yx}(7aD;pXJL8?#l z;vr3+e*)cc6$w-0ND|EnXq#NH!rt-Xzs#3Q?3jfRBC)2+qdl~YK&Rj-{-bkJ$al`O zaaE;v$C}eqN>#3zhNSuNiYu=_i+T_M4u}(Sqd#E2kD~~JxTkdaQS^~|%ZGjp%}%9B z-JzjLmG^#f{jBR<$P60I#`H8COx8S#v>olmcd3(Fv6fPZm|fuAFg%0{ew4o+Cw#Qm zf7GrO_mOEKbyaLzckuarWy*fz*XA5j(AxnO#KepDhug9qjzDgp` z+}rpzJx^h+Y~so{ZL0{rKKA2{LnLj^WPEZ@r+7oh_q&ddej)=Fm6H5&joqx(((IUe zl$s@NF@;D=`zJ|dVXhQ@<(!9oXr4M_Hb#%BC?A&o6vJVQHQVGpM{*F-e*;YDcaEl$ zO4_3;>|+UcE{@mWdx$MXaR?=psGXMLke0#rgDD?z5z8l*G|DH|GFdCVQF^f6*!GBU zq$tm!hc};joHcM2;R5*Am3uIYgOo-TQPZaD0!nSpD=*>%C0;xIy;nL)W|k^4(Z6_aykgOA--!zru`)vKIJ{<15K3GAMB%>^`%nKk{e2 zjKK}V%&5lucTV`zEr|qUC&O{VVw1IZ(NqYRa?dh@1$&~7x%5TW7ej5vln;xe2>Lwk zCMj*|xtGdWU7#VLzA4YXbI{ys>6%;_GExfygyX2AVp~gNT&MOmTbZ2s6nr`D#1pcT zCmr%B9uIsG?dmt|sGlYbEkwkH&kl8jX{l0Y*;{H+tQ5c1!*64xV0deK0DdT`PYMLP z7-z;bp?)1jN=9rJsY(l~SXiXP617M#$+=I(Dmhm{Y3s$EFLeuze6~-)@l98`{@G`R zrKH~%k*xi~nJqtpR3Gl%;o5F@%rG>2;)v^PFF^Um%@0c8;clb{Avn(g_a^y$=t^^h z2}Rs;sSH4DM2nz3U_0Zq4n2W5}qIpq%dkM^?nt!>AVF9{<^7a+-AY(k%}b< zrDMHl*? z#Yw{?yxQCzDKjNjX1%s$id)){K3ieIDF*LfTXE8=ia5vne__n*DEVo&PyXFLGswdD zLn<`JP=~c<#e*b-r3&h&)1R4IRk16smm>LXm%kp!b&I1l0b{Q~Cj!}%G#pxHV!e`| z=CCOXCq|De1n(`0n{~LC#_353yVj+b`IfE`y`BWA7MZTDJ_O1fGN`m&l zzZ4af_wtTJYN|dlAHusI`1;1rmu-EgAx3gLO^kHPPp8KS@&P3W+?tkSon75&)FlUA zT3Cc*Fpu16KBvI2O5!t8>~fctLS>dltxtRHi{&@o$d6Ydlw4SEYSSs@s;y^U$HyCIhC|+Kj0OkK28{R;i12uURYu+hZiGV?tOJZihyRoknBtpFTEtNisIBiJP zX1Zpa2DN9e-8yi7^w5yn^(APYtm4*gQ9QzY=YcJ&)s~%2Uo(|yCDwB$)xl2Nds339 z^|I~lFD0qxMcOLa*&o#vA=}4^F?P(+tX2GjybO1eGkvxWr;_h&CL?u>zkAVqzxOu9 zvn?AgF`fE?00;Hu$q?oB6di%01I8zsPxvwy;YQwZ#v00}}=iV()0J_E|SybJJA+Smx&X+=tCk7;*+Do+QL|V?+ zT=GV@Xw72X| z>XBp;uL3M%qEje{WK<|4&9ilIPjIfM<@@ojz722soEJWLC2>A@T1uah&>-cfvy?EA zVP%VPNJBW6?SUaSj4B(VxxVOU4bdRMD5`xUL4eLv5eX_{8;}u~i(B$7W}x|wsZU*p zy|YRxlI~uAwq4LA8KXzJ|2$j=IGB){3k+MGm%GZ-@##g_m@}F8F+TFmzQR`fe(xr`iQac5*=KGDWgp4pCRXUaDI$n*vV7nFLTZmO@iH1)y1w20Z9fBc zGr&O%H%$5!lEW|z9;~)ow$7I0pRco8t-N-hXOYDE>@Px#@5l~~T$XiXyzwrLB~GO* zPb6ZZWG&H)i_0cJo$YFEMRrK|V>5YbKXAAad>ge##8w zY|9&Dtv|2|*Z7Wo5WR-qA-o5=_GqoCbKK_pGs!PAvYY-n0ESsBBv!?PR^W0)FGJ2S zO}RXcK&V)w)!R1kuWzPY-WF(~UHV&Q8YC$E8ZsVE@~L6O3yVbSITmRaxGXr#_FC|g z&IUYFg0iIbrBIaU!SXR>Wi~;XJoMvWqhN#-1{ z<2MFG<~Bggz~on1wI`a0{yG8#Ud=;y@cj4Jp0uQ-6Tq`Susv#y9^U}r&H2JVN1Oe| z_+vOtCAZgWeL$IU2yf+hAjgwHPm8qw9v~JU6SD(e2LNKrtS@h`eq-$a_zwUDtGdikpXGqevBM2i8t~~i}$PWRI zQbZ9<11RVFvt6)!012>V#{xc;U7gTf`ex~EJS-3WjaueI;(pBS4n16j{vD(rw)%+g zeA)gt#?V~f&Ctxd!*5K0GNMRHUDq$j z+#fZZjPX|NwM<)PN()-3@`pul4c=+RQ&R}tnE%EgC_!=o8O*LxGRGAfXg)=hc-9)^ z7C)EaH%4cb464V@6ci(n-@PNirYQML#a;;w3*=a?YOY7&0lh5%AYN+B_!%!ygJMmJEmqXxgHZ*R+ zRo1`g*MY3h(qsL`_~nGazC8!{Gr*A>7x2he9q672T*If$aspze(DhBfLfH3L*x?g} z<#u#@O6P<~C9e;TxQgEv=zr2WDQPjJg&;aetR*2G$*paR8Xu&I07gXD-z+~|)M=>; zKd%^G*|IQ9-O&qu!5GEy(K5kKBKqDYoa4Oy51yiNFA@K6LR|)~;`;74#+fjnHkY$N zo)ATWXbjG>$UI^dHTn+7U2M|mwM6*BWMTn`J8Bp)vU(eO4ec`+eW0}!9eb6y&dMAy zKGxQ@AzJEZInc_T0ADqQzbbY178;D2i7tr>WjGnII2l;a8NWP{K31DQ#QtC2v;OY~ z8jZat;=VrM9k=rH=+_rj96+rYNE0l&u21UnSQ z>5dE;-w0k&5=jlwCyQIdRebbBrG{!&ynzan*C;X*Adq<}72*ikEMOmzx+n@D_Wp&e+^e)m_jq}`dk+!gPwvY#HlHyI}LA7)dszrK7N?23c}`=pj%I&^MoHT*Zm z7^({Cj_B=PxrZQ0yrUaQE?ufd8?9@@ZgQxEAUL^A7l zzXg8S_V2Ss1F+%AFyNpWn*T>S+54Kl&K=<4n&G--C%sHAnQ@sP^dHe!vtFyj3-&9R z9rc|j(+UmWQXO^3b6LK%CvB1tDTU_iV{C4Ech^WGs*a*7{C;D=2ukfv$G96Psb3}W zmk2-Aq!`UrTF7=jj-R~&-V6ze?I0d{0c;)W6*tf6L~kIE|p2y*>sAsQr@wvp;r=KM9aopOd){ z`QW$K0cjBz2hOKXs&Q}O*@;RL!j)zSl0kas-^Lr`wpCOZ-;@_-?_MOUdb)DTKGi;zpteyM zwj@*iQ!nyR;-hQxxA*~$@^u< z6DUo(g&O9etg!>6M8q~$JdbsC!av=A0_Ppf-VwS1L_zQk2{;0BdjH7}Fu)IodB~sa zP}c7)XLBrU(TQhK?k_G!p00tS-GU_Y`lAb=lkT=v2aQd{QDdWaEO?9QmR7kSg;#Nh z#vr`Tr+%30E7_K58tpADjWgUXj474QE;h|gsZxpgsXkmTLkpZg?t-KgutFMuldZB7 zpAI-r{+<$`s_sv!%z#VvUsCes_?~l>HEW<*8WmTWdU=%>A*^4kxOPnF7ocQyTHj%2 zs-Uo8A2*AkHQ^gn3hgNE%Oe5a)?8!eAdgX6r6%HXQk%{$wJ(2AD^o(3QAyo+QO1`a zEq)5v_I$q#Oine1@9EjS+iR;Bfp(D!HhNYh*-DJ}f!G7(8Akvx$BE>bo zWAj|O^$w#`&1RffrL@QLRN<7A=3jS2Y32{U19o5>L`{Dxw7+^e3m7FuDY&;4EiYJ#Qm2&8sQguZicb(Sv;G8FNPMTzat7FXPtAU9uZFsu! zdc>L=6qs&uGxt3u=#5)-f4;jMSZYom6yrw;ZX{2h|8K zfrnQW`8EmDk?3IVt7Uy3jVZdOLu-F1t>y068I~p*Y*z)GVAks*kC|C z!GEf!x?b-BM~C1OMPi?`c~oV{>K@Qau9V8 z{TGMOJvxdkjcF&AXs7L3-@wME__2j#_Hn*gF`c$cbA^D-Q&3qRe19?wawfPB`AZgs zXj)&mFFK;i7kv%r>jtT3-}+{Yu@4JL?c?N3m18sx^0?4%{>5}do1IhX%;6cnh}$bX5Sy?2#xL#zm--yW{y7+j)i&@M>od|Mv{08iCu`^I4}=cUEXbDi`_0qND83>Su7<`+ z*Bq>rsiC4Rhid5bT8{MW|C{zb2M zRAJ?k<#AvCGBeQmu^puW_udId5pPrp7)&V?mf4dLcIvmpi&m$%ICnp`@?Lu!3t?rA znfZ?LuJn`Zg}R0Z5q@#-5%w_}o1Yp!S#0QUd;hRH>PQ5?lu`7uW&xk07B7Cudad z!Vz>`b>-Bp6#w0a#Kc}*o)|MJcvJKlwFZHoqDJPd`P|c)P)819C0LB8TF5ITWl_#s znSCZP=`hItDoXtFKbOu1`5!H*+>qqoyM$~4U~hISY{$--H?t5%L@N)8ov;iABQnyS zcJqRGEKOfa(k_W9TxF>C(WWTv>ztNf&%fk8MhM5Pi=hPUty8R4N@lxiOQS= zF`r3@_#@XgT^MQiFMR(;4@m$;g@omu2|50~X;fSw1DSnGua?4TR63!*F;Wuxu{z=X z=(NvfjvJbhhVb)d;Cf;kCKoM!&)v zdu`pgp2c+L_vQLa#RKr?4CMnjHGtv&p%9b*-Uj|~!P}5Pz*6qtTZI&mJqy__L0&M#tA*7m-Yn-Yycfgujz;aInI{<|^g6hZaWZnW3M4ATC0taWs zKL;l^8pjjv2^cHjivDS=nYmOGB0hn)m$V4|?K%hwH$-uL3iNUXdm6q*5PUx`4E%2ilmSuFcV~ASP^oST<~Fzg1p=LVU6RERu{( z(8aXX6cQP#9&%SKO!s+|frpA=>=~B4vCM z3I<(+7U6loOF&gzH^}{)tNQA^gIinz5l0lL=lBQfSy$wO>NsNr_d@rW%V5+W3ytxswhF{>mD8V z)iQnKJnczGqcIbP6T+h*J8!7^C%%D9wI9Ku7%km^oB)q7_kN$G1KDFTVaw_Zrc%+* z^cHQ!AzbllEUvx|DK?qWt)=tZT5oLfW4BE9Hu^eIC*W5xCJ_+hl2IpjZ+2l_O)aq8 z@afnGF(L-{CahJCS6ZH|G?a7+CDA9t60ER%sqdUWw@|Ire$;P^m57A2ZP>^3l8?;p^>X%FKH^d%>7BJtyOuHh}*-e&fN zTtI?Vi>Ifp)aWeRsW$cVBeJ#BQ8G7bK-W)3B<){2*awlFQ{m@603*iWPEGDQVmj(UNom#%e#MIg4u9NevAdhDV;-hZC zD|0U0RmtzBD#|Q|Yi$75OYgbZZodAx?sI%Jy8B9bbaxPs>&YmY1{=C!HUG{5QxKRd zj=&DEfXaVi0RUSduf)qh7k+AzM(gLtt?KeFeeJ4?9Y$>d7s<9Hn$s90X?PmG+WP@l z&UbT7*ER+>u&FB5(##@x$JI-YJoF34^}k;vH+D~G@piPjKW8RoBA!P&9bj~=uSuonE3B*w4>mq&2oDotCe#aY)0BL7|$;WBz z2^8B^Lbi2DEavOCq@lOfOs~|i-UprP06$twZX=PEVx&TS1cpN}y_v`5?y5fx75Tu_ z^TjeNF(f7t`J|pI-!}(P*+{g2+h+JL;z&ZwBtR)c$?+Lh9m`+7+Z&-quh8f>I~6k< z4y-r53I@GNXNexbOS^%6ffXz9;|WAwo-1i87M|pY8@E5d{J>`~!Xd@-POm;#F$WP! zlExHR^JFmwahRh`&MV4WwY#r~!ygAP@E+NF*DbFN};+I(y+y#?3y$+aQ%3n;7 z0nuEjbg;Fdf&=#vpwuS63$EM1A?FYuQtz51z*;S~pr>A8QNcn)uL6A3AbXJm+Db{@ zJFV2zd@9*&?i#55_{PA8Ct29L3d3IYLiik+g0{e%6(%>7qaLbHPDtvlqvv(gOQm{F zDp1iQ86ekZBgh+w19k1dn)sov9iRlz=!QsiKQ$?aIg{f_9*ChT=(@3^?y@N{7Ms?Z zt)TxJux#30?cF6!n4DOK)?Q9p^hqaIZ6-^fT;vr$dkP!G7>9+nO*3NE{MTzCvx;(# zYF3xVF&o9@1jjv;*U1Ayq6CA-w^e=HVomtV-mH&8CL|80@TMmKDA2g>OVWm}f3&+^Ec?(r50@W|@BvCy1l zW~%LcCZUH?-rLR;MGkL$$ppOHi)oL3fTHnax4=8uxOj;~=T9fZqpw7Yka~q%?sOSN zwNHy*6Emo#bKI3aAWZK+k%9buXL%prcV6(Y%OepYYYpM!FcL*{yOlKBuFwG-43`_4 z>Yk01zv`p(wKeQXnzyV-IPg}SKbVTUtX3PViLx3lP3}48y)Px`*ddY`HQm%SqHYf4 zV~S6z9)DIYuMTQc?la#9^qgQT;&=F4|D{m^4&R@8Ee_!EZGsxc&C)#{-l!cuAV0tw`EG(1wS~Y!;sRY>noW={uMN8f>eT{z6lRzLq3uV7UVHa`?3C0LMVc9|M}CFh*(K=o3Zou27Uo4-8EDdk8m(% zxB(^|kpA6v73RBD>0{w1@aiM- zvHeu;S4^6_AL4X3j2UIn+{oeCneGT2j0lz4)x4dssye6GJy+6`98+W%#WkE zmDyA`q}m}^dm+RV;{<=DQ%ew9ky>rq=nVc4)pt3{LUfji=un~8ZUP`RUfl&}nPyuZ zSxV$}fK4F2TXjD?0N;&_&A-pfIU`G`$G!#esm4v)+=?AQXPT!9g}ct=Msh8ZP7;KT zE;u}>sXHU+WqkTpk@eME;+wRv`(#b)^Ek%vxlQ`p(`PZQQ85f2Y*9rVPfJi%;}#Y@ z46bVLTSiZC;$#)sRLBp(N`Mpc2OR=>+)pC;X?7fY%jpgzCn6VQ6%40Qn3eW9pUntK zNqn;V+&-85jJvRG{swmp&t;m<3WCg5$}Rc+IoZ?d%VGU^=p6V6QGTEiOA|55_FVeC z<(s0h2^dq*d5=p<9~&-daqNXqKP>zNKG3bFvs%snLVy66NVhrzh~dOv+O0u;aES?G z2)S@ME7VEaWf@@GCu=X~;JsVE@|BKaSD32n6L(9?{COku?X*I#hS}bqeOjP?J$r12 zao{#dbU-pU`p_z+{KsK9H}25j*}3zz_e*3RJgvOG(lg|&xVmdW;&j8f{H;A7IC5S8 zXvnYtGj=6!?)!WO&>>yi&6Vee6@#j^a~&58->WKm=zEW2qWebG=H* z;%R5s%i~;Sh(;76Tp(k$Iw7=H!_nQx=S;eT8%_JJ)stwxx?*y%Bc>rvp**MOjyD{) zuk(Em!Sf*-OGW{I`wweVAnU+01MIHur&!B0IX<@DO}wxwMbzwlNgrzar7&x+J^m}&vz%XBgPR}2=Eyn7!iQ{%=e{1{F3as6E_BnU4THx9z9>^{RIbiB0wY( z*l2a^!!)k&H=Z6whw#Q0n_R)#8ya&D(^9H57a16yn7FPF-WANRGIhhNHFgBN#)ldA)y1a&WPWaWE#-5p5oAc<( zdj+qhP@ok|$Lr4(QsLPw>U3d43-Rk^o;xflLivTJP0^K26rB^)T=h2(H^15`94-8p z#S8#c)YGK?@)usqo&D%#v<_?%5UBJ~XS6#r3-?TkE`Y;e@lsmjqqgWAW%um9N26&r zrIjV+56T?hY30RO-_kvy=M|}J;5~qKLDLZpt4opb(~f4_4^4%hpX1eaEb{Qm$>O?w z&-dQZ8Ir>wgR zqjDDBj`g#@*^MoOnXJSZ|2@ILHf5giy6EP#lSJ?gDug63yb~_qyrAvN-eDKiJ5Klz zHW`T(9alFE<>9_U5v&Z$gkBZ2$I2~=! z&=v^g>NKbl0}GTKTR&5qtdmUHH9{V|7Sesn@x`aUduGX}-z*PtuI_A-IBoysWaRlC z2QPlPBj(otmphM)$UVe(#HTH3p>=7F8bjeZU5YE=+RgTpcauOM)jktD_ zhY?r!3GhoLG!_C!;~)%wU=jjk@h|%*Fb$*0^4PyqzhJM!5;EP1(ELC&9#ycl&ojd> znmaxWH^yep>$f|7fs~A^Hg?O;2-uX;Dz3&phG)kpQVQY#R%nZ{|BVL#B3d*62B6^} zEPddk&22_!8;;edJ&xz0vCgl`^JR)Kc=pv~BLwgP{N>L7J2v+(V++#O@l}PLXtfdE7ZfKcms=)zq9+CMWEa{mZ!2>>OI2bp(mFJnBvG-mff&G$b8q5S9o zDv}-Q1Yj-$L2JMwdJg1*^bvc0gu=th@>x-;I3jD%s}i_-td!O#F&Ug1T44Evr6ah5drhFUb+_bC0d zXCuka#*y9tmVntQkV`+O?$}1*AK)6Dr-lDot7Au0JaZDc_BD7W%B_hGUoE9p}JgL#rN)oM803!NCEJl z`imlJbV+I%1l6ZLm(uZkFOQ$ps+^0W@da+5HD~?%)^v$Xz$pZfNhm!~7gBB1VS3rV zM;5uCVjiK9aOmtu$Y1FHPFDhmg9dN>!M7ZwRUCYaR;3ScnFRxVIS3v>0`I8;DMirl zRCX|;V>=DA0`^b)MKJS^VB?a6;E#78{#2Rgqr3`HJOI(Z`h(~N09^hHtsYPrj;#Tp zvdWEfXB0*^vxssYGljZ%Ex0leWI3P_fEBp>f#9^=$Wvb-2VnKEAHV(tC-Cd+5P|~n+mQp9)4hW&DR3lN?+{SG*7pcOd|y8kg3&o;3~)e$4bg)`+$$ELr|O7nCvnREb|h~m1G6N52i4E5W)L(J z?wJCIIOx!d#Knds!2>#${&ArGKKB5Q{yZ;MZI}z-MlLzrE0E{U&Hcat_(x*gvKo=j z`^V}3iw6SCEOYWZS4JYXeW%SquSgNJC$PgT zh9(^`SRG*cK>lD|051as(GOB{ZI3iR-)q|^sA;U21UJtNCrk!>G-|LeC-GP&+nL~`B~8Gu(P25 z{Tn1ySYLs^jr>RI#a`6c(7O0N$)4ePDagQJ;NsE8T7NYieXPPz3VLZUpaKFCQu_~S zXsLkCP%s$uA9hs{W&pil(Eqog2Ltp~{`&1`D?_Q?Ar(Gh5I@8BiPe4^3rz8&nc`sK zgE0p?Ir?MbUq2pwtj3_HuBRbG_D&KM(Gh0VVWc0cdr@%f%WnRDcJj5@#o4UpnwRDR zVkqk?UeCA1RMjwFV@Ar5cQ(f(p1EWCW`{@fJa9#64XvyYJhqS=FMx3>NP@6<7SQQE z-7W8xbsL-HnhvOgGr1)jwHcH88oq%h2G{oy4bV&(u>X56rlYRXCky4SO>gjO9a5tN!Q6s*wM((0caE$kuBqrgjo+w_goVDChxg;|+wIOpt9kd^ zb;7zBuYj(b4BN_T3vu#k#_sq4Sqki~1rmyF-K^oh`qAnI-8(*}>rmaWF1{;zV&;;i zCU4<>FBtv+#NUAb*XCa+QepT3a0dM!I6JKW6K98jpAPE-7y-Tf(Z&H_?>mMLU;lq< z22jKYpb+Q){O{+K2NNCA_%+dY$~c(Y^(WcRWNki?|c zj9l--t3qc529f3PKK?bBGw{5Oy>_b3KA~iSwO}OqB7mq<$AIeB-?&EZcj|B43 z|4ah;ce;3faL^Cv0wA~lriL>Q;1i`xDZPEdutUsZPmJcCXp*uc(+CVYr z;tC+LlVc=6)?uoq-AqoJ5TJ*!(9m5N;CgZ0XPw0zzP?qwzFb5#)T_UW7@b<6a7cmuWG*JKU^tG)b(;;@xge~Re z$+B$O23xzB`){`Soj1l(>xkK0WAA=&elIQr_!AIeA{gV5LmZz4ex9{!?zY1!#i8bM4?e^R7 zd(}W5@Uj@&N8i!4pjJF+(g$OLI*5|Pc@wk--M!_fv|89|9eK@-Vu9y^^834_fwxce z{ac>iJ3>B;3P6@ZAJwbg$o;;`iphHeAqGCLq{WtAZVENMdff zjD0<#;T{ck3Vc2m=#bnScVnVl^c>+_(KPa9GiD|uwwV_OOLiKh^BV@snmJQd(^l=PZ5lr z*vKvga;mjoI3^KogzqF=#52$2e6sNLv%JGrp6%+G_33rz{neD3u9CCYrndZbOy|$a z$SA%nbfl7-#GMmdEPMhhO?XTaab&J%E(PjOz?E;-pmaZGnA$yncx5z8tyi%>ZU(tU zxLJGW`%b{9x2-H%lpsF*rj^5z)SY)DYo)OAmXgBUoxHa+FGe0mh~4E7pVj?q1*P^k zg~_#Gf*zg<&Flxb*EsNxy7dx?l3}={+d#=pa7xexle4p!Px8T+p9(H~1C536rek%( z72NWGig2AdtCvmcBPGY^Mo!YEv5Lo52qhnzcY?IybkY0I^N4}H(Szp>?1TTBHz4NW z9zbyoAbBG`b55831Mq5DC9(6EpH=8-{S$PDJZ>_os`1`>+yj@-BTdVRynAKspu~ZR zDq{`>YHlkEAL9`$?J|~CHWs<(b?9V;4G0IhN*Vx?i8kxSiNf`mpl1(+-5KKLYYgT? z=HofgvPYKkj z>*F`oa=}3YX7(DBxjx%mSCbiwm2TAVoLbpIS*e>lrSIS#!<}BiRWYf_bGpZ`+6|Sd z6Fa&=oJaVqI1XQYaNQ_K=ZHdT*@(##QZEz$84X;x2ose5K%@+Ky0GT;lNTBBckN_4 zS8_nC8~5)?gy9}7#G3SvIc4jsF!KDe0W&1^lR)44|3em*Wnlzd-+pGFP0?=O|1#fh zitV$0sdO)z*AT*rzLmP{>jvS2Th71RLH}7T`QLPLlO{-++B%fVKfgfX(2Q2hnHst0 zRh5@x?quZz?0@-coVBh&(4K(H7biC=9Z#{ta(1o98_6<-O!X1$->;9nn&$p02Yb`lC<2fy_L^ zr_woZP}QQecqV9 zQA+KKbl^bSw8Xr$WW9XCOt_~jiF6DoRiJMSfin3J%BlVV6y&U?)=m6 z7d#BjiZ0S-7T7l_@fBkqhVNHo$#pxAnv}1s+|S?DN)+y-syrj^W_5vg$VBC1K*Y;i zdHsM{e>LC_`|IL`!O;r<@9Ag-qDm7Lq;TMGW6?S5m~Ws#{k}jvESLBR7-gAt5g^n< zTd4K)Z=f?1=sq0a{M#4tFIJ+5yW%xwqWx0op0y|m4!us(9TXn1xK)%$YdgaC4KMGV_l)-0cF)fPDcza)blW#@tASd8iiwf30n(562wWuY2eaj@*_p9Y*Ae~AhIRFMg8^4QuLf5#@ET-*R?76{W!*+a~kIh&)xNUmPeZko6A8;gcRBI#vGlR_h1QT=VtKrQ8b+EWszIh6|~@a||JxZ?4-TW781copZ-Lx5)6iCXQ5k4xJo3DLI2knqKnI2A|FBEn1zS|SfgKd^L z3)ii?zCOfD6g|Hlw|nx`ZIorsMzr42BicUJ@4i{b zJPUgg0V^Z7<{9-p=P)gVH8;PSP+l&Fcy9OPqMl3q)Rkj4@<%(T8Vq{{*IYuOlq7bd zWqEl8b~hp^rda6ubA_ipOl!{6yjSxB9>?*%JH=u58t;NJnN4WARtfkjsV_(CB&|^- z-Q3ca$R^SA=B+XF$SYH)x+Xd2yuW|P*Bj1XGAz37GM>|?N>Q-zCC*6M&K;G#sX`X} zp-O+laS&;b2!S4vj2=6;sX4kMc!aMaz);(u*Cf(DRU`FMKsQt}NIl}t6S9|19Ph~1 zA(U_-Krx)swHkp#-XiR$J>zXFKSFwwU`j>~EK+trlsrr5V-5bw34sOXG%|G8Ao)sytm-I!d^3xaU1Y)Rk6 zD8Xm!>Vx**)N|eO<=#&#a{f^{)im&kQ@ zLD(n$hTLrbT>cO)bq4=qO$+!!>ks!EaO5WCq={n&dXp>o7HXfNB(h(( zc6Y6oW`?3P@HA6W@H5&5ssu=^l2NPVLw3}iUe*ekq2tfo2q;z`vm|@`l$MrKxgqn|J6?ai zDl9m#mj50QEy4u>nlm%pVn{)Ej#?&F%)vH`+{=lWSiw`5vv;nMHR_gp`CR$RNkl2i3S$kBYH$5wn9 zhiid?|Cg^8CF=#LM14MW__p1-E6%a|JL8{Z3TjIbT6%v3c8I{~L2aEGwhKC^U5lGk|K8Sf}Farf%{mhSQNXrXjn+0L)d zxn^Ty%Og)eIbhTr9q)#7=wC_ai9B(`$mkx3%n#JSXje7Av2iEvDWHBBoIi*T{pLR% z_|N!H??LH^v`XA~J-k(+QUeVs;gRhY5w%B&QuZ!!1}-d_&9C+PT1~un4Jdl;z&0I6 zXFhc&)SLV|1?mtTNZy6+I!Q2TIXlZYpz^uFfV%R3lNT1o;X8yXElX%xmxCR}8(&Ln z!8!P=eJBrW-jpWz24F8_5wv^<**u!8_=wQiJ-WqK=Il#n?h8Nv2I^MBZ8PpAV^(K( ztFW*ZdVE4liTbXvKMlU*c{zLgeD&Fdn#nF_^46jtJMHbeX1>}4`!%2Px5?U zNM*oOQ?qI7ru2|b4nee(7)oM+OAgobt%`eQw9=`6^?KeZRG{IVc(fCg_HlG+GXTz2 zBE+=Ye?<8DA$^7$Mrz7o0gb6Np>q|*#T6&aE&Q^>&Vv|tXamxfvn*Z*g3xt$!@0Bk zvuyKHH<3g#w1OIznZAXk-pjkx=FRB=k<7zg?(Q9dXpHFcYs2R{J zJuxZNHskK&&$8ig{OB9R_H(z{dghgHASqzT{gnUCAjkh2tpD>Kc3~11A>Ri7Lk^DLL&*Q; zTlmj5z^e#qqycl2#&yQhI)TB}meK4hClK47XSzg3N8 zTsi6J^m46_So&;0&BdIUfyoN2HNjT2j#{6>(tL=$%$sUEHH7QzZNaU9mSK<<*6lgI zfW}p0STGUN?S_LLhfAd!N0hAXY~2u8Eu3)7%G@S>0i@0hj2s_52M*m(P(jOw-YRpr znqZ=He^5;JQ{pBu&^H3l-}rEuqCENSM7X!$J;sx&lvH2%>x?l_e_^M5r_z*jlNMtZ zevB7aH+1iNCw$XLwGQxV=kSasyQ-7`so@U2 z!`VU~K3vN2)3^zmA8mzUP)SzhiQvc!h$pF1L$ybh+N>T~$-)D}E`d^pEzuk&E(ggj zy?S|g@aEn9!>@~K<kND~0+&j_-g&+18(rCyjT~)G|GsKk6(Z!oln6KY8d`ybSMI-oy83H=7WQ zp13yalhKy==$SUmnL^%z3~leFGRhMX3^AF3M#^_zrn4t-3{~hUii8O^DX3t}B*Eik zO^CxfQT8qj4jxGfyTRj9AxfP!IA7g(kBtdjMZ_aEIo^c&E!vSH@S51HjsR=pnX#wZMyA03Z*XD7q)^3ZrC zn2KnO32uP~xe`qv&0<6O7uW)29XZcVqyLY zobGdV=F_GpC2M3A-rX|0b!@YeH~7e#qC+fSXKVv1E-D&_<=QG#NZqrXyf`x0%~sPg z&lLIMuwKkl%~V>M+-v_r0YB3*qMO3bl6@Qc|Du2=4!__JkyLX|tYpQV1r;O}x%bFf zT^iQTy<1)3Ds&Z5TNhoF^U#;S2(j&>yHC09dF$EZG`%S?tAgZrIwHq*2lm%VS=>oa z<})*Qh{9hvFYwuY1KkBOT)*s7?eD{|83ac);N~tKy19agbR!%Eo)MGYeZS!_V3sx^ z2L-Bi0*P-3L*i}H7!bo*{0b&~O`B7DpSJb>pwpnS%WiM!P0ja z{OY4&hWr}>CIAG+47`7Iy1J)RV%X z=E-yR?Wm(f9?XY?g$2QN^I6St^S0TF$MB@nef*kQRDmY>A}Pp1JpVe08SkRTu46XhDsq)k(c+QY#Otl4^_l_4 zvPi1S*ywfy+vIBXPIp&v=YrLg(!CtSV<;_%Q*c|lA(f(}sHA+L)DgkmI|%}LKu4e1d-e$*a}8ER7Q zb?ACifymman)6ISKPjp11_$p?Go?QDA94~I_3eH=R8=&Kq*Z>~kamjv*g6Pw-T3Az zn|K8E556J#2b24%djtB}{`wIj6Qea=HR0M*CalvK5ZaQHf z{_X{^*gt0byH{)pqWmxRS%@FlFFW9wM1W@k>=0P+#`=xPq1PqQpIrbh{0GPU+2u_h z>KF6OB}pQjZUgcFa6;_w1I+M~?w+Z)sfXlJUf-`y02}^;VgBs1EGYdq@B9XegiM$p z$c)AxGGmvXWXyE{0YQnsdm(^MzYCUMo&EvSKVbTYn*KR-|L~@NSU4aL|B-b6C<30`LJAw)1wA_LHY6 ztM9Z8NG?Ah7)xs}SXs)(8$+g@n?D&3v%KSndf3|H1gXJH>4nLJDZm#|;CAoe+L!{_ z?koHS4=ipbIE2K!Jlg#hwW4*ryhNk2g}*G3U*)-|JjdI?dk+`yk^6x=6QHG8ZE1l@ zC!aN;%G4SNHAoue+jq-o?3HhqQ6J5}>T6g#f_f-3<-5Cg%`Gw`u&}ru>M4qbL@TkZ znDaM9({;}3GV!Rm-tdeXNRMSAZxz#P?~b{j6$ELe51m=LyRIPT6w#j;%`b7aYHY3%$=Lew|vMKh5tO)16IWwVaTrSr(MvH z)IG8~E!DrCZY4Hf8fB$4*C%eF9(HX_NQ_tKJj?ZnuxOW;IfB<7Gx~Efh1Imfd+3o zfe8rfkEQR9%RSHhy!E;)>;@?h-Z92`SxR~Q&^F=;Jt9D;x5k!6kFU2I2CZwhJg zIO5Lu*tPAk`xWnb#?u8*3Du@DIPvYlqM*lp#qfr6sWAj?BE?`nQU4vy*(LLl$BHcv z?Y8o zGxOCk!d0AWn`WTFMWb7t6VGSEIVs_znB*oTBjRqiqT5_h8P>4HnyNqRqqCEObSD1( za^7s%_;C5r0#zuyS9y|__Ae{yjk%{B^Ey4WvG9T{ zJmgF=?(~s1o6|)R6!OIdUqcpgWWLGfAXkhTfqq)^3N5?eDP8Z&RJd5-_ zwGLH9H{*I?-(DCU`>r? zQR8_WfI_tZ7ZRPY9(*M+_5q`LGv0ucx=ne!*g}PMZGbpqmAX&s<}4a9;~B68ylk2OsDo_ZqJ3WG}>R^_|Sg)66%7F+8i{< zT6!3H7aE+#D7i1)gQ*XGwPI|NkorLIv!$iW)=-x{UHMv+(Gkv$jOz+yv(iCjK&cOW z)@~K1FYtm^B)tW4#7Icg$qqR#nI;!ii_2ed8*91>W{yION#sY(kU*4c2B35eheayC1HN4XUHqNDxgq zo_4!JeadXNZLQN^jgCTT7@L{Ulw1ef#SJSlI3$Q!5}^Y-p7lH^b5&_-C$sf<$*9XI z>8T!OhfjV`>t}m(WM3YyHd}bYQ+_r&NI?QUWw2;5DkOADi_l$_igqaI+DMFuGJD|_ z=s*8bgz63uCP#?7oSc`#G3bT0G(tgN|Eb>eD zklKXpza(nS=>`IzCy4>km(lPY2&<=e!|p@2wlqYp)*?#9#g!^q=)R^G{P3%Xdm4v4 zPAKYL!Q0G*>rTL0pv)qYQ>pazmyRTTao(}H~j%gS&b^c1^hQE8RLrKZ_ek8M&EGTqM2k=yo@eM-s=YhR2GRJyQ)WZh1= zjIL-wjT;)`9nn3P9G_)+BD7L(Z%>J^jAcA_eswagaw_t}{mke7I-Xov=)Q3qG`K|+ z&o7mZ)f}=&*2G4|AsjQO?#SILHdo(j_Bxhm^vvdT60^a~b=wdSS*ZsMT+2w%BYj9^ zuf!|PLIZ(|Mv8~K7M7MC4SdXhuy-?PLG^|3nnYx;(NySo7xamgRMG}Xj>v|`0e6!m zJ}PmpJgoMu!7>|GmQ==1Xq_-MKdV(aFV;3BsSdA9h_|B%k?CX9&J8_|wM}ag9fQpv zBLQ{~V+INo0&(;3#i7%>MKG-tropdUo^ui7V{fH+@2gYMd^x2kVX5>E!@v6kQ_-lX zHg^riUaR3fhkqbjCZEf?L3X=XbnL_6got9lNF}G zgJssYVsFNkQ#fy0#Toaj8V%mJ_`E-nQ=#D6$?5oIt|iH2-5P4wW7s^+jSrrws%IWw zqAw)!j!>eVboGZa{GL>3e|Ez4hR>JSJY@Ip8!@B|R}#P{W0@v)PvS4n$s_1j^jE*W zEqbHkku5y=dIc@(e8xn>j;5{|M0Qx`;zAAJq3grCv?0nDgGpe#XnRsx01^5Qf7bdA z^v3hgi~zNFli$7SaG$^va`jqqIza7_Wq55Y8CrLZ9^=@|MLnoZ%5w64bLal0q05Ju z9E6G!J+3ajx$A!YkdX2W+gA@m{EfNf=m~@d9!Z;A( z-O;ddwB)68AHz1e7r6PX(rGx=et{5)(i*>Mj_nJmh0QLPS=gYd)5@pEttQmF8gwqo zYo^ao7$W1kHWWsir|8ka>*45VE<8>jpH_%xZ?fhX-6+)t>tLq(J|)&SF-tRxmPI5t zzdPr>0m=z1OedOQ+S}RE)}PG)HTun65@($t%?Z2ACaDj2x~mfB&!+0T3f?;ZSucA- z1-?I9&Kf0_h9EqtT$>OQe?cpMDLulZw0payjHCsQ`0p0T3>svN~i;*^7{vhhbe4jcj z%?_SOUC=q}V?Lxj9N%l>k`414DR(I)k@S2GiXzI{KhG`9UD*3Vpt=2ZHx0YF8<=c8 zI)}BLtr@VX9d`Q2SBoQAHBYvmAoqLU(RN<`T*TwS`qKTJdPabs!5W}|a*ByVo81{N z)k}UD6Q;&v(Z@kDd`}+uRMk+C(rb?Uc?H`i;AxgbEL0mWJ?rUvim0&v?t%*!Ty@C4 zEcS~=Jho8M>Y`*=Njpt-dYGi%hh|*xXDa8BR^QJK6}j&mMykUERH{Syjy>{QVW(P7 zB0j+pl^ElW(5sp*7dDBkxr6owmIonp4eXL9n!qdkZotv zw;CVds2CrZWoB8>`Kmlf+A<*M3Me8XAYfVN2K^T|GzWsUp3irp8r4{M6n=3S&NFL_ zDJ$E3b@#1{_}O?B!`<^^>dt&I{j3*{(?2}A^ay;o6ARhByPrjr!01|G>)*{G$>Ews zbqPAj)|HO=mzoH=k(m#^^h)!*A_IZ6j-QL;BjVbN;V|GX{miRWZme2kiuh0qTS3eW zLA7Skxqu>TH}Ls0?pl4z^8y^NbwyN!w}@}JDwXOmqqE;Y?JnVIrAl#xi^K<*4_t?b zY8h=@?O!d0dNHNT_dJT^z|r+EcSlC39x+h2JliPr^~eg+CXLxMtJMR+X43MtZaPmg zEre}2@qm~Wb@gz+^t!!p?bA$jiix5LPtq(&oXCwYoP|cm_zprS6@V9j zW_N`72|O14E_PwPW(H?W!n_?rl~oXNMisP&XDFw(4DQU@d_dSDqB_CrhjQRY27S+H zV=WphlB9PFp5@*-J_sAPyg18Ol4N&H{Ij&WX%xuPM)(@=vX2S6eLaw}0=sI8FGP!y zFEg{n^;86HBf(Sbj<=oi*>85cu^%o;xPJNYAzqI1!IQo(@!Y6VXeit<+p`9p@I*2? z*qT*KpCT&Mg_rqj_#_iWR?^mq6Q?Zw{0`q2ryPVW_61Kf?&m^nwU8&^B6wTO)(~3l z@#YTeW~ty%j>U3hR{p?bzopt4vk{5?e)0Q8_1D859YNPK;DMr2w-7+k{mwi-5M9%A zxn-J~EwZj`(oloVqQ}wx9{Gr4%|549%q@Mc{-Z2p6(dR~v1*|rh#TFyER!n<2CF?Y z(sk0C63H14Bp06bia$esKz(uUb?bG536VMxV7L)_Uv&31-VsZ!_n4@HR@7W*H>fT@ ze%+$((ztsZnu_YCLh$W|8CojMicBCfRlc4EBv!i-#M&{%5Nk#4L#d7a%@s6(N+W zt~fK?p{XERmFo%z>@@P-C*J`!em_9We`$eG`!ZNAP5acpd&Md|jWZU1dLw>A=FKf5 z<*pe&5HE-jTuNlefI}2)G3~9s^uco}n~`w-982Z>P&Cb%J)rn)Ol7?3nR>n1m$TP4 ztR}Sl8N>sZlqBvTN|C{}_nqO887EP6R&$E#fK9Sm?80=@*O6s)n$xlKET1EyF3m=1 zP~FzEh~5XDYQj&f^IT$HPg-DU_mips-KYUe3HcUsm3h&pJWUZ5 zpc6UO;CJ&7%vD&>);OH64F7yuaFpL7v5Z}G%}n&rKnAz_KJj6VcnRlAAQ=bkEL&I( zb%?Y##^Ba)SZ2$9mor0Uf;7iSj^4>$Yy#&=)rX5KlW2HJ(^^`|T#N%f~NY3(UwyLx~Z=T14k2Q7_# znE1xw(VItJRlPC7Z~Pfe7b{LBzP*Lr`<@!(hYp3iF^Mp|q`VuQ4R^wXqqzzRR1-v&q0%(zHi20y zL2XB{5=HA&@kPGqUI%ihfa+ewy3GGz@4cg%>b`#8C{ml^Pcki-QRuhJMOvXoH3rk zKa4e2lD*end#<_Wn)CBr%DIMo{Nn|ZsVVs6QXf8}Ey#Sf$!x|w3RJPb6f12x^kg5M zB_7M(Q(iwv;`Ypro4Q`2b7ExsaB+rc{2aC{h@>X5`=sb1+={H-9-V%r(%x}_NWX0T zRdZHv=? zljg`Pu`n)`?rvl_^wLwn{{b&|zfh)gAZT{q9cI_6AC{6_stI@WpIFb5yop&$Ct$jD6QZ%+EiP-WzTnUz%#43wNg=0|rNMxh4mZM# zTjXijHCOvXDe@qg^KcPF5+PP%(Q#JIgVku##c?@iZ{J`UJ>S5hwBXpAmUHP>pWf;^ z4-qj`q5e$n^20EJmw2ObsOaO))p8soyK=*Le4uPGW7OmBIt=?G?0g#-GQ&aBtp&Jd}`- zD3P!)uZXv+t<^}7Rg1W*7GbCaqN2QZR#oD(r0F?8UIMVT=mg~}z5*IJ2Bps00bl8^ zFOPz+!7J2@ooe9kmMIf(TIweza9(2gGC*UaAlVQVC-(xju!Y}$%KMq$7YH}3X?&oY z<(!nn{Kk+{Ff^=PRNFor%gBV7MqO;-Cwgsl@?NSTsxLKjF1VS2Yo|AV2GxVyLTS4e z?mI>GYj(}(bHA;U2*I{cZ5`!YJk5o`JXirVU3Xc$WPGrc;^};I+IalX2rPxDg~u!x z?MUEpjESnk#BLmzW(y$z%PQr%KjOmVD$hjycu8l6kQL&$mQL-;DO3TQM_hz+5}dOL zsH3P!xLe_KR6B&(X>@sPbw4+C$50FA$Wvl?&P1`B+9i= z2dIC!?2&j-DzE$cK#Ezum@-DqO{^;Uv|`j?;il#zZB1L%+tysM7`q!oJ3NHN4&>*x z7`VebjWyCed}OXv)S*(ABD`C(DqXIwQQI#W(~~5_)c7F%87n+7mZIa&k_O1L>`e-FFcI%BA4tLpV`_b3rWVkBbm3P&zTdXnVC z!R%u6ltKl}f6ul%ORTT)J%cC6<5gmksNf4p+`G3ii&C*LfQF!|fEb2eR6c_yW-Lu+ z;8T{{f(B}eKN2HV^hfuO2og}wnh+}yFK%pT#xs+}$O{L|s1q$xz*v*+iOvFvmf+H9 zjd_tni%&-}bRI6G3Dz7(79xF;zkZhZD)4etvA`rjO(_(pP!d1JOmi()E|9zkQeUjW zFDpnU_^KeQ&13oMzBr#|P_R%et@F^lJ0B#G3^r$2WX@h)x0 z$iz~T!TU~!vULUTPN$nG!~E@@9-h~)H9iteQd?qBc zt>bxA%D=WkNdchp|d;hYZs?HG9zS&&MmB`DI|8l zWmh2TeMA7Jj-`o>G6D6@@`>1cnBPoV6JfE}Upa*)q5*Z|X4*^t*)O0EvZ=#mMFW_M zsO?jN49;WwBjEe9TEVRd;1}^FK}q=2$k=D%+j)Cv$@9VEMlDNCWM_m^N3dA~ngfm? zrr{QeSAC%Dnzg=##4jq&~Tfbh8~PY(K}ZbwTMM z&d~FK*z5x8^bL+dT1CxWqUqcFZ!XPsnUIA$KRCbM%Hdd6ROE~KMTvAyj1MQD?_WGK z6a~RGDCBq?`OiwIt{r`-A407Kf;U?{4&Urd#T3t{I*cIQ45E3(@j12l?mH83y^?^7 z*z~$72tGL=>aLc;=bmKrH148i0{(}?gqrwyMYq?g(I?`Tx|bSDZ2+t80Jk6pN;Dxf zS_*xn4&!ef07|J#l=fWjo(kk0@jdHoM8;=8tB_G1$|@!BEBK=~%ZZFy6s@HJLA#Gj zc8i1%I(!XTH$b;;eO94jt7U1qcA^4jB8Gx@m`lmS0FQ$gg-^WKG%}cRDb(JVp+Cc0 zxlhbyj?0wk+7-_b3Kn*OD{nH?4k79Qz^wn>KOheO;$4jY2g*S}pY~^F<|Sue8`~+} ziUgM$=<3N=-*HvxAKG>Uli_?oHmMGo73uItLew(#>9GO6^p==`2V~VTSj11cyK7S3 z4C8Pj(e!M0U#9%*8kc7_?e1?-Igs{>4=0}r6U}*+7k_yIJ}c%#B1q_Av3}ozqo`ry za6h^0kL=Ml#*+gSR_jO}NC8v5j$AANc=ls?`Ljd6-XOm6#Ql?t!2Wd+qE;$R06H_O zBFaJLgzJxNRT{{TQDg-8+YE$61oEvAsU$J|n19tt?XOPv(i4H(vER=9_AjsLUv*;o z>%vD1|G4niz`)2r{_6-BoyPx>lokj6xdCabTK^YaUi{VNcA~X`%<6!Ql1c06q;Zh9b>7}CS=XjmgF3E7HDK@gy@L>E zx6vNux<_k3?5DHb;bMfc1b~UDlp4@tbo8Ko8-&g*sYT{y#PA9!<2=7CrDlU~OjBGlA4s8tT&uz(fie=7S!+g_`UXk@`uT1=Z_OBlqZae>FW&e76^Zznx9Df=$*O7nK=P$$OYCN*DM(S62 zrNoK)(+2)!-bDVgvt57MSt}o-f6?bJTWs~9DmV}jFJN@K(B<-%UH&)o_NPrF{AtzA z-m?CyPJfwxvos0b1!RBwwE920eY-zb7Pcadf6?c!)#vXa`g@4}o<;xkZT$O16DIy` zi~cs(|2M1N-zyrhx%zw2{}(>b|7m_vX)6r&q|nV?J9LxzPU2`$VWDV6ki49}m?J;{ zaJKlbCC8(tnie4{8{>ae5dV`(5{1_lL3i|iLv*7zf$k_f0tZs3$ME55LYfdea&?a zsFp7Hclv5>Qq=mMyi)kxbODnT+hjfc3PV{?R2T{#l?*{ihyc}m5_Vjs6> zR&G>9r?WrsG7szYXAaQ^p+9W6zQ3gW1Dn|95rw2`lFnfwy5zR!y53tgOW+^e)1G5b zx1`e%SUOYxE$I)0(80ZbyT!m2RNEq@1^hmSh=Dow;oce;=t%~nZrv$$jhrI%3{TsxwKzFJ)<0^RZA?UVUSqERMvIxaY zGwmOI_dnz;{yX41(4egF1yYG>Lq?KsPPPaU{dJQ`2Ix1cV@Q8*x$#^fJRgWQlu zRtNX?F}wf+DDf1?nwy4=h^|yzac@gWj|l_Ptlyml{`r%^|344p+ti0ccN#Fj1@Tp6 zK>~gLGfKM<@PdJ`lM!j?Nj*UdXCB#dDZqBQgDjM-#clU8k9sXrzbCvNQizZwj&<28 zpXQVKevMLz_M6e1QPnHCxf%T2z#A&lar3yXP%~z=SR!K+(Tqh#dMIX>f7=LF7E*B$ zF5D?iB`q2lgPDo3-Iyx>j;l?Lk9Gv?FQTqa(v#IG_T`QMRu(k!7#S)1%}4LDa%#A2 z2YeLc^}LzVl<0sTG5~nEyom+ft9n-%I*7jtka(Vm=TTVj`A!E~?TvjqGq_5UvI>DC zYQW68WkHiH67afG^t=nX4x>*Brb77v^E_>2ht&k;Ps?r811G>sZGv6mx^ zVxB)YA`br{1@b3_RtS*#_`^v2-SD3o53m?w`ZoywY3k_tl)I`-_oqIrg>ZRr#K@aV z2bdWLi<-c=51M-lR4O;Y*V!iEX2hQWLi1`Wc3#P{cKk5jzy|CxDjP34jXqRbyu$9E z!HaEtELK+;mpV5UgKX8td>aFX9<4Q(e3$=pGwOPI zZG`Nb3X~HuA2;uCd18d=2Q7$zZy9*9by>6*m6t#lLi)A<2ut4!Qc5| z<5ppP<@nR-F7tsG>0gxN!Zg)52jGl224P$r3U4^XV?{LSzU&`X?Ph&@Y%t|GJv(C& zR$-BAX5togxg^@$j$|6BDx@;u`3??qx^}LS=Rg5 zYbQ=9=FwRn{dS zE9bg@Vl(<2Dl~RW!&JlyO1{$m|0446pZS&lxV+L|G{8R=*MGf&@hg4KixH*!hc)^C z`v#mUAL%xJu^mc3ACTWMb(Rk1t7HyQMo%;OUo9anc9D6A!Pq|ZMPlZPTglFY9E1Ly zX;y!S8O$YwDBOrpw8OsqVP_}b)0w+4EfbX@JG6+H?iQRH^rS*^3qIgO8(T|F9nYOw zw}Tl(UD9#{n0{TaI?P_kf;pc2e154J0BAF-UI|zO(uekTH23`3s;Z6QjRBQEvuZs1 zE+7BIdbDjZQg8IWckQ{_m_>zK=57pP)ZmGDKs3UC>TAsmnzj-_8P!qjC~$m7?V|Hy zP83=fm=Z?i(oKIAXGB$;H}+tQ@h_HYKmH902?AEJ3EMXBkt-y$pd?aVNtzI6vq8{ zA*D=BVALZ|4x?wT=FAReGOTi5dZ6ma$Q^#+9ll+Kk=tC309_JplZrpqcppnpZ+`}R z=K4L$+&P7FX%1x1jjywKcCxu3sZ zDM(*kwY%_ae@;1tZAi)AZAft-5K(kO3up+`zFZ-wEQYvEZsMS-+K=iRB1gVc^JQ7A zTHNUUb%*l%ck{^uqyA2Z!6pxU2DY{XdG0r;t)762D)nV#_|#E8?RLGJ=EWn3V24qj zh@uInG1cv*WZN59=(p4(d;G&0r?n^&8#jBVYN1>4%v|-#_tfq{XK#wY_#4i5m4DrR z-TOUPd1}I^$|eFxuCB$?VHvx1#jK@b5M1F8FDa3S#AK6(7~jOV-OTk#R+~A`_4%3f zwM828S&{&e?fu*al1}+1RTR&LSzWS~PT5tlw=ItQGCi$cejnY6Q$u8IF&gYViE_k1 zd^i@<7A@C{06$J%-@)m2VK6tE64xH8dM!3Nf6Eu9d9Bg=g;Q?h!KKs`=7WcZE$XX~~GF6CK=c8OY0$5uD_=cQ85vwuqy21=DHFfOiQR9SC zC?}CT!ks$Pl1|jc=Zv-biP7Jal(SA!9Z|i7{M-_Ps5Zbdw&{9MP8Z*zi+5gUyk#i! z8N6g*Klc+|-Nd={y;dW~=~tri6sr9fJys#E3-%}5u;&YrjAdR7tKY< z6x5H)#^&X%E)Gp+duTj$DU&gGnAmny6gNcuV8jo2j+>sy8r)oevbj}HV;7vDajQ<( zx_$@jqu07wlzTj7@8Zw%-S*Kltp(o5idW)RX?;G%#^bE4Mi0={bSm-`p1~sV)M%4! zN{~%not{9KXF#cQYMX&uFg@c+FW*~1TH@fOMX%^HwbmVLRhJltUzuW-T6r=bNwYDa z^$#Un&;10XXEv`_$L$P5M7vMZ*I_~b7=HPGsI#1jPdO2YBXdftX6+w& zSX{KMzD%62pQ;n_IT{LJ;M?gq{1om!^ zi$zgQ$|+j8>XLoBPJiu>Fb);@2GO#>ug{_xiHkG_6k&)qk)UZ36-MTR~<)q@-1 zYmXv`H}Jm-8pnO+tqmtR()ROeYai!Bwfp9-t=03HPvo$lHnvc?_6s$QDBszcTB)vr z3~rl8BpVfcdLGPUbSu$mmdyAak%*I2;1ZX{E^xQsQ?zisTAQ_nhV7Ea_8@8`_NA2w zGn{X)^1kiJx$|ew8O4xxY5~pNSMYPSx1-+$Bsq}Z0tyP`Qj1m&N3ONf| z2g=V#lmKA;`&Ym2~`b73N-|Fh3?t_`Ot3YJ%^YDHA@lrlVeA6rhclM(z**dw1b zaAr`twGHTI_MFSd>VE1$9q*~pMJW^zVc1+*Z->P-%vgl zS-m|pilKcnW%V=S;JM$?+|0u#@_s+1+cfD7WS?xmHJBG>!$A1~JjGWcG}u{!6d>7j zt<*Y|XQFCy?0&H*zV*KLK-`h9G2=PU)Q*7IqjEx7?I)N&hLR-bQAV_G8xcH?IGuVO zH*#ID;mYI88+)HY#pSftkGUDar}z4ctc-jDS+*nuiYx9JX!(tr1to6_Jx^@<$eE^T zu@pST24ocs)d8~a!rLoN zgk-qQOp$FF`~Dd7%vhWDrBH0pEQ<^zDrB*t+u0bGR%b!(9a>J+-mTrU?z z4==dZ)VL3@HC^zgkGN#9tl_yYZY1hveBQ0&!%#-QImG!fx)1toahbYvkp{?LM{b7` z#w?J`o2uaGGTG*PF4Fykyl&z2QF)t53n87OLdhux3a-paq$IVO0CXIn6O=gX-L)V`99jm`1dzv| zQ%~h~oavr3u(Zu-<*5t)s>cVxVW>v>8kHk<~?AuF4l(&>G8+O>T{6jNX}nQHW-oT+V-^ z>e70;wDhb|i#pYSC@TDA7DL~Q;}LF02(_dyxUvLm@9;@me~INhxjR_PaQW`a*WCG( zo-ZNSb-o@`4vl6Oet2>Vk?JCu|7l*s$(5!*_5K$3W0|lyKaFQczGDLs(6#-a52!CT~v6fOfujgLK2%D|b& z43>WgpfgJ!DKGeCxM}oIqUYr`%h|6C-0zAb=bD`S;iXyvo9wDTwRDUe4v&~b<88L2NQ5Sfj1U5~xBfb}zuFd`=H7RN@KP_UY z>DqaaV>T_ZeVKiy6W@-7Bzz}2+2?QZ$`d3C5WXvA5$+if$A@R{me?Sk(Flqtsd2^R zh*jXFI#A(|nxfWI=XRYGoEzB4#NpG%KDZKUmHu5YPu{IDoe2mu}=nJmp4OX%L8aM&*` z)a!9f6m)Tj@a$1}V7{+bL^Nohfo{(->>QdC=0MeP*$riF9}>I~ zG3WkmE@G>8yMVV56B%)zduTJ&fbcWa7uHj?Bi0e*wldf85*xY4DdNW>9>=f6Ec%qV1bHR;q)*~9p3a7*e zw^atr;`GtHvH5C(&#HD)?%hwx<`?u7>)8%eG!+C{ES=R{djYq>4)F%K=n*nrNTr&L zR0o*hCUMFUDGKY)zDOqz7lXd4ojXf|ZcBU#xvYAH^qiQ5g)qZih}Jkg11u_h>}VCl`-k9ERc+nOhi08j08|ceLMpa-s$&Zk zo&*zJkgP1f{Hn(G{uhalqPe8K9G#V^l?&E6*_TOLr^UZPSY6T`ppMf9au?5Yl!`mm zHB9N+@8A`kFxMQhtFOKry&xxPW>_BKd-ireja>o_^%uv}*Qy+l#6w1kS-2g3siJn6 zWpB|Y!?@Ds)^i7Va4Bucd(&9^XJE?EGkC3DJ(G*crx2kI7Wr@A%~V6eU@r)ZoybTe zJ!LX678kG7j-0Xem_{sm^I+b7-`SV<5bzt=YUEL%KSXz;zLo7S)xKzvBR*+c#vT>q zD8_8MNzYmjSKpnK<$Rwx-BH#rC>q1(>{TpSlE2F5MLh}}jWrsaUnId!Gq;=kk35K4 zE|b2jnO(^nttSi=!_u~*czd;rl5wE#}rK!~)JM2qC zk+uxPkDqEgz%%}x2x`pK^r3}MR-lI!U2t$TkI|z&1^#!APDZUDpA9I*Mif?xBpZod zLk#EeRpZx3+4~4yStCeq$S!htE_98fTLl443N|qWo|E*{c>*=gBcz3O{c}JpPO{eH z-A0DO4NHK0r5!gUfA0z(x8pUz9MHKG&{fPWj1!ugbc-O>36$nyf9we05?`hUc=GP9 z=`^R{t##>R?5T`YC+s*|DN>Uzh;>391H2V8NLAvF3sx$O%v1r$GDfN4xsM};t5`%k zOSa@MRm{ac?_Sx9{UT9|`qA?xP1IH7iK@flSO=hkr^-WA=;YY5aaiB}~IfJCE#y+J9r^czC$mGAw;T`9mO zLsZS}N2-Z9l^u(-?YK&K4 zkmR00l`M!6^h1{t8J-}n6ZCN3TraCMWtf|^XeWqdcyrUIJW~X*=!4EBiS-`A$7 z#ve<^1MXz9F^6%Gg;5{oF)`Fn{qmY{(xpqtO2i15 z4PAuy2wSa5pVh&CrgrLVs=FqhTzAgxiiQ3cohx7wZEcfI#PqGVQ(edz53KNZdg}FI z$yGg%htbliBiy#Imv2I;SHjL~@4R8F7rmj}@Xa4$a@r|@2q4dct1m_1Ay_gRkJhZ3 zc-o{}w!WF^v=hfKCZ?v;#P__=QJuS(TfgT+LEN<^FIB-w$kQkrha)&X6CLk{!#^;b zC%)mm$#Pv%H zS_E?iAON=WUT{Hz`4JXDe?NS1g3ujRLfuD9qS~{I>l#740Fnq_|z2gR`o}GBXt%%Xy z1ja8YV7j+82N2DT2$@*}=B7II($=onw_bvj_xMX>bgr10T{#3R>Hw1sr0n50JQ`Gjxr*U)yT**HJNma+j>v=6q_o2rN!OfVWVH@zst^k*>e(` zIR4E!2FF?#8s04$X~skQxkq4A)M`^HWdu-KvHwpf7inPu{ovpK5#2seN&KVrR(F2& zsEoMdGn3U@Nt4;l8_-=5@YM0dn7L!`y5k`_To#fDoXA9V?0)D^KfoK5VDX?z&Zl~+AKZe})WqyH?K9)%~tR*{K_o5M|v_`%M$S~5Uz9%r)Yg?B3cWY=iX=vK`T&<6 z`|(tY`XjZ7Gw%sZGMn#NEzIy1Y5s6vvKa_e4>Zv zI*k2(Caw*?o>Au5wp*KY=*aWnBxc>8J|rkLW{sq&oE7|BBOv%@awWNsrv$=1Ou6Gu zelDhgnUE;W+=D3vI%{84DttWrSuI50B95M7Di_`7K@9Ndh91is0JT&mZENgO3VM>y(*K6rY$a z&{d4C5M1J=j0E-M7Ywdl(a{%>>8tK>fq2PjKDw!-X6yyAH<^`O^*mVj{gmHOy*%KP&n5`+QD@Fo`u4w-~a!?a`tDXBa2l#Gc;K`{)~+ z<$7Hc=_iTRUWL=|EM$8pL-R6FTyQaeya13)w-g0sTA#cnbX4j7X%(z&sV-Pbcf;(4 z>UzK1DIi=5T8RCBs-r{bsS5LZQ{t5gKA zuwL}=xr;Bkg~<1Ogl}G|znzZnyp~pY;|}X1eKp$~+56qE;nz;9C0lNhJjcnG;X-(I z81Kaq=&4M{=;G@u+FHDiT95qa9G~62wn~-6(ETWKmZ)1u9%+%luWir!o~Q;u5h#a} zCqLPbt7mR7dY&6@=k|-yn!oeNIkhoPYJP$17;>6M<|c^}-S7tOlTaFGJuS`gozY*_ ze4q@&OjEA4j5>GuZOG=A^Ca07)-cJnF!Ct4b|?yQA>a?_gerc%JF&^FULbRFewshb z666#slA>J6#`j8NSy7@s<0Crf$Wm7IW1>cq_66}xv0YN%UajB zojLo)(B@hc%=@w6i^m|PQKXVGNax;$a;D<~~5@&2S zgI3}X(?2?Sd~_Wv94MS~m6h&lP0{|&l{OJYX~Cq%qhVF1fL#l%LPa8|N8o4P4S1?% zy_*#(th7-~@wlqWBqRDkNsA)%3)>nL>;s8jxPsqFe}g~iyo6w=2&^oAVt!Fsf!Qh! zBr$Cm(k+;}s_FHKB4i+Lw6R<-hoFJ5AU4_>gk)kOl`Zl6%gW?i3CqSYQ>LTtzKYcp zg2rJ(BJqN5`LyPqob-K>+t0U-5&)sLCJlmA8@RH&J>IfO#TTcY38Xq1MZa-Q7=_5J zXx^bEu*TiytEX*zb!jlCC&wE&*?WwPwpwg6h`5DLXj1a<@J2caRq)%0H3{lTYtDw< zQaF=vy*=|#is6Yj%$9q!IyUV=CVBMyyor*!F2^G(FVEC2J;Wo#7?hgK2Nxomcnc*a zR72+r4qpL%@7dYo9p4TqBbVzZ#+R_qj@cG!%;j!u; zV0AaQqu~|XNt)#d%U5NVx|}yXQ$CeeIfjW%zXzg6#r22OU5uPhpo}e?0f}9i%0>pk z1y-^IL2Tw#yW3~lD$2Q!?9E!(0?>wpsdgj-vD-98#6vEYL-M`0I!)SlL+#t0FF7|- z44UEWgzhDej>HHs%^`;AF^bt2$}+8dmf=zR1HbfZxo^Wb?db=~N50lRTmqvgV17CL z#MWXs_%sPl8H_EBE!|3&w|ZVs!CW)fB$VUDwIXzO$t9$Nh2|XF_QCMY!|@Yx3PzNq zlQ$P|u}z9V(T1tAf+1u_m)TRNYbsffY9mx#`CK8+NV=2GqskMX>?Y=}8yS1E*OL9w+WVz>t!ty(5ZAdAimsrEp(tf=88LEz zMK@luZF5sDRfuyb`mMTMc$k(VAFhru1k7{@utaDMq`(1Tbir=w4^GUpcs@0>DaXFC zx5=HJO}njcv7a-?_)6L1L>1sm_~W~+f!tIA#B8z}W^)T?V>Wnfp4qiK*MS&)Ss}yJ zrqao=5>3H%zUQaZw?497WgGY>({Iq|NsJI{zK@V_Fz?#(5a$MzQTfWJfT@QGu1|el zH*J_vn!PH!-4^L|?)y)BOS1(?^^__*I2yql{eu1S!_2fQe^Iv2{L=hAA3FS6&$L7~ zL5{ldPg|}K)jq)pTbYrW4U|=*)rBQpO0Y$_d@e~7;)LId)+0SH@HthUhXJr7cmv^S z&ee(P2UE(D+coSNRJuy{zYN3%5cbXt2OH+qyQ{hc^Zo#M7uu6#3Apg6vOi8lVh8|Z2YBB-Pb;iDp%D( z;#d7sg2QO`88iDoDSk1EYjF9owB=nTKki*E^Ea z;<~p_bRCyMbUJU2O?)%TnNr0Tpub|MhZMRQq_=Qd9q5F(dm3SU_yI6_6yvhTGBS@i)cmEme`n!qh~e);*)*#!=il z{d!U0K)=*|$23thb(2Qb>c+;J^6JdvBh#>j`O7+jUTj88PH&=@KvX*DwY8(N(_)eu zVXT9l7mPpcNav;c{HnCNA|;ZLMDTp3IA|ae^@3yBPY;nxUO>n<;yt!EO4J;##2}gH z)C%2s?s~hY*aO(37-98oJ*27}KHKzluUu5@=oIohQX@dBt;$8;vheYO&R*B4MC0LP zUp4Ak9zZ~iK%8wR<`THt7a6w&ulJVOYPpYwaD9E5-FJ0V6JFOYFD(;izLD68oT0#G zY^ULF;9UUMuzg4z?p@X0w*%HMh%qe((#%w6Yn!z27VPmGx5mOV7qLs#k`at~Yol zxY|fc3O%r|RJ^mqgSigAKFIj=K+yYPU@7ENwSRDNv9HSDg-+!K=_eO1Fm$<|rq`NJ z|L{4^lb}M7~Hf<8S5W%vy{e z=$$3?Df^|@`nx+fGRf1Iml)MEU+*!y%=UTmIDRB`Io6Y<> z|HGy5xcEqwIj`jv(cwfXAY#rVuZgI;QNHz#G`&fYNpw275s*Q?tT~1ScgT-D<+@u}-O>`Xx01r6 z+3~Qglr29mRWP+%cz5c%iaM*^ZOnE9d1o1x4d`@6T1}YKe$_u;li`$K``o8tbgd_* zWSdRR*&&45IM_66mTO*q)0#(fLD%{<*VE*C5j-4zPIfd9t5X)@{)x&QN%%AukfHU6 zFrT49d3*?n)uM zGS0e}{cd&E@5yAtlgWyfvSXQLV890a$>;Y8sk)<5XxUPz`c$=q1Yqj1E0XFzwW$8;7>syi0K@{q`{EAk%yq?0ehNL{xip7#|Ob+6(i_llKN>RV8yAwC}5ay z6S9fS78@daP_6d{;uUw2v$dDu+GXXTZ*I~zkqXYjaGTxC5g#>X3ZD8q1rD-vQJc^i z=?J^H$BCe)>Mwz(kw<<+iv4?3%V$sFTHP;Da+G z?DlhXH3nayV6q6Dir9%?)*!ftz5`C*qRSr!2Bp1CYZNN&#QdPpX*P9m7Ip}04>*@A zezw~Gk#wiN>cNy8+GFpz<$k>j`*`Ps?a+mJVPRg!G1ubauVNO(rHg5!4XQWtnv0i_ zUlQ5j=casy7>RAWU3<%>mDcN-Z90x>TuN6t`nd1PKR7t&z&)ARDFuLXsoP=~&u?T_ zrDry;#b2@XA<9l>x?0q0SWMfB3?4~~obh-3S;lEE+Wkh6r1b0IG40I-mb~{^BRS(; zRlkLWs4ydFkRE`!|3RFf%DUKF{8+#;6Qbw}eVV$XsjA@c)0*=YDvRDcfg_t12)l8S z`xjYA7YQD^L{YOTYr@rXR^!>S>^)_TtCG?K;+||Y%x}JxO&Fy-yC7ejj^jg%$N}Gt zYbE$uzD$j{OmMsUbG;Uk13beKC}JMjdJ7?=A%00d=w}q#_v}b)*5lpPeTfwHh_u!a zmT=-rPV%~Y_5LOTh{Us$waCrGsYV~wjyI`0@L8_b`pbK2l{>oTm}@x2tH-6hk%%y~ z_#pJKrb%Lex(s!_mEdmqa@7acxmP4y*6ZuP9w5YaRFnv@N5-@;;JZHJo{O1{kW6{Q zva0FaL~%OqrC;}c#pgeK0eu}8*@dtb8zM6z`dSo;7;M&W5Ks5V4Fm_iEK4C-zOdvW zSAb=g!nw8@zgiud?t8LtPUo_xjKEW$< znye}qz~05nj_<1dxZc*@MVa1iWnDw=6EALxcDfOD$NU`pg*?Le+tcS_Ck}|O3oLlZ z3!g+zB4w2u)uQ%T_fbTpP2W-J9p@BT^P2efXpp#$Qbx>#lU%*9d}&{OuSvYuA(Zis z%<9q93d~J*YGPxnIkA7Xm~M-i!gf~2z4k_Qp7a$pLAN9xX{r@PMS+-)P%rZ2B@~Zu zFk{4J{|?Xm#pc~?hY`NvN2sBsdo^3pBg4)v{dVH&&R%Sqfg?Z|G|ZpmTvu7EJMYnc zjHvc!%2AOF?p2+rBm_L22dICN-V!MTRISL?#6WSk4Y%5Dk_{<_1-#ypdH-0rc zJF~l34FzP8w-INVm?h-Pi}CP3BnL=)2IFTPm$}3DVt9IHf;< zoWDliS>ojgyM42p(sbqMKNT9y8Yq0SDLa-}R9}my=(xNXQ5|c* zM07uPQ!p;~6l7<*sUu)N90oBOPGDp^1tEGMRsF~+>KDT0Q*G8njM@Y1@3+|`9o$(~ z<#y`gZY8ia*h$Bb1GhG%z72KNB4|$Yl?9Yp*upLA$h~#8qgufy)zll3-Nzr$TKxS^ zcbXiKzKxK5RqKufw*1#ChoWBOy-cszt@outkRg*}1@v3;8915nHT0-zR?csY1po8-1&S zgC{8I76tTE=i{Me`9rOF-!;r7W$Or{ivtEn4*>@EYQ_!P~|1g6e` zgNTtQ*s@A)_L->8#2Kj*u5QZ0fTgNQmdUTvLo_WWRT5Q8?lj4v*&xrSbUx~;bzi`wGbixJ zt8=EOPfQZqD40^z9452-yzyaCqTCjmE&$wND_njzeNB6;><7Qj8Ie)zwowtg0+s@_ zBHwZydI0+821fF~Y+&B18~wuu=JVhMYWlwF*p#`Tj5c)40zUx$9g5ANCHNIlHGU!r z8;rX_s+N-OWlZi_&;7N9ar?I|O!DaGU|p0E{2Vc&1pa`i`;Pb%8b${@HGKHSp~lR@ zyth7`xk^ci{?l2n?(C`0=KZL03@OVa95denY)Q9bhEG3`B{wnDs_eRD0YTS~2S0lA zzIE(ZpecOA)8A;YAo5{WZP{-wOq%|y>b`cNl;8HTbPS0!j&V+mYfuPxCcH&=#(N>T zXKuSMT=W*O)De|Dsx`D0@}doEcU=~tW;5?vp z_f*7hkmUX4zApKnTivQ#P&z;^Rt@<*+7)xe1Dq|724_AP_0maj?>Wg!X`)y@3MD9X z1Zid!1%Nwia?X#Ca5IDX;zN|9w`q4S?G}e12scxtwTPWdo`wi;RdH z2Fra6CdM&+W}~}6yz1i0{dV}Lm)jQw_~g$p)UNU+>&xy&T>Eyak=H_Tn(J#2{S#G% z4plyn-|xt{#W{IUpK?1z+ihH7=G<7-nK0$#4!SGKfd9&I&TA2fnh_X}>;P;%fkKNI zUP4U0H-4!7R8C3H3|nM*|6M&Fi}~u^XBQN%fFf`5XBO7HTScTyg}|Sj<~1?nZKt39 zsC_~fXm~i|DqkeiV?aB~#P#(~aFWj2lbQ=EA1{2w=tvBFeu1 zT2RD=yt4?!DJM2PI6Hs7i}J49^LuGKxz$tWkxiAeksS=LNRHM?R{um~_0o~7dndCO&4sqoXTfT%U*ksRP z!}}QI&)alfF#7K4l{HcM`K-%VG3m-P!!8sDem)L zs{P0R#ol{HHP!ZgqM->$Q>3@hL8>TCT12FYfPnNS0@4ITrAbH>q?b?xq(-EPlt}MH zO6Wy8gqk2KLLd=@C;`vD&#d*FGtbP2ci#Chvu4fLYwctuSKI&cD*`=%)j!&W@@ox) zIi6rt#!GGaOFTy|cvwEx#dimgDt6&-ce;hU=CpY6vPk-EZ2JdaKL6KxyECGjTel1o zEX=MoWz8yygVa&g%GyGseOLwLTb~a$Gk01Nu@}sYQaKo2WG=jrwdrK$f+zKicgovj zkNnx5F$_L)X5O?IeE1Z`Myy3_z?rw#*gv}~E&8N0ykibk8kBf*{P2O@Nl{ccD-c^1 z18k3f!B)LcQx0V05e2ZJK_{UbC~FtUPfNR-s#Pnz)LT+EQ<8%M*Os!;`zBAk*$1Uu}h?)l&8epbwFh!KO;&PE3_|EaW!@WnYBQMw|zJOee z1saCFfS5Fad%u9nH6IC(Gj^@L@Yyl2yerT{I!2>5#T&|GslT0?+9*NuLEy1V0c4S998j87L^>tLR zuZoq2-9UHT{DdYfrw^-0;6E`WN7&8Q>;`Mw_M04;_-xs2wO(=gAuF816NR}G&%i78 z7&UDk13st2OSwSYAv@w=aa;)%FY~O5*8@ME>SeYt>p8lh1wd4K@RPhD zKC)%No+5TWtapAZ(B=>Cp78jHV6ScLWgBkjPZJTj=U+23z?VseyEA`5QVS;re?bet zT9sI~A<2>!GhY2Df>ss~dBcjQ&Af@zS$;>o6%KGWc#x4!lMRUAs?xWPeiiPYaBW!O< z%o1)q`UMI()+z4}ov}wy*psYjGh6y^(`(Z&KhT0nqJ5!ML`9ry?>@CJn3=)^oTG_LE*ZeG6m~WFCP>?DU!ekV)gnU4d%NM8*JRZ`HCv}>nz)uDU6?OoVY{5nQE!h~E^PPsLw5|Z%i{bASj8sS9Zq^w-W%&A~YrUI>#kdZp- z!BBlx&ydB^ZWjNs4@QqUHwK;hjeg7;Z z_@^sDZ9^7C4*=9T+{wBn+n;L-H-0VbR|h=u-zV&CYQ%KS9cbAai?Uz!oKTa#@!s}4 z{UW*^(f1dW3MbAY*_TcTVMQzI!PFM-1}lnz@xL}ZiGGHs>QWe?xgv{@KjUkvPr z3+Sl$fiIMhI>Mgbnm?@4mif9it*UlSmziX&9AI{#@(+#J zHe&REi}dG4Z9RjTPU8g3SYvOP3<6I#W=*{6{bo4UuK<3`LUO{9If#klj_h2=7tt zWb;;B*ceO=UXM%jJwm&8*T+8Ws_Td!aCg&E_&!Zf*HSe!^duxkz}A#lb!1LHA&%M* z$KO%HTDypGaTPJk266ha6PIh=MG^wePn>Z6rhO46nVkpgg<*p&luOIFqdGUd={3xn_#EmTNingU zBsiNro#YcxoW;7VS#ZvnQ^E^=MLNK2^;OYxbP~uw@%c;%luDH6?{Q)8>TaJ_ zmVP!gZJoG##jaMNhwZxVN96`a=Q-+(YiX}PKWP{NI@v{7q4a8~Cpl*m&N>*0-)A1m zZk}J0nkBq#4p*!hRuGHcFFy>HxiMrdbl>K2$u}f`y{Dnr1I~LLz&-)LN;e^CIxJLM zVkMW4{I8i>`FV-c#R)Q}XE5BqIHWlaEBNkO$JV<~L-CsFz5-Q3&q4)6vbMUX4!5!L z8pkrrci#$^x(&JWNM1enIg!=xBe{}DV*%7U)^YFwe*XuuvhX-Uh-YJ1?%Fqk3}_Me z!pXf=xovK6wD`Bzi7D9&Pag-3An|X|$NUm>x15$cWMlIy;+*N82?`cBE;D8+!O(_3 zfY~c}_?O7J({8;G`=Pkhf&r{Ba3|hj3wZr+*y_f0MzXuf^j7_AW5zhkwp2BsBm2ld zgEt1t={b_V7$sXX2Y-M_<%^3CgzEa87?V>+99trb<-e>7$nEYh1U+Z%UwN!9)QgB( z|AK3WINveEJ9F3h{PCrS^Cr03R*UTjTS=jI4go5^mNlT9d8e?fZO09b({ z{wwgzc423sAF%}91S6nsRbjCUn`3>zkMJl8P;cQwN8uIaS@&OQqt&UA8*`GTf)M=a~7Z2!-g-O%>q@6 zzTsc;-agOOUhOIJopIjuYL6n-B#B>rQuoo0VN^B}H^`~xv>@167kb7+e{}ON=v^rN z**U6 zkYdb(8@MS)Kpa5SfSf70RGmn{hAm*Dc?oN-U`0q$G0jv%~87shBuR=gc|JIOl9Ww!Q}UT$seW@m=IK#}oqZ!+#4os%>u z(uz2Gq)yf*9`w0TPwmSgU1z%mCxb)Ff2c7}-Bv(JTr!pwzw>_4^KRmDRE(gbIIx(2 znx5xh5GTMI8^u8qUHC`{iYNj%E^9H<|FQ3@b7{`UarSXWDepy3+Cz1EKUue^_SRh} zgw%efhyXWo5A5^#m>wb7paNug^|sQAlu~m^kY5l-BHNv>jxm#PY4>PXud`7&hZfU@ z;?n3wT%4BU2L&V?e{!pCda5KHgBf~y(lu25-V7JDrK{zovOu|fnyDoS{E>+HZaPe; z#8{DRLr0wb95voADgXPOtGC}~Hdz>5ES3U2G2xQx>*j*>z+{MCDO&V^f)T!JG6||p zT(kF@dX`y{<}5yKR2dZL4L z>E!nBO?v3Dt=f^Lre=m2)q{&qo4B{`(uIe{!yg7RQ3OxhbS_|ms$9t3<~**x4kEs>tp$!+kTHD(4Rp3z`UK|#7qn}pypg5TVLUGZ5RCI^wnAm z$(EeaWYqcon+E0ri2(8%?YWFf#V)++^uxLGI+3|C-7G%i*~NkM*sBe%SE`FVZe48L zh#laKreT?GKOWuiEe$4OBqc!6%V zYdVEqDmhD=srL732?O}MAoT~FR1d1Md-0{GwPy?zy*Zd9MHxUZv-o&9kRVZtVo^uE zYKfNv4rfHtJ`Bssq;!?4a}GGXURiGjWFIN(;G=;0BZVTQL)SZg*l%R1+3nGfzPTq}N3 zli=J-cfilYW6ir^G5Pk%518B8O1DfaX`OHlXgM7{BFCWAUY&9KHXj|g9E*9ldKZ== zeSAvkto3fCOB!6vEv#1sG$6GBo8-w|Ym(+CA+^(NiotP_*sP>q#Q4Qpv02!Zuf0;? z+jhnY_kkyaZ;rB5PE<&?8^~UCO8YGhFb2%JyffcC=5k3StUM;ZM``?$cx4gJj^<^- zhHVZ-=`C`aV@PqVgdnT>gwO%{`-s+2C{n< z^y&iSA?L@LZc(^TzkkgkZ}jia_rn!M`@fv?36T+q7WSh3C;%IS8UjFe#iSx0WDok{ zp`FUO=tk_7F$U>PVXY@3wke;)=?!c^=>?Zul!&OKtEUCsEaX%OHyPaP!_`FPPPaPO ze;jyjL-*&aiIF(kl%CN0o5AOw(RF-Kj}3ybQRM4@svjMh?ZY^VqCuR-{x}dxURyDhpWWsUw!=Pj|4v5-@R)s#)yiIH){+qOZLcj6>v!*Hm9TRZcT9do&=tbJWmB2d zG=S^=aGVm&29WFblK&H`r_$pb4*@;a>HmnePpz7rWy=J-lj>CIRJN{K@X1QO#0aA8 zXUpfqs`6iwCVIK37hju6;N6$_r6wlnvN=EqHR>Dmd-OBxv;7!)A_!#8`u1{PjMV|zgqm#~v1$L{)$>0zmzfs()5b53 zv=?puz7S3yJ$o%xRn>SC912!Zi<=3H) z1T+n+Cr^fchc^xvY>GjO)!A8D1*nbONQMMBKDeX9{?Ii>L-{YLBY8`5woEU%09Gns zpw9cmsMfW$$PL{vkrjkrPXPkhmJMxr%JmJ+*V!w#`ai7S3CcG5BhAWG@c0#hKAUEL zq`1t-At0`N+53-nV@QZi5a1EQ9&<~d2SnwNc5~s9oblZFom>k;_wvh*47!I>2jX*o z9*Dl9b)kMi|JiCMrJ-10uNGQpW!xUvUa#eGGSmtlX_pzFdjxNM=uAdfCr zAp3+QNQQlpfqkxQT7lt#?j>QVdwvGS;+^6S+H-k#X0Vtik43#)s*9ucK&SqAFZS)& zaoFit`T%{~eRvtnG3;P7ux}sBy+QvHz`X3owgiTg==-^g_?tSXX%%v?4AX(~0fAq; zQqo!4u{9k7_AgdKc30RS?^Thc>vLQL$mYnXh^Fiaz_r(ogx_E)k|S}VUTMi!lk5Me zprw2(mAWpBsxnfZ9?QDIK+`ZVj!sB=a z{YERN$ovrl;ezgA`?|sVep~RAGk$XJ$tBplEkNx+M;~4fMp1{=+Lz60l}T_07J76x zXMJ*K%6)Chl*Un5HL>7uiYeC+kDnSU&U#6EF-><@UqzM<@pWMnLD=4$WGAEhCyAN; z&&OcQq<~!cf8EN!ahA!Q15m9fS;cdw_6o^u}ScuWO>0_YA6G?0336oM}(?gfap z&qDohk%5l6#X+Ltp?vPu7wHAq>)y+!rjA{`aSnY8Frq`^&?#C{w&*#5I$uq+mwwIwARD6NCpZtp9nDWR#N<7DR3x^b;QzJ#mTRL( zOLMcI&&8pM`=MUCGZOVz#@H^O2t%z&uDdy;j(ewR-70t7r%?M!r7qV+d1}2NZDC2z zS~_|5f131wav4rr-sR}vMqo^%iU{;Z0laZCUd!s~TPI&2RMB!1f9cVp+qgU|lFebv zHj=|tjZwXBA^IixoDIOO$D;_Ro9kF1Q$?KNZX}KnXuj}EHYOb9Wyikt0P#U|;J{G8 zBh4ev7-Y>eIA(NO(%KTW$KBGN@-R_1*6{8~-{*;b4g6{FD-vm_k5OS-xPEQbf2^97ezY~MlNE$Yh4^cR z0_A?x!gF~^cp&v7AO3&^D*l+vol^%bdG&u{8%$#t0 zIGy^+Roj_vZEu*=XNop4X%iW9_;q&hrASNUO@`qEhj}~PbsqXHerLNEA^Xe*pZg<$ zIQz=-+eC(Wad7Wjs^90c5x!Z~(U~0(<0FiWV zCn|RDs{3lEr9`XoyGwVMKp%1-EA%gSAOgTSFWT7%jncB-m}Jy@yQ4$jiLldan;75(LwxT{})(UM{V9oGvr6$&53=Yfn1lA^d7z4wqMa%SrtUdKAb= z(IdN7hq>eS5S1?E5{H=pw~X1oSM@1)*&yYN=%_l8hs^ZiEX%s;N0a|RuTlSkUjGSD z7fx6+D!J#wYWIT5{fGJUymBj>dOeHK4lVbixG5FDuKOY>8&TreHMjc*8mnGHSER?w zYJKm?*v$f}VJ%j_Z>Sf*AML!hsrZ5~r*HIDYs&&f3b!1uct20{EdxF*TK3uXPeba$ zq|{%>p30(8b|9u;oyOOPA6J6gexi1*{(>a8aHu~!@V}t4jD-}+C1MY$A_3yF&%j74 zU-QG(#!rm-mCxF5PQ*!Rv}^HU<7xSRYnv)W^o(!kFR0-kmLe-W4$^4;B6|VT&fHM7 zGVGxMTjn>K%=uM$jA0T)ZR$PG36%1aaOib zuL6cDcI8@>$^A4ch}mdhpr)89`_NQDr6+CWFWzk{t3s1kZteh*m@M5AxAzORv`#o0 zvRBr2nK*J{O|KvI&m^=5qk4SLsyn@K^8ABEm*yzEc%Du`IJ-GxK3G6?i6dns;wm!N z`~el}YtAO5XPt|3fi(Uts?(Ng%%iL3v!!T9ixi`>*XKjGf+|mQQM5LGOdw0&`O#dr z*Wr4U{jbYzUOC3tpa7F`O_wzU{N6Ko{c&<53PgbuZ4*`6xcC_qJ9EpPaEh9=uB{C1 zE>c;ItqgvoU1*1`KfZc?{N>0SKVIqn(G0>SK8p%)T-&ZX89t0SHh{-!1>zx06k#Op zj%7HPLs7A01+{JqQ6$f?ododCi4p6V98Pdl)Hk=8|Egd2;>_8Uq*F2dwBJuj--Vq?_dcCYr^+ z%CY*vEkiqhPD{J5Hdy~=}vSq4kg zs3NLZa|V0@&M zTFH5m;E#*+Jva(JEov%MG%NZsXh!^g;zTl-6gcN6OI_qHEfV?&dTHTYw*-K|3B-dj z;|2>>dWfFV}PF*^k z1dV13zSPe8E6-Sj1l~GqxAj_f<%4`#hd+e;A%FY}nhLUn&4;#l9epGFfeV)XOFz@< zX-N`v-)#PPzVIn;VE~=bEed3%v0Ad#eh}|z3g1+7CfgEWW_LiCT5y!d=L!eBT7Lfy z$!h?S0J~0FD+D<98iUJFmTIo+mlky* zf6FYKQI)&)gymU_Tp!Vp8AVY#vo>emtrOdhA%mypcY)?fe3&lm^M<2uH-D7k8%o-+ zxWwn@vbp@P8iA7u-V$!Q_r`dZe3rXF} z-oMs|4i%S@jzU_=xXp-h^M3y7PBz@28ei9v1kHynOy{HPYa8tSk^Q4yK{v3FCPfSz z2gR9Wot<0e-6AANhU2F5UH@H4b`3Ecpt3>^C&$K%Fvx3D$3t>P~VQIbQw}F*EX%_iGim#m#%0 zrP|vrmnoJlVrJ%ZJ{2LbSfG+;*o%sFQ748*L9KAEJQN2~){4!vqwln%89!gSo4pp=@ylysON%pC?-E~Ydjj@S%J0&>tp*Dp z1KD<0gn6?LBrL)iH~>*Ez1soT`Ji)!m0@vxlJi@5H|Ayb2|xkF-@iJUfj){Q(r_cc zOLhJ1yb6h(sr+6?JMmX%NLhSO5M6YAj8^tyS*w;z3+{I)eiEC%iB1*!>`pRINFfpD zD13#{DrN5)ZL^ntmPv@vjeH^gwxZlvphHpY$q9IPJcQU39O-LAvdiGrr(727Og#Et zH(>2{Pq*m;D|gZF;gPe2GRC$ZweSPK0Dz`k6pMICY9bPg<6pav>Q1-&?}YvAvYeh; zAhsQ~>;@7yHv^(iY*-BYu{ISRa_7oUyPiI&mwOssHv|gL-+D9r*ggI6c!$vaAxpoR zGgjZD^~LpO$u?X>v;cE3BSA9e&t+%hlCagFRE*K~0NZo&Oh2;OmLP&hvrgh7-3QqD z`CE&4Dfy0$;hrgEnQ08W`X=Iat)bIG1{s1ML{MmO`|M#>z0XmL$|!E#BWFvnC?jl& z!B}j$`(CJdwk_|?ilR1}izZh~OtO{troTSN9{aA2lL{md0}&3bNH5-Dt3-<@TaA+C z%lB2ie0(`KaEzSWF zPl0T#+Mek2VY1gfX7N9Ta|jpc(pC4e)jWQC2ECRE-$_&YX8rYnl2W0}V@H7enbdZ~ z>tbaYtHzy>>D1)p(0sPb;e5E)IXSJ(=6DJ)**z6$$$occ*T(U^FCXfwUZcozh{Hc(~{e?G@9qVES z*3ndAV*Rn+{cWC1KkIDd*14cpPOAZ?t=JTv_$@|D4{h+GE_LE3&)x$>sE3!8s6seH zEw^}qM;^ZWGUX|`lJxE?b`!7KCHX$vu`{09kgn4)Vc4$tp6R0cn(l)uOc!Kl!V4t~ z?#*Pj*PD$?t!kgLrN%LAdLk+kUiZ(=_nmYfu)X~W)4WI*E}Oj zwB&xdFP8eCu8+S#h`O}QF7p$Ow(LPpFR}@k3$_h3qwYo_4F