Merge pull request #644 from didi/master

合并主分支
This commit is contained in:
EricZeng
2022-10-08 16:09:40 +08:00
committed by GitHub
180 changed files with 5077 additions and 2232 deletions

View File

@@ -13,7 +13,7 @@ Before sending pull request to this project, please read and follow guidelines b
Add device mode, API version, related log, screenshots and other related information in your pull request if possible. Add device mode, API version, related log, screenshots and other related information in your pull request if possible.
NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](LICENSE). NOTE: We assume all your contribution can be licensed under the [AGPL-3.0](LICENSE).
## Issues ## Issues

View File

@@ -133,6 +133,8 @@ PS: 提问请尽量把问题一次性描述清楚,并告知环境信息情况
**`2、微信群`** **`2、微信群`**
微信加群:添加`mike_zhangliang``PenceXie`的微信号备注KnowStreaming加群。 微信加群:添加`mike_zhangliang``PenceXie`的微信号备注KnowStreaming加群。
<br/>
<img width="116" alt="wx" src="https://user-images.githubusercontent.com/71620349/192257217-c4ebc16c-3ad9-485d-a914-5911d3a4f46b.png">
## Star History ## Star History

View File

@@ -1,5 +1,66 @@
## v3.0.0
**Bug修复**
- 修复 Group 指标防重复采集不生效问题
- 修复自动创建 ES 索引模版失败问题
- 修复 Group+Topic 列表中存在已删除Topic的问题
- 修复使用 MySQL-8 ,因兼容问题, start_time 信息为 NULL 时,会导致创建任务失败的问题
- 修复 Group 信息表更新时,出现死锁的问题
- 修复图表补点逻辑与图表时间范围不适配的问题
**体验优化**
- 按照资源类别,拆分健康巡检任务
- 优化 Group 详情页的指标为实时获取
- 图表拖拽排序支持用户级存储
- 多集群列表 ZK 信息展示兼容无 ZK 情况
- Topic 详情消息预览支持复制功能
- 部分内容大数字支持千位分割符展示
**新增**
- 集群信息中,新增 Zookeeper 客户端配置字段
- 集群信息中,新增 Kafka 集群运行模式字段
- 新增 docker-compose 的部署方式
## v3.0.0-beta.3
**文档**
- FAQ 补充权限识别失败问题的说明
- 同步更新文档,保持与官网一致
**Bug修复**
- Offset 信息获取时,过滤掉无 Leader 的分区
- 升级 oshi-core 版本至 5.6.1 版本,修复 Windows 系统获取系统指标失败问题
- 修复 JMX 连接被关闭后,未进行重建的问题
- 修复因 DB 中 Broker 信息不存在导致 TotalLogSize 指标获取时抛空指针问题
- 修复 dml-logi.sql 中SQL 注释错误的问题
- 修复 startup.sh 中,识别操作系统类型错误的问题
- 修复配置管理页面删除配置失败的问题
- 修复系统管理应用文件引用路径
- 修复 Topic Messages 详情提示信息点击跳转 404 的问题
- 修复扩副本时,当前副本数不显示问题
**体验优化**
- Topic-Messages 页面增加返回数据的排序以及按照Earliest/Latest的获取方式
- 优化 GroupOffsetResetEnum 类名为 OffsetTypeEnum使得类名含义更准确
- 移动 KafkaZKDAO 类,及 Kafka Znode 实体类的位置,使得 Kafka Zookeeper DAO 更加内聚及便于识别
- 后端补充 Overview 页面指标排序的功能
- 前端 Webpack 配置优化
- Cluster Overview 图表取消放大展示功能
- 列表页增加手动刷新功能
- 接入/编辑集群,优化 JMX-PORTVersion 信息的回显优化JMX信息的展示
- 提高登录页面图片展示清晰度
- 部分样式和文案优化
---
## v3.0.0-beta.2 ## v3.0.0-beta.2
**文档** **文档**

View File

@@ -59,6 +59,8 @@ sh deploy_KnowStreaming-offline.sh
### 2.1.3、容器部署 ### 2.1.3、容器部署
#### 2.1.3.1、Helm
**环境依赖** **环境依赖**
- Kubernetes >= 1.14 Helm >= 2.17.0 - Kubernetes >= 1.14 Helm >= 2.17.0
@@ -72,11 +74,11 @@ sh deploy_KnowStreaming-offline.sh
```bash ```bash
# 相关镜像在Docker Hub都可以下载 # 相关镜像在Docker Hub都可以下载
# 快速安装(NAMESPACE需要更改为已存在的安装启动需要几分钟初始化请稍等~) # 快速安装(NAMESPACE需要更改为已存在的安装启动需要几分钟初始化请稍等~)
helm install -n [NAMESPACE] [NAME] http://download.knowstreaming.com/charts/knowstreaming-manager-0.1.3.tgz helm install -n [NAMESPACE] [NAME] http://download.knowstreaming.com/charts/knowstreaming-manager-0.1.5.tgz
# 获取KnowStreaming前端ui的service. 默认nodeport方式. # 获取KnowStreaming前端ui的service. 默认nodeport方式.
# (http://nodeIP:nodeport默认用户名密码admin/admin2022_) # (http://nodeIP:nodeport默认用户名密码admin/admin2022_)
# `v3.0.0-beta.2`版本开始,默认账号密码为`admin` / `admin` # `v3.0.0-beta.2`版本开始helm chart包版本0.1.4开始),默认账号密码为`admin` / `admin`
# 添加仓库 # 添加仓库
helm repo add knowstreaming http://download.knowstreaming.com/charts helm repo add knowstreaming http://download.knowstreaming.com/charts
@@ -87,6 +89,156 @@ helm pull knowstreaming/knowstreaming-manager
&nbsp; &nbsp;
#### 2.1.3.2、Docker Compose
**环境依赖**
- [Docker](https://docs.docker.com/engine/install/)
- [Docker Compose](https://docs.docker.com/compose/install/)
**安装命令**
```bash
# `v3.0.0-beta.2`版本开始(docker镜像为0.2.0版本开始),默认账号密码为`admin` / `admin`
# https://hub.docker.com/u/knowstreaming 在此处寻找最新镜像版本
# mysql与es可以使用自己搭建的服务,调整对应配置即可
# 复制docker-compose.yml到指定位置后执行下方命令即可启动
docker-compose up -d
```
**验证安装**
```shell
docker-compose ps
# 验证启动 - 状态为 UP 则表示成功
Name Command State Ports
----------------------------------------------------------------------------------------------------
elasticsearch-single /usr/local/bin/docker-entr ... Up 9200/tcp, 9300/tcp
knowstreaming-init /bin/bash /es_template_cre ... Up
knowstreaming-manager /bin/sh /ks-start.sh Up 80/tcp
knowstreaming-mysql /entrypoint.sh mysqld Up (health: starting) 3306/tcp, 33060/tcp
knowstreaming-ui /docker-entrypoint.sh ngin ... Up 0.0.0.0:80->80/tcp
# 稍等一分钟左右 knowstreaming-init 会退出表示es初始化完成可以访问页面
Name Command State Ports
-------------------------------------------------------------------------------------------
knowstreaming-init /bin/bash /es_template_cre ... Exit 0
knowstreaming-mysql /entrypoint.sh mysqld Up (healthy) 3306/tcp, 33060/tcp
```
**访问**
```http request
http://127.0.0.1:80/
```
**docker-compose.yml**
```yml
version: "2"
services:
# *不要调整knowstreaming-manager服务名称ui中会用到
knowstreaming-manager:
image: knowstreaming/knowstreaming-manager:latest
container_name: knowstreaming-manager
privileged: true
restart: always
depends_on:
- elasticsearch-single
- knowstreaming-mysql
expose:
- 80
command:
- /bin/sh
- /ks-start.sh
environment:
TZ: Asia/Shanghai
# mysql服务地址
SERVER_MYSQL_ADDRESS: knowstreaming-mysql:3306
# mysql数据库名
SERVER_MYSQL_DB: know_streaming
# mysql用户名
SERVER_MYSQL_USER: root
# mysql用户密码
SERVER_MYSQL_PASSWORD: admin2022_
# es服务地址
SERVER_ES_ADDRESS: elasticsearch-single:9200
# 服务JVM参数
JAVA_OPTS: -Xmx1g -Xms1g
# 对于kafka中ADVERTISED_LISTENERS填写的hostname可以通过该方式完成
# extra_hosts:
# - "hostname:x.x.x.x"
# 服务日志路径
# volumes:
# - /ks/manage/log:/logs
knowstreaming-ui:
image: knowstreaming/knowstreaming-ui:latest
container_name: knowstreaming-ui
restart: always
ports:
- '80:80'
environment:
TZ: Asia/Shanghai
depends_on:
- knowstreaming-manager
# extra_hosts:
# - "hostname:x.x.x.x"
elasticsearch-single:
image: docker.io/library/elasticsearch:7.6.2
container_name: elasticsearch-single
restart: always
expose:
- 9200
- 9300
# ports:
# - '9200:9200'
# - '9300:9300'
environment:
TZ: Asia/Shanghai
# es的JVM参数
ES_JAVA_OPTS: -Xms512m -Xmx512m
# 单节点配置,多节点集群参考 https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docker.html#docker-compose-file
discovery.type: single-node
# 数据持久化路径
# volumes:
# - /ks/es/data:/usr/share/elasticsearch/data
# es初始化服务与manager使用同一镜像
# 首次启动es需初始化模版和索引,后续会自动创建
knowstreaming-init:
image: knowstreaming/knowstreaming-manager:latest
container_name: knowstreaming-init
depends_on:
- elasticsearch-single
command:
- /bin/bash
- /es_template_create.sh
environment:
TZ: Asia/Shanghai
# es服务地址
SERVER_ES_ADDRESS: elasticsearch-single:9200
knowstreaming-mysql:
image: knowstreaming/knowstreaming-mysql:latest
container_name: knowstreaming-mysql
restart: always
environment:
TZ: Asia/Shanghai
# root 用户密码
MYSQL_ROOT_PASSWORD: admin2022_
# 初始化时创建的数据库名称
MYSQL_DATABASE: know_streaming
# 通配所有host,可以访问远程
MYSQL_ROOT_HOST: '%'
expose:
- 3306
# ports:
# - '3306:3306'
# 数据持久化路径
# volumes:
# - /ks/mysql/data:/data/mysql
```
&nbsp;
### 2.1.4、手动部署 ### 2.1.4、手动部署
**部署流程** **部署流程**

View File

@@ -1,12 +1,28 @@
## 6.2、版本升级手册 ## 6.2、版本升级手册
注意:如果想升级至具体版本,需要将你当前版本至你期望使用版本的变更统统执行一遍,然后才能正常使用。 注意:
- 如果想升级至具体版本,需要将你当前版本至你期望使用版本的变更统统执行一遍,然后才能正常使用。
- 如果中间某个版本没有升级信息,则表示该版本直接替换安装包即可从前一个版本升级至当前版本。
### 6.2.0、升级至 `master` 版本 ### 6.2.0、升级至 `master` 版本
暂无 暂无
### 6.2.1、升级至 `v3.0.0-beta.2`版本
### 6.2.1、升级至 `v3.0.0` 版本
**SQL 变更**
```sql
ALTER TABLE `ks_km_physical_cluster`
ADD COLUMN `zk_properties` TEXT NULL COMMENT 'ZK配置' AFTER `jmx_properties`;
```
---
### 6.2.2、升级至 `v3.0.0-beta.2`版本
**配置变更** **配置变更**
@@ -77,7 +93,7 @@ ALTER TABLE `logi_security_oplog`
--- ---
### 6.2.2、升级至 `v3.0.0-beta.1`版本 ### 6.2.3、升级至 `v3.0.0-beta.1`版本
**SQL 变更** **SQL 变更**
@@ -96,7 +112,7 @@ ALTER COLUMN `operation_methods` set default '';
--- ---
### 6.2.3、`2.x`版本 升级至 `v3.0.0-beta.0`版本 ### 6.2.4、`2.x`版本 升级至 `v3.0.0-beta.0`版本
**升级步骤:** **升级步骤:**

View File

@@ -166,3 +166,19 @@ Node 版本: v12.22.12
需要到具体的应用中执行 `npm run start`,例如 `cd packages/layout-clusters-fe` 后,执行 `npm run start` 需要到具体的应用中执行 `npm run start`,例如 `cd packages/layout-clusters-fe` 后,执行 `npm run start`
应用启动后需要到基座应用中查看(需要启动基座应用,即 layout-clusters-fe 应用启动后需要到基座应用中查看(需要启动基座应用,即 layout-clusters-fe
## 8.12、权限识别失败问题
1、使用admin账号登陆KnowStreaming时点击系统管理-用户管理-角色管理-新增角色,查看页面是否正常。
<img src="http://img-ys011.didistatic.com/static/dc2img/do1_gwGfjN9N92UxzHU8dfzr" width = "400" >
2、查看'/logi-security/api/v1/permission/tree'接口返回值,出现如下图所示乱码现象。
![接口返回值](http://img-ys011.didistatic.com/static/dc2img/do1_jTxBkwNGU9vZuYQQbdNw)
3、查看logi_security_permission表看看是否出现了中文乱码现象。
根据以上几点,我们可以确定是由于数据库乱码造成的权限识别失败问题。
+ 原因:由于数据库编码和我们提供的脚本不一致,数据库里的数据发生了乱码,因此出现权限识别失败问题。
+ 解决方案清空数据库数据将数据库字符集调整为utf8最后重新执行[dml-logi.sql](https://github.com/didi/KnowStreaming/blob/master/km-dist/init/sql/dml-logi.sql)脚本导入数据即可。

View File

@@ -272,15 +272,11 @@ public class GroupManagerImpl implements GroupManager {
// 获取Group指标信息 // 获取Group指标信息
Result<List<GroupMetrics>> groupMetricsResult = groupMetricService.listPartitionLatestMetricsFromES( Result<List<GroupMetrics>> groupMetricsResult = groupMetricService.collectGroupMetricsFromKafka(clusterPhyId, groupName, latestMetricNames == null ? Arrays.asList() : latestMetricNames);
clusterPhyId,
groupName,
topicName,
latestMetricNames == null? Arrays.asList(): latestMetricNames
);
// 转换Group指标 // 转换Group指标
List<GroupMetrics> esGroupMetricsList = groupMetricsResult.hasData()? groupMetricsResult.getData(): new ArrayList<>(); List<GroupMetrics> esGroupMetricsList = groupMetricsResult.hasData() ? groupMetricsResult.getData().stream().filter(elem -> topicName.equals(elem.getTopic())).collect(Collectors.toList()) : new ArrayList<>();
Map<Integer, GroupMetrics> esMetricsMap = new HashMap<>(); Map<Integer, GroupMetrics> esMetricsMap = new HashMap<>();
for (GroupMetrics groupMetrics: esGroupMetricsList) { for (GroupMetrics groupMetrics: esGroupMetricsList) {
esMetricsMap.put(groupMetrics.getPartitionId(), groupMetrics); esMetricsMap.put(groupMetrics.getPartitionId(), groupMetrics);

View File

@@ -7,12 +7,14 @@ import com.didiglobal.logi.log.LogFactory;
import com.didiglobal.logi.security.common.dto.config.ConfigDTO; import com.didiglobal.logi.security.common.dto.config.ConfigDTO;
import com.didiglobal.logi.security.service.ConfigService; import com.didiglobal.logi.security.service.ConfigService;
import com.xiaojukeji.know.streaming.km.biz.version.VersionControlManager; import com.xiaojukeji.know.streaming.km.biz.version.VersionControlManager;
import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDetailDTO;
import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.UserMetricConfigDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.UserMetricConfigDTO;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.metric.UserMetricConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.metric.UserMetricConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result; 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.entity.version.VersionControlItem;
import com.xiaojukeji.know.streaming.km.common.bean.vo.config.metric.UserMetricConfigVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.config.metric.UserMetricConfigVO;
import com.xiaojukeji.know.streaming.km.common.bean.vo.version.VersionItemVO; import com.xiaojukeji.know.streaming.km.common.bean.vo.version.VersionItemVO;
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.common.enums.version.VersionEnum;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import com.xiaojukeji.know.streaming.km.common.utils.VersionUtil; import com.xiaojukeji.know.streaming.km.common.utils.VersionUtil;
@@ -47,29 +49,29 @@ public class VersionControlManagerImpl implements VersionControlManager {
@PostConstruct @PostConstruct
public void init(){ public void init(){
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_SCORE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_TOTAL_PRODUCE_REQUESTS, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_FETCH_REQ, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_FETCH_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_PRODUCE_REQ, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_PRODUCE_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_MESSAGE_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_UNDER_REPLICA_PARTITIONS, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_UNDER_REPLICA_PARTITIONS, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_TOTAL_PRODUCE_REQUESTS, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_IN, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_OUT, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_OUT, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_REJECTED, true)); defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_REJECTED, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_MESSAGE_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_SCORE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_REQ_QUEUE_SIZE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_RES_QUEUE_SIZE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_PRODUCE_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_LOG_SIZE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_CONNECTIONS, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_MESSAGES_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_IN, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_OUT, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_OUT, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_GROUP_REBALANCES, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_CONNECTIONS, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_JOB_RUNNING, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_MESSAGES_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_PARTITIONS_NO_LEADER, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_PARTITIONS_NO_LEADER, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_PARTITION_URP, true)); defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_PARTITION_URP, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_LOG_SIZE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_PRODUCE_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_REQ_QUEUE_SIZE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_TOTAL_RES_QUEUE_SIZE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_GROUP_REBALANCES, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_JOB_RUNNING, true));
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_OFFSET_CONSUMED, true)); 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_LAG, true));
@@ -77,18 +79,18 @@ public class VersionControlManagerImpl implements VersionControlManager {
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_HEALTH_SCORE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_SCORE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_TOTAL_REQ_QUEUE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_CONNECTION_COUNT, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_TOTAL_RES_QUEUE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_MESSAGE_IN, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_MESSAGE_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_TOTAL_PRODUCE_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_NETWORK_RPO_AVG_IDLE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_NETWORK_RPO_AVG_IDLE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_REQ_AVG_IDLE, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_REQ_AVG_IDLE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_CONNECTION_COUNT, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_TOTAL_PRODUCE_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_BYTES_IN, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_TOTAL_REQ_QUEUE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_BYTES_OUT, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_TOTAL_RES_QUEUE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_PARTITIONS_SKEW, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_LEADERS_SKEW, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_LEADERS_SKEW, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_UNDER_REPLICATE_PARTITION, true)); defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_UNDER_REPLICATE_PARTITION, true));
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));
} }
@Autowired @Autowired
@@ -159,6 +161,9 @@ public class VersionControlManagerImpl implements VersionControlManager {
UserMetricConfig umc = userMetricConfigMap.get(itemType + "@" + metric); UserMetricConfig umc = userMetricConfigMap.get(itemType + "@" + metric);
userMetricConfigVO.setSet(null != umc && umc.isSet()); userMetricConfigVO.setSet(null != umc && umc.isSet());
if (umc != null) {
userMetricConfigVO.setRank(umc.getRank());
}
userMetricConfigVO.setName(itemVO.getName()); userMetricConfigVO.setName(itemVO.getName());
userMetricConfigVO.setType(itemVO.getType()); userMetricConfigVO.setType(itemVO.getType());
userMetricConfigVO.setDesc(itemVO.getDesc()); userMetricConfigVO.setDesc(itemVO.getDesc());
@@ -178,13 +183,29 @@ public class VersionControlManagerImpl implements VersionControlManager {
@Override @Override
public Result<Void> updateUserMetricItem(Long clusterId, Integer type, UserMetricConfigDTO dto, String operator) { public Result<Void> updateUserMetricItem(Long clusterId, Integer type, UserMetricConfigDTO dto, String operator) {
Map<String, Boolean> metricsSetMap = dto.getMetricsSet(); Map<String, Boolean> metricsSetMap = dto.getMetricsSet();
if(null == metricsSetMap || metricsSetMap.isEmpty()){
//转换metricDetailDTOList
List<MetricDetailDTO> metricDetailDTOList = dto.getMetricDetailDTOList();
Map<String, MetricDetailDTO> metricDetailMap = new HashMap<>();
if (metricDetailDTOList != null && !metricDetailDTOList.isEmpty()) {
metricDetailMap = metricDetailDTOList.stream().collect(Collectors.toMap(MetricDetailDTO::getMetric, Function.identity()));
}
//转换metricsSetMap
if (metricsSetMap != null && !metricsSetMap.isEmpty()) {
for (Map.Entry<String, Boolean> metricAndShowEntry : metricsSetMap.entrySet()) {
if (metricDetailMap.containsKey(metricAndShowEntry.getKey())) continue;
metricDetailMap.put(metricAndShowEntry.getKey(), new MetricDetailDTO(metricAndShowEntry.getKey(), metricAndShowEntry.getValue(), null));
}
}
if (metricDetailMap.isEmpty()) {
return Result.buildSuc(); return Result.buildSuc();
} }
Set<UserMetricConfig> userMetricConfigs = getUserMetricConfig(operator); Set<UserMetricConfig> userMetricConfigs = getUserMetricConfig(operator);
for(Map.Entry<String, Boolean> metricAndShowEntry : metricsSetMap.entrySet()){ for (MetricDetailDTO metricDetailDTO : metricDetailMap.values()) {
UserMetricConfig userMetricConfig = new UserMetricConfig(type, metricAndShowEntry.getKey(), metricAndShowEntry.getValue()); UserMetricConfig userMetricConfig = new UserMetricConfig(type, metricDetailDTO.getMetric(), metricDetailDTO.getSet(), metricDetailDTO.getRank());
userMetricConfigs.remove(userMetricConfig); userMetricConfigs.remove(userMetricConfig);
userMetricConfigs.add(userMetricConfig); userMetricConfigs.add(userMetricConfig);
} }
@@ -228,7 +249,7 @@ public class VersionControlManagerImpl implements VersionControlManager {
return defaultMetrics; return defaultMetrics;
} }
return JSON.parseObject(value, new TypeReference<Set<UserMetricConfig>>(){}); return JSON.parseObject(value, new TypeReference<Set<UserMetricConfig>>() {});
} }
public static void main(String[] args){ public static void main(String[] args){

View File

@@ -1,121 +0,0 @@
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.common.bean.event.metric.*;
import com.xiaojukeji.know.streaming.km.common.bean.po.BaseESPO;
import com.xiaojukeji.know.streaming.km.common.bean.po.metrice.*;
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.NamedThreadFactory;
import com.xiaojukeji.know.streaming.km.persistence.es.dao.BaseMetricESDAO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.*;
@Component
public class MetricESSender implements ApplicationListener<BaseMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
private static final int THRESHOLD = 100;
private ThreadPoolExecutor esExecutor = new ThreadPoolExecutor(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()));
@PostConstruct
public void init(){
LOGGER.info("class=MetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(BaseMetricEvent event) {
if(event instanceof BrokerMetricEvent) {
BrokerMetricEvent brokerMetricEvent = (BrokerMetricEvent)event;
send2es(BROKER_INDEX,
ConvertUtil.list2List(brokerMetricEvent.getBrokerMetrics(), BrokerMetricPO.class)
);
} else if(event instanceof ClusterMetricEvent) {
ClusterMetricEvent clusterMetricEvent = (ClusterMetricEvent)event;
send2es(CLUSTER_INDEX,
ConvertUtil.list2List(clusterMetricEvent.getClusterMetrics(), ClusterMetricPO.class)
);
} else if(event instanceof TopicMetricEvent) {
TopicMetricEvent topicMetricEvent = (TopicMetricEvent)event;
send2es(TOPIC_INDEX,
ConvertUtil.list2List(topicMetricEvent.getTopicMetrics(), TopicMetricPO.class)
);
} else if(event instanceof PartitionMetricEvent) {
PartitionMetricEvent partitionMetricEvent = (PartitionMetricEvent)event;
send2es(PARTITION_INDEX,
ConvertUtil.list2List(partitionMetricEvent.getPartitionMetrics(), PartitionMetricPO.class)
);
} else if(event instanceof GroupMetricEvent) {
GroupMetricEvent groupMetricEvent = (GroupMetricEvent)event;
send2es(GROUP_INDEX,
ConvertUtil.list2List(groupMetricEvent.getGroupMetrics(), GroupMetricPO.class)
);
} else if(event instanceof ReplicaMetricEvent) {
ReplicaMetricEvent replicaMetricEvent = (ReplicaMetricEvent)event;
send2es(REPLICATION_INDEX,
ConvertUtil.list2List(replicaMetricEvent.getReplicationMetrics(), ReplicationMetricPO.class)
);
}
}
/**
* 根据不同监控维度来发送
*/
private boolean send2es(String index, List<? extends BaseESPO> statsList){
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);
return false;
}
int size = statsList.size();
int num = (size) % THRESHOLD == 0 ? (size / THRESHOLD) : (size / THRESHOLD + 1);
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))
);
}
return true;
}
}

View File

@@ -0,0 +1,72 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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.NamedThreadFactory;
import com.xiaojukeji.know.streaming.km.persistence.es.dao.BaseMetricESDAO;
import org.apache.commons.collections.CollectionUtils;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public abstract class AbstractMetricESSender {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
private static final int THRESHOLD = 100;
private static final ThreadPoolExecutor esExecutor = new ThreadPoolExecutor(
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())
);
/**
* 根据不同监控维度来发送
*/
protected boolean send2es(String index, List<? extends BaseESPO> statsList){
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);
return false;
}
int size = statsList.size();
int num = (size) % THRESHOLD == 0 ? (size / THRESHOLD) : (size / THRESHOLD + 1);
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))
);
}
return true;
}
}

View File

@@ -0,0 +1,28 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.BROKER_INDEX;
@Component
public class BrokerMetricESSender extends AbstractMetricESSender implements ApplicationListener<BrokerMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
@PostConstruct
public void init(){
LOGGER.info("class=BrokerMetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(BrokerMetricEvent event) {
send2es(BROKER_INDEX, ConvertUtil.list2List(event.getBrokerMetrics(), BrokerMetricPO.class));
}
}

View File

@@ -0,0 +1,29 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.CLUSTER_INDEX;
@Component
public class ClusterMetricESSender extends AbstractMetricESSender implements ApplicationListener<ClusterMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
@PostConstruct
public void init(){
LOGGER.info("class=ClusterMetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(ClusterMetricEvent event) {
send2es(CLUSTER_INDEX, ConvertUtil.list2List(event.getClusterMetrics(), ClusterMetricPO.class));
}
}

View File

@@ -0,0 +1,29 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.GROUP_INDEX;
@Component
public class GroupMetricESSender extends AbstractMetricESSender implements ApplicationListener<GroupMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
@PostConstruct
public void init(){
LOGGER.info("class=GroupMetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(GroupMetricEvent event) {
send2es(GROUP_INDEX, ConvertUtil.list2List(event.getGroupMetrics(), GroupMetricPO.class));
}
}

View File

@@ -0,0 +1,28 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.PARTITION_INDEX;
@Component
public class PartitionMetricESSender extends AbstractMetricESSender implements ApplicationListener<PartitionMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
@PostConstruct
public void init(){
LOGGER.info("class=PartitionMetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(PartitionMetricEvent event) {
send2es(PARTITION_INDEX, ConvertUtil.list2List(event.getPartitionMetrics(), PartitionMetricPO.class));
}
}

View File

@@ -0,0 +1,28 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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.common.constant.ESIndexConstant.REPLICATION_INDEX;
@Component
public class ReplicaMetricESSender extends AbstractMetricESSender implements ApplicationListener<ReplicaMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
@PostConstruct
public void init(){
LOGGER.info("class=GroupMetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(ReplicaMetricEvent event) {
send2es(REPLICATION_INDEX, ConvertUtil.list2List(event.getReplicationMetrics(), ReplicationMetricPO.class));
}
}

View File

@@ -0,0 +1,29 @@
package com.xiaojukeji.know.streaming.km.collector.sink;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
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;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static com.xiaojukeji.know.streaming.km.common.constant.ESIndexConstant.TOPIC_INDEX;
@Component
public class TopicMetricESSender extends AbstractMetricESSender implements ApplicationListener<TopicMetricEvent> {
protected static final ILog LOGGER = LogFactory.getLog("METRIC_LOGGER");
@PostConstruct
public void init(){
LOGGER.info("class=TopicMetricESSender||method=init||msg=init finished");
}
@Override
public void onApplicationEvent(TopicMetricEvent event) {
send2es(TOPIC_INDEX, ConvertUtil.list2List(event.getTopicMetrics(), TopicMetricPO.class));
}
}

View File

@@ -3,6 +3,7 @@ package com.xiaojukeji.know.streaming.km.common.bean.dto.cluster;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig; import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@@ -34,4 +35,8 @@ public class ClusterPhyBaseDTO extends BaseDTO {
@NotNull(message = "jmxProperties不允许为空") @NotNull(message = "jmxProperties不允许为空")
@ApiModelProperty(value="Jmx配置") @ApiModelProperty(value="Jmx配置")
protected JmxConfig jmxProperties; protected JmxConfig jmxProperties;
// TODO 前端页面增加时,需要加一个不为空的限制
@ApiModelProperty(value="ZK配置")
protected ZKConfig zkProperties;
} }

View File

@@ -0,0 +1,32 @@
package com.xiaojukeji.know.streaming.km.common.bean.dto.metrices;
import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* @author didi
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "指标详细属性信息")
public class MetricDetailDTO extends BaseDTO {
@ApiModelProperty("指标名称")
private String metric;
@ApiModelProperty("指标是否显示")
private Boolean set;
@NotNull(message = "MetricDetailDTO的rank字段应不为空")
@ApiModelProperty("指标优先级")
private Integer rank;
}

View File

@@ -7,6 +7,8 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.Valid;
import java.util.List;
import java.util.Map; import java.util.Map;
@@ -17,4 +19,8 @@ import java.util.Map;
public class UserMetricConfigDTO extends BaseDTO { public class UserMetricConfigDTO extends BaseDTO {
@ApiModelProperty("指标展示设置项key指标名value是否展现(true展现/false不展现)") @ApiModelProperty("指标展示设置项key指标名value是否展现(true展现/false不展现)")
private Map<String, Boolean> metricsSet; private Map<String, Boolean> metricsSet;
@Valid
@ApiModelProperty("指标自定义属性列表")
private List<MetricDetailDTO> metricDetailDTOList;
} }

View File

@@ -5,7 +5,6 @@ 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.common.IpPortData;
import com.xiaojukeji.know.streaming.km.common.bean.po.broker.BrokerPO; import com.xiaojukeji.know.streaming.km.common.bean.po.broker.BrokerPO;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil; import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import com.xiaojukeji.know.streaming.km.common.zookeeper.znode.brokers.BrokerMetadata;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -79,20 +78,6 @@ public class Broker implements Serializable {
return metadata; return metadata;
} }
public static Broker buildFrom(Long clusterPhyId, Integer brokerId, BrokerMetadata brokerMetadata) {
Broker metadata = new Broker();
metadata.setClusterPhyId(clusterPhyId);
metadata.setBrokerId(brokerId);
metadata.setHost(brokerMetadata.getHost());
metadata.setPort(brokerMetadata.getPort());
metadata.setJmxPort(brokerMetadata.getJmxPort());
metadata.setStartTimestamp(brokerMetadata.getTimestamp());
metadata.setRack(brokerMetadata.getRack());
metadata.setStatus(1);
metadata.setEndpointMap(brokerMetadata.getEndpointMap());
return metadata;
}
public static Broker buildFrom(BrokerPO brokerPO) { public static Broker buildFrom(BrokerPO brokerPO) {
Broker broker = ConvertUtil.obj2Obj(brokerPO, Broker.class); Broker broker = ConvertUtil.obj2Obj(brokerPO, Broker.class);
String endpointMapStr = brokerPO.getEndpointMap(); String endpointMapStr = brokerPO.getEndpointMap();

View File

@@ -53,9 +53,16 @@ public class ClusterPhy implements Comparable<ClusterPhy>, EntifyIdInterface {
/** /**
* jmx配置 * jmx配置
* @see com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig
*/ */
private String jmxProperties; private String jmxProperties;
/**
* zk配置
* @see com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig
*/
private String zkProperties;
/** /**
* 开启ACL * 开启ACL
* @see com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterAuthTypeEnum * @see com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterAuthTypeEnum

View File

@@ -0,0 +1,31 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Properties;
/**
* @author zengqiao
* @date 22/02/24
*/
@Data
@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;
@ApiModelProperty(value="ZK的Session超时时间", example = "15000")
private Long sessionTimeoutUnitMs = 15000L;
@ApiModelProperty(value="ZK的Request超时时间", example = "5000")
private Long requestTimeoutUnitMs = 5000L;
@ApiModelProperty(value="ZK的Request超时时间")
private Properties otherProps = new Properties();
}

View File

@@ -1,12 +1,12 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.config.metric; 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.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor
public class UserMetricConfig { public class UserMetricConfig {
private int type; private int type;
@@ -15,6 +15,22 @@ public class UserMetricConfig {
private boolean set; private boolean set;
private Integer rank;
public UserMetricConfig(int type, String metric, boolean set, Integer rank) {
this.type = type;
this.metric = metric;
this.set = set;
this.rank = rank;
}
public UserMetricConfig(int type, String metric, boolean set) {
this.type = type;
this.metric = metric;
this.set = set;
this.rank = null;
}
@Override @Override
public int hashCode(){ public int hashCode(){
return metric.hashCode() << 1 + type; return metric.hashCode() << 1 + type;

View File

@@ -41,6 +41,11 @@ public class ClusterPhyPO extends BasePO {
*/ */
private String jmxProperties; private String jmxProperties;
/**
* zk配置
*/
private String zkProperties;
/** /**
* 认证类型 * 认证类型
* @see com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterAuthTypeEnum * @see com.xiaojukeji.know.streaming.km.common.enums.cluster.ClusterAuthTypeEnum

View File

@@ -31,9 +31,15 @@ public class ClusterPhyBaseVO extends BaseTimeVO {
@ApiModelProperty(value="Jmx配置", example = "{}") @ApiModelProperty(value="Jmx配置", example = "{}")
protected String jmxProperties; protected String jmxProperties;
@ApiModelProperty(value="ZK配置", example = "{}")
protected String zkProperties;
@ApiModelProperty(value="描述", example = "测试") @ApiModelProperty(value="描述", example = "测试")
protected String description; protected String description;
@ApiModelProperty(value="集群的kafka版本", example = "2.5.1") @ApiModelProperty(value="集群的kafka版本", example = "2.5.1")
protected String kafkaVersion; protected String kafkaVersion;
@ApiModelProperty(value="集群的运行模式", example = "2raft模式其他是ZK模式")
private Integer runState;
} }

View File

@@ -14,4 +14,7 @@ import lombok.NoArgsConstructor;
public class UserMetricConfigVO extends VersionItemVO { public class UserMetricConfigVO extends VersionItemVO {
@ApiModelProperty(value = "该指标用户是否设置展现", example = "true") @ApiModelProperty(value = "该指标用户是否设置展现", example = "true")
private Boolean set; private Boolean set;
@ApiModelProperty(value = "该指标展示优先级", example = "1")
private Integer rank;
} }

View File

@@ -42,6 +42,7 @@ public class Constant {
*/ */
public static final Integer DEFAULT_CLUSTER_HEALTH_SCORE = 90; public static final Integer DEFAULT_CLUSTER_HEALTH_SCORE = 90;
public static final String DEFAULT_USER_NAME = "know-streaming-app"; public static final String DEFAULT_USER_NAME = "know-streaming-app";
public static final int INVALID_CODE = -1; public static final int INVALID_CODE = -1;

View File

@@ -52,6 +52,10 @@ public class MsgConstant {
/**************************************************** Partition ****************************************************/ /**************************************************** Partition ****************************************************/
public static String getPartitionNoLeader(Long clusterPhyId, String topicName) {
return String.format("集群ID:[%d] Topic名称:[%s] 所有分区NoLeader", clusterPhyId, topicName);
}
public static String getPartitionNotExist(Long clusterPhyId, String topicName) { public static String getPartitionNotExist(Long clusterPhyId, String topicName) {
return String.format("集群ID:[%d] Topic名称:[%s] 存在非法的分区ID", clusterPhyId, topicName); return String.format("集群ID:[%d] Topic名称:[%s] 存在非法的分区ID", clusterPhyId, topicName);
} }

View File

@@ -19,6 +19,11 @@ public class ClusterConverter {
ClusterPhyPO clusterPhyPO = ConvertUtil.obj2Obj(dto, ClusterPhyPO.class); ClusterPhyPO clusterPhyPO = ConvertUtil.obj2Obj(dto, ClusterPhyPO.class);
clusterPhyPO.setClientProperties(ConvertUtil.obj2Json(dto.getClientProperties())); clusterPhyPO.setClientProperties(ConvertUtil.obj2Json(dto.getClientProperties()));
clusterPhyPO.setJmxProperties(ConvertUtil.obj2Json(dto.getJmxProperties())); clusterPhyPO.setJmxProperties(ConvertUtil.obj2Json(dto.getJmxProperties()));
if (ValidateUtils.isNull(dto.getZkProperties())) {
clusterPhyPO.setZkProperties("");
} else {
clusterPhyPO.setZkProperties(ConvertUtil.obj2Json(dto.getZkProperties()));
}
clusterPhyPO.setRunState( clusterPhyPO.setRunState(
ValidateUtils.isBlank(dto.getZookeeper())? ValidateUtils.isBlank(dto.getZookeeper())?
ClusterRunStateEnum.RUN_RAFT.getRunState() : ClusterRunStateEnum.RUN_RAFT.getRunState() :
@@ -32,6 +37,11 @@ public class ClusterConverter {
ClusterPhyPO clusterPhyPO = ConvertUtil.obj2Obj(dto, ClusterPhyPO.class); ClusterPhyPO clusterPhyPO = ConvertUtil.obj2Obj(dto, ClusterPhyPO.class);
clusterPhyPO.setClientProperties(ConvertUtil.obj2Json(dto.getClientProperties())); clusterPhyPO.setClientProperties(ConvertUtil.obj2Json(dto.getClientProperties()));
clusterPhyPO.setJmxProperties(ConvertUtil.obj2Json(dto.getJmxProperties())); clusterPhyPO.setJmxProperties(ConvertUtil.obj2Json(dto.getJmxProperties()));
if (ValidateUtils.isNull(dto.getZkProperties())) {
clusterPhyPO.setZkProperties("");
} else {
clusterPhyPO.setZkProperties(ConvertUtil.obj2Json(dto.getZkProperties()));
}
clusterPhyPO.setRunState( clusterPhyPO.setRunState(
ValidateUtils.isBlank(dto.getZookeeper())? ValidateUtils.isBlank(dto.getZookeeper())?
ClusterRunStateEnum.RUN_RAFT.getRunState() : ClusterRunStateEnum.RUN_RAFT.getRunState() :

View File

@@ -0,0 +1,31 @@
package com.xiaojukeji.know.streaming.km.common.enums.health;
import lombok.Getter;
/**
* 健康状态
*/
@Getter
public enum HealthStateEnum {
UNKNOWN(-1, "未知"),
GOOD(0, ""),
MEDIUM(1, ""),
POOR(2, ""),
DEAD(3, "宕机"),
;
private final int dimension;
private final String message;
HealthStateEnum(int dimension, String message) {
this.dimension = dimension;
this.message = message;
}
}

View File

@@ -90,6 +90,8 @@ public class JmxConnectorWrap {
} }
try { try {
jmxConnector.close(); jmxConnector.close();
jmxConnector = null;
} catch (IOException e) { } catch (IOException e) {
LOGGER.warn("close JmxConnector exception, physicalClusterId:{} brokerId:{} host:{} port:{}.", physicalClusterId, brokerId, host, port, e); LOGGER.warn("close JmxConnector exception, physicalClusterId:{} brokerId:{} host:{} port:{}.", physicalClusterId, brokerId, host, port, e);
} }
@@ -105,6 +107,11 @@ public class JmxConnectorWrap {
acquire(); acquire();
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
return mBeanServerConnection.getAttribute(name, attribute); return mBeanServerConnection.getAttribute(name, attribute);
} catch (IOException ioe) {
// 如果是因为连接断开,则进行重新连接,并抛出异常
reInitDueIOException();
throw ioe;
} finally { } finally {
atomicInteger.incrementAndGet(); atomicInteger.incrementAndGet();
} }
@@ -120,6 +127,11 @@ public class JmxConnectorWrap {
acquire(); acquire();
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
return mBeanServerConnection.getAttributes(name, attributes); return mBeanServerConnection.getAttributes(name, attributes);
} catch (IOException ioe) {
// 如果是因为连接断开,则进行重新连接,并抛出异常
reInitDueIOException();
throw ioe;
} finally { } finally {
atomicInteger.incrementAndGet(); atomicInteger.incrementAndGet();
} }
@@ -131,6 +143,11 @@ public class JmxConnectorWrap {
acquire(); acquire();
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
return mBeanServerConnection.queryNames(name, query); return mBeanServerConnection.queryNames(name, query);
} catch (IOException ioe) {
// 如果是因为连接断开,则进行重新连接,并抛出异常
reInitDueIOException();
throw ioe;
} finally { } finally {
atomicInteger.incrementAndGet(); atomicInteger.incrementAndGet();
} }
@@ -186,4 +203,26 @@ public class JmxConnectorWrap {
} }
} }
} }
private synchronized void reInitDueIOException() {
try {
if (jmxConnector == null) {
return;
}
// 检查是否正常
jmxConnector.getConnectionId();
// 如果正常则直接返回
return;
} catch (Exception e) {
// ignore
}
// 关闭旧的
this.close();
// 重新创建
this.checkJmxConnectionAndInitIfNeed();
}
} }

View File

@@ -5100,9 +5100,9 @@
} }
}, },
"is-callable": { "is-callable": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.5.tgz", "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.6.tgz",
"integrity": "sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw==", "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==",
"dev": true "dev": true
}, },
"is-ci": { "is-ci": {

View File

@@ -1,205 +0,0 @@
/* eslint-disable */
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const theme = require('./theme');
var cwd = process.cwd();
const path = require('path');
const isProd = process.env.NODE_ENV === 'production';
const babelOptions = {
cacheDirectory: true,
babelrc: false,
presets: [require.resolve('@babel/preset-env'), require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')],
plugins: [
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-methods'), { loose: true }],
require.resolve('@babel/plugin-proposal-export-default-from'),
require.resolve('@babel/plugin-proposal-export-namespace-from'),
require.resolve('@babel/plugin-proposal-object-rest-spread'),
require.resolve('@babel/plugin-transform-runtime'),
require.resolve('@babel/plugin-proposal-optional-chaining'), //
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), // 解决 ?? 无法转义问题
require.resolve('@babel/plugin-proposal-numeric-separator'), // 转义 1_000_000
!isProd && require.resolve('react-refresh/babel'),
]
.filter(Boolean)
.concat([
[
'babel-plugin-import',
{
libraryName: 'antd',
style: true,
},
],
'@babel/plugin-transform-object-assign',
]),
};
module.exports = () => {
const manifestName = `manifest.json`;
const cssFileName = isProd ? '[name]-[chunkhash].css' : '[name].css';
const plugins = [
new ProgressBarPlugin(),
new CaseSensitivePathsPlugin(),
new MiniCssExtractPlugin({
filename: cssFileName,
}),
new StatsPlugin(manifestName, {
chunkModules: false,
source: true,
chunks: false,
modules: false,
assets: true,
children: false,
exclude: [/node_modules/],
}),
new HappyPack({
id: 'babel',
loaders: [
'cache-loader',
{
loader: 'babel-loader',
options: babelOptions,
},
],
threadPool: happyThreadPool,
}),
!isProd &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
// new BundleAnalyzerPlugin({
// analyzerPort: 8889
// }),
].filter(Boolean);
if (isProd) {
plugins.push(new CleanWebpackPlugin());
}
return {
externals: isProd
? [
/^react$/,
/^react\/lib.*/,
/^react-dom$/,
/.*react-dom.*/,
/^single-spa$/,
/^single-spa-react$/,
/^moment$/,
/^antd$/,
/^lodash$/,
/^react-router$/,
/^react-router-dom$/,
]
: [],
resolve: {
symlinks: false,
extensions: ['.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json'],
alias: {
// '@pkgs': path.resolve(cwd, 'src/packages'),
'@pkgs': path.resolve(cwd, './node_modules/@didi/d1-packages'),
'@cpts': path.resolve(cwd, 'src/components'),
'@interface': path.resolve(cwd, 'src/interface'),
'@apis': path.resolve(cwd, 'src/api'),
react: path.resolve('./node_modules/react'),
actions: path.resolve(cwd, 'src/actions'),
lib: path.resolve(cwd, 'src/lib'),
constants: path.resolve(cwd, 'src/constants'),
components: path.resolve(cwd, 'src/components'),
container: path.resolve(cwd, 'src/container'),
api: path.resolve(cwd, 'src/api'),
assets: path.resolve(cwd, 'src/assets'),
mobxStore: path.resolve(cwd, 'src/mobxStore'),
},
},
plugins,
module: {
rules: [
{
parser: { system: false },
},
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules\/(?!react-intl|@didi\/dcloud-design)/,
use: [
{
loader: 'happypack/loader?id=babel',
},
],
},
{
test: /\.(png|svg|jpeg|jpg|gif|ttf|woff|woff2|eot|pdf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/image/',
esModule: false,
},
},
],
},
{
test: /\.(css|less)$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: theme,
},
},
],
},
],
},
optimization: Object.assign(
{
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vendor',
priority: 10,
enforce: true,
minChunks: 1,
maxSize: 3500000,
},
},
},
},
isProd
? {
minimizer: [
new TerserJSPlugin({
cache: true,
sourceMap: true,
}),
new OptimizeCSSAssetsPlugin({}),
],
}
: {}
),
devtool: isProd ? 'cheap-module-source-map' : 'source-map',
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
};
};

View File

@@ -0,0 +1,132 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const theme = require('./theme');
const pkgJson = require('../package');
const devMode = process.env.NODE_ENV === 'development';
const babelOptions = {
cacheDirectory: true,
babelrc: false,
presets: [require.resolve('@babel/preset-env'), require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')],
plugins: [
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-methods'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-property-in-object'), { loose: true }],
require.resolve('@babel/plugin-proposal-export-default-from'),
require.resolve('@babel/plugin-proposal-export-namespace-from'),
require.resolve('@babel/plugin-proposal-object-rest-spread'),
require.resolve('@babel/plugin-transform-runtime'),
require.resolve('@babel/plugin-proposal-optional-chaining'), //
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), // 解决 ?? 无法转义问题
require.resolve('@babel/plugin-proposal-numeric-separator'), // 转义 1_000_000
devMode && require.resolve('react-refresh/babel'),
].filter(Boolean),
};
module.exports = {
entry: {
[pkgJson.ident]: ['./src/index.tsx'],
},
resolve: {
symlinks: false,
extensions: ['.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json'],
alias: {
'@src': path.resolve(process.cwd(), 'src'),
},
},
plugins: [
new ProgressBarPlugin(),
new CaseSensitivePathsPlugin(),
new StatsPlugin('manifest.json', {
chunkModules: false,
source: true,
chunks: false,
modules: false,
assets: true,
children: false,
exclude: [/node_modules/],
}),
new HappyPack({
id: 'babel',
loaders: [
'cache-loader',
{
loader: 'babel-loader',
options: babelOptions,
},
],
threadPool: happyThreadPool,
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
RUN_ENV: JSON.stringify(process.env.RUN_ENV),
},
}),
new HtmlWebpackPlugin({
meta: {
manifest: 'manifest.json',
},
template: './src/index.html',
inject: 'body',
}),
].filter(Boolean),
module: {
rules: [
{
parser: { system: false },
},
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules\/(?!react-intl|@didi\/dcloud-design)/,
use: [
{
loader: 'happypack/loader?id=babel',
},
],
},
{
test: /\.(png|svg|jpeg|jpg|gif|ttf|woff|woff2|eot|pdf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/image/',
esModule: false,
},
},
],
},
{
test: /\.(css|less)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: theme,
},
},
],
},
],
},
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
stats: 'errors-warnings',
};

View File

@@ -0,0 +1,35 @@
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const pkgJson = require('../package');
module.exports = {
mode: 'development',
plugins: [
new MiniCssExtractPlugin(),
new ReactRefreshWebpackPlugin({
overlay: false,
}),
],
devServer: {
host: '127.0.0.1',
port: pkgJson.port,
hot: true,
open: false,
publicPath: `http://localhost:${pkgJson.port}/${pkgJson.ident}/`,
inline: true,
disableHostCheck: true,
historyApiFallback: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
output: {
path: '/',
publicPath: `http://localhost:${pkgJson.port}/${pkgJson.ident}/`,
library: pkgJson.ident,
libraryTarget: 'amd',
filename: '[name].js',
chunkFilename: '[name].js',
},
devtool: 'cheap-module-eval-source-map',
};

View File

@@ -0,0 +1,59 @@
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const pkgJson = require('../package');
module.exports = {
mode: 'production',
externals: [
/^react$/,
/^react\/lib.*/,
/^react-dom$/,
/.*react-dom.*/,
/^single-spa$/,
/^single-spa-react$/,
/^moment$/,
/^lodash$/,
/^react-router$/,
/^react-router-dom$/,
],
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: '[name]-[chunkhash].css',
}),
],
output: {
path: path.resolve(process.cwd(), `../../../km-rest/src/main/resources/templates/${pkgJson.ident}`),
publicPath: `${process.env.PUBLIC_PATH}/${pkgJson.ident}/`,
library: pkgJson.ident,
libraryTarget: 'amd',
filename: '[name]-[chunkhash].js',
chunkFilename: '[name]-[chunkhash].js',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vendor',
priority: 10,
enforce: true,
minChunks: 1,
maxSize: 3500000,
},
},
},
minimizer: [
new TerserJSPlugin({
cache: true,
sourceMap: true,
}),
new OptimizeCSSAssetsPlugin({}),
],
},
devtool: 'none',
};

View File

@@ -1344,6 +1344,16 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"@knowdesign/icons": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.0.tgz",
"integrity": "sha512-7c+h2TSbh2ihTkXIivuO+DddNC5wG7hVv9SS4ccmkvTKls2ZTLitPu+U0wpufDxPhkPMaKEQfsECsVJ+7jLMiw==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",
"react": "16.12.0"
}
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -6815,9 +6825,9 @@
"dev": true "dev": true
}, },
"is-callable": { "is-callable": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.5.tgz", "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.6.tgz",
"integrity": "sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw==", "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==",
"dev": true "dev": true
}, },
"is-color-stop": { "is-color-stop": {

View File

@@ -21,9 +21,11 @@
"build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000" "build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000"
}, },
"dependencies": { "dependencies": {
"@knowdesign/icons": "^1.0.0",
"babel-preset-react-app": "^10.0.0", "babel-preset-react-app": "^10.0.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"knowdesign": "1.3.7",
"less": "^3.9.0", "less": "^3.9.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"mobx": "4.15.7", "mobx": "4.15.7",
@@ -36,8 +38,7 @@
"react-intl": "^3.2.1", "react-intl": "^3.2.1",
"react-router-cache-route": "^1.11.1", "react-router-cache-route": "^1.11.1",
"single-spa": "^5.8.0", "single-spa": "^5.8.0",
"single-spa-react": "^2.14.0", "single-spa-react": "^2.14.0"
"knowdesign": "1.3.7"
}, },
"devDependencies": { "devDependencies": {
"@ant-design/icons": "^4.6.2", "@ant-design/icons": "^4.6.2",

View File

@@ -22,6 +22,20 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 12px; margin-bottom: 12px;
.left,
.right {
display: flex;
align-items: center;
}
.left .refresh-icon {
font-size: 20px;
color: #74788d;
cursor: pointer;
}
.right .search-input {
width: 248px;
margin-right: 8px;
}
} }
} }
} }

View File

@@ -47,8 +47,8 @@ serviceInstance.interceptors.response.use(
return res; return res;
}, },
(err: any) => { (err: any) => {
const config = err.config; const config = err?.config;
if (!config || !config.retryTimes) return dealResponse(err, config.customNotification); if (!config || !config.retryTimes) return dealResponse(err);
const { __retryCount = 0, retryDelay = 300, retryTimes } = config; const { __retryCount = 0, retryDelay = 300, retryTimes } = config;
config.__retryCount = __retryCount; config.__retryCount = __retryCount;
if (__retryCount >= retryTimes) { if (__retryCount >= retryTimes) {

View File

@@ -1,6 +1,6 @@
import React, { useLayoutEffect } from 'react'; import React, { useLayoutEffect } from 'react';
import { Utils, AppContainer } from 'knowdesign'; import { Utils, AppContainer } from 'knowdesign';
import { goLogin } from 'constants/axiosConfig'; import { goLogin } from '@src/constants/axiosConfig';
// 权限对应表 // 权限对应表
export enum ConfigPermissionMap { export enum ConfigPermissionMap {

View File

@@ -15,6 +15,7 @@ import {
AppContainer, AppContainer,
Utils, Utils,
} from 'knowdesign'; } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import moment from 'moment'; import moment from 'moment';
// 引入代码编辑器 // 引入代码编辑器
@@ -26,8 +27,8 @@ import 'codemirror/addon/selection/active-line';
import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/edit/closebrackets';
require('codemirror/mode/xml/xml'); require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript'); require('codemirror/mode/javascript/javascript');
import api from 'api'; import api from '@src/api';
import { defaultPagination } from 'constants/common'; import { defaultPagination } from '@src/constants/common';
import TypicalListCard from '../../components/TypicalListCard'; import TypicalListCard from '../../components/TypicalListCard';
import { ConfigPermissionMap } from '../CommonConfig'; import { ConfigPermissionMap } from '../CommonConfig';
import { ConfigOperate, ConfigProps } from './config'; import { ConfigOperate, ConfigProps } from './config';
@@ -384,7 +385,7 @@ export default () => {
const onDelete = (record: ConfigProps) => { const onDelete = (record: ConfigProps) => {
confirm({ confirm({
title: '确定删除配置吗?', title: '确定删除配置吗?',
content: `配置${record.valueName}${record.status === 1 ? '为启用状态,无法删除' : ''}`, content: `配置 [${record.valueName}] ${record.status === 1 ? '为启用状态,无法删除' : ''}`,
centered: true, centered: true,
okText: '删除', okText: '删除',
okType: 'primary', okType: 'primary',
@@ -398,9 +399,11 @@ export default () => {
}, },
maskClosable: true, maskClosable: true,
onOk() { onOk() {
return request(api.editConfig, { return request(api.delConfig, {
method: 'POST', method: 'DELETE',
data: record.id, params: {
id: record.id,
},
}).then((_) => { }).then((_) => {
message.success('删除成功'); message.success('删除成功');
getConfigList(); getConfigList();
@@ -431,22 +434,28 @@ export default () => {
<TypicalListCard title="配置管理"> <TypicalListCard title="配置管理">
<div className="config-manage-page"> <div className="config-manage-page">
<div className="operate-bar"> <div className="operate-bar">
<Form form={form} layout="inline" onFinish={() => getConfigList({ page: 1 })}> <div className="left">
<Form.Item name="valueGroup"> <div className="refresh-icon" onClick={() => getConfigList()}>
<Select style={{ width: 180 }} placeholder="请选择模块" options={configGroupList} /> <IconFont className="icon" type="icon-shuaxin1" />
</Form.Item> </div>
<Form.Item name="valueName"> <Divider type="vertical" style={{ height: 20, top: 0 }} />
<Input style={{ width: 180 }} placeholder="请输入配置键" /> <Form form={form} layout="inline" onFinish={() => getConfigList({ page: 1 })}>
</Form.Item> <Form.Item name="valueGroup">
<Form.Item name="memo"> <Select style={{ width: 180 }} placeholder="请选择模块" options={configGroupList} />
<Input style={{ width: 180 }} placeholder="请输入描述" /> </Form.Item>
</Form.Item> <Form.Item name="valueName">
<Form.Item> <Input style={{ width: 180 }} placeholder="请输入配置键" />
<Button type="primary" ghost htmlType="submit"> </Form.Item>
<Form.Item name="memo">
</Button> <Input style={{ width: 180 }} placeholder="请输入描述" />
</Form.Item> </Form.Item>
</Form> <Form.Item>
<Button type="primary" ghost htmlType="submit">
</Button>
</Form.Item>
</Form>
</div>
{global.hasPermission && global.hasPermission(ConfigPermissionMap.CONFIG_ADD) ? ( {global.hasPermission && global.hasPermission(ConfigPermissionMap.CONFIG_ADD) ? (
<Button <Button
type="primary" type="primary"

View File

@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Input, Select, ProTable, DatePicker, Utils, Tooltip } from 'knowdesign'; import { Button, Form, Input, Select, ProTable, DatePicker, Utils, Tooltip, Divider } from 'knowdesign';
import api from 'api'; import { IconFont } from '@knowdesign/icons';
import { defaultPagination } from 'constants/common'; import api from '@src/api';
import { defaultPagination } from '@src/constants/common';
import TypicalListCard from '../../components/TypicalListCard'; import TypicalListCard from '../../components/TypicalListCard';
import './index.less'; import './index.less';
import moment from 'moment'; import moment from 'moment';
@@ -119,25 +120,32 @@ export default () => {
<> <>
<TypicalListCard title="操作记录"> <TypicalListCard title="操作记录">
<div className="operate-bar"> <div className="operate-bar">
<Form form={form} layout="inline" onFinish={() => getData({ page: 1 })}> <div className="left">
<Form.Item name="targetType"> <div className="refresh-icon" onClick={() => getData()}>
<Select placeholder="请选择模块" options={configGroupList} style={{ width: 160 }} /> <IconFont className="icon" type="icon-shuaxin1" />
</Form.Item> </div>
<Form.Item name="target"> <Divider type="vertical" style={{ height: 20, top: 0 }} />
<Input placeholder="请输入操作对象" />
</Form.Item> <Form form={form} layout="inline" onFinish={() => getData({ page: 1 })}>
<Form.Item name="detail"> <Form.Item name="targetType">
<Input placeholder="请输入操作内容" /> <Select placeholder="请选择模块" options={configGroupList} style={{ width: 160 }} />
</Form.Item> </Form.Item>
<Form.Item name="time"> <Form.Item name="target">
<RangePicker showTime /> <Input placeholder="请输入操作对象" />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item name="detail">
<Button type="primary" ghost htmlType="submit"> <Input placeholder="请输入操作内容" />
</Form.Item>
</Button> <Form.Item name="time">
</Form.Item> <RangePicker showTime />
</Form> </Form.Item>
<Form.Item>
<Button type="primary" ghost htmlType="submit">
</Button>
</Form.Item>
</Form>
</div>
</div> </div>
<ProTable <ProTable

View File

@@ -21,9 +21,9 @@ import {
} from 'knowdesign'; } from 'knowdesign';
import moment from 'moment'; import moment from 'moment';
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'; import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
import { defaultPagination } from 'constants/common'; import { defaultPagination } from '@src/constants/common';
import { RoleProps, PermissionNode, AssignUser, RoleOperate, FormItemPermission } from './config'; import { RoleProps, PermissionNode, AssignUser, RoleOperate, FormItemPermission } from './config';
import api from 'api'; import api from '@src/api';
import CheckboxGroupContainer from './CheckboxGroupContainer'; import CheckboxGroupContainer from './CheckboxGroupContainer';
import { ConfigPermissionMap } from '../CommonConfig'; import { ConfigPermissionMap } from '../CommonConfig';
@@ -611,38 +611,45 @@ export default (props: { curTabKey: string }): JSX.Element => {
return ( return (
<> <>
<div className="operate-bar-right"> <div className="operate-bar">
<Input <div className="left">
className="search-input" <div className="refresh-icon" onClick={() => getRoleList()}>
suffix={ <IconFont className="icon" type="icon-shuaxin1" />
<IconFont </div>
type="icon-fangdajing" </div>
onClick={(_) => { <div className="right">
setSearchKeywords(searchKeywordsInput); <Input
}} className="search-input"
style={{ fontSize: '16px' }} suffix={
/> <IconFont
} type="icon-fangdajing"
placeholder="请输入角色名称" onClick={(_) => {
value={searchKeywordsInput} setSearchKeywords(searchKeywordsInput);
onPressEnter={(_) => { }}
setSearchKeywords(searchKeywordsInput); style={{ fontSize: '16px' }}
}} />
onChange={(e) => { }
setSearchKeywordsInput(e.target.value); placeholder="请输入角色名称"
}} value={searchKeywordsInput}
/> onPressEnter={(_) => {
{global.hasPermission && global.hasPermission(ConfigPermissionMap.ROLE_ADD) ? ( setSearchKeywords(searchKeywordsInput);
<Button }}
type="primary" onChange={(e) => {
icon={<PlusOutlined />} setSearchKeywordsInput(e.target.value);
onClick={() => detailRef.current.onOpen(true, RoleOperate.Add, getRoleList, undefined)} }}
> />
{global.hasPermission && global.hasPermission(ConfigPermissionMap.ROLE_ADD) ? (
</Button> <Button
) : ( type="primary"
<></> icon={<PlusOutlined />}
)} onClick={() => detailRef.current.onOpen(true, RoleOperate.Add, getRoleList, undefined)}
>
</Button>
) : (
<></>
)}
</div>
</div> </div>
<ProTable <ProTable

View File

@@ -1,12 +1,13 @@
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Form, ProTable, Select, Button, Input, Modal, message, Drawer, Space, Divider, AppContainer, Utils } from 'knowdesign'; import { Form, ProTable, Select, Button, Input, Modal, message, Drawer, Space, Divider, AppContainer, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import moment from 'moment'; import moment from 'moment';
import { defaultPagination } from 'constants/common'; import { defaultPagination } from '@src/constants/common';
import { UserProps, UserOperate } from './config'; import { UserProps, UserOperate } from './config';
import CheckboxGroupContainer from './CheckboxGroupContainer'; import CheckboxGroupContainer from './CheckboxGroupContainer';
import TagsWithHide from '../../components/TagsWithHide/index'; import TagsWithHide from '../../components/TagsWithHide/index';
import api from 'api'; import api from '@src/api';
import { ConfigPermissionMap } from '../CommonConfig'; import { ConfigPermissionMap } from '../CommonConfig';
const { confirm } = Modal; const { confirm } = Modal;
@@ -341,22 +342,29 @@ export default (props: { curTabKey: string }) => {
return ( return (
<> <>
<div className="operate-bar"> <div className="operate-bar">
<Form form={form} layout="inline" onFinish={() => getUserList({ page: 1 })}> <div className="left">
<Form.Item name="userName"> <div className="refresh-icon" onClick={() => getUserList()}>
<Input placeholder="请输入用户账号" /> <IconFont className="icon" type="icon-shuaxin1" />
</Form.Item> </div>
<Form.Item name="realName"> <Divider type="vertical" style={{ height: 20, top: 0 }} />
<Input placeholder="请输入用户实名" />
</Form.Item> <Form form={form} layout="inline" onFinish={() => getUserList({ page: 1 })}>
<Form.Item name="roleId"> <Form.Item name="userName">
<Select style={{ width: 190 }} placeholder="选择平台已创建的角色名" options={simpleRoleList} /> <Input placeholder="请输入用户账号" />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item name="realName">
<Button type="primary" ghost htmlType="submit"> <Input placeholder="请输入用户实名" />
</Form.Item>
</Button> <Form.Item name="roleId">
</Form.Item> <Select style={{ width: 190 }} placeholder="选择平台已创建的角色名" options={simpleRoleList} />
</Form> </Form.Item>
<Form.Item>
<Button type="primary" ghost htmlType="submit">
</Button>
</Form.Item>
</Form>
</div>
{global.hasPermission && global.hasPermission(ConfigPermissionMap.USER_ADD) ? ( {global.hasPermission && global.hasPermission(ConfigPermissionMap.USER_ADD) ? (
<Button <Button
type="primary" type="primary"

View File

@@ -44,13 +44,3 @@
.role-tab-assign-user .desc-row { .role-tab-assign-user .desc-row {
margin-bottom: 24px; margin-bottom: 24px;
} }
.operate-bar-right {
display: flex;
justify-content: right;
margin-bottom: 12px;
.search-input {
width: 248px;
margin-right: 8px;
}
}

View File

@@ -1,56 +1,9 @@
/* eslint-disable */
const path = require('path'); const path = require('path');
require('dotenv').config({ path: path.resolve(process.cwd(), '../../.env') }); require('dotenv').config({ path: path.resolve(process.cwd(), '../../.env') });
const isProd = process.env.NODE_ENV === 'production';
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const pkgJson = require('./package'); const devMode = process.env.NODE_ENV === 'development';
const getWebpackCommonConfig = require('./config/d1-webpack.base'); const commonConfig = require('./config/webpack.common');
const outPath = path.resolve(__dirname, `../../../km-rest/src/main/resources/templates/${pkgJson.ident}`); const devConfig = require('./config/webpack.dev');
const jsFileName = isProd ? '[name]-[chunkhash].js' : '[name].js'; const prodConfig = require('./config/webpack.prod');
module.exports = merge(getWebpackCommonConfig(), { module.exports = merge(commonConfig, devMode ? devConfig : prodConfig);
mode: isProd ? 'production' : 'development',
entry: {
[pkgJson.ident]: ['./src/index.tsx'],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
RUN_ENV: JSON.stringify(process.env.RUN_ENV),
},
}),
new HtmlWebpackPlugin({
meta: {
manifest: 'manifest.json',
},
template: './src/index.html',
inject: 'body',
}),
],
output: {
path: outPath,
publicPath: isProd ? `${process.env.PUBLIC_PATH}/${pkgJson.ident}/` : `http://localhost:${pkgJson.port}/${pkgJson.ident}/`,
library: pkgJson.ident,
libraryTarget: 'amd',
filename: jsFileName,
chunkFilename: jsFileName,
},
devtool: isProd ? 'none' : 'cheap-module-eval-source-map',
devServer: {
host: '127.0.0.1',
port: pkgJson.port,
hot: true,
open: false,
publicPath: `http://localhost:${pkgJson.port}/${pkgJson.ident}/`,
inline: true,
disableHostCheck: true,
historyApiFallback: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
proxy: {},
},
});

View File

@@ -1,183 +0,0 @@
/* eslint-disable */
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CoverHtmlWebpackPlugin = require('./CoverHtmlWebpackPlugin.js');
var webpackConfigResolveAlias = require('./webpackConfigResolveAlias');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const theme = require('./theme');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';
const babelOptions = {
cacheDirectory: true,
babelrc: false,
presets: [require.resolve('@babel/preset-env'), require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')],
plugins: [
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-property-in-object'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-methods'), { loose: true }],
require.resolve('@babel/plugin-proposal-export-default-from'),
require.resolve('@babel/plugin-proposal-export-namespace-from'),
require.resolve('@babel/plugin-proposal-object-rest-spread'),
require.resolve('@babel/plugin-transform-runtime'),
!isProd && require.resolve('react-refresh/babel'),
]
.filter(Boolean)
.concat([
[
'babel-plugin-import',
{
libraryName: 'antd',
style: true,
},
],
'@babel/plugin-transform-object-assign',
]),
};
module.exports = () => {
const cssFileName = isProd ? '[name]-[chunkhash].css' : '[name].css';
const plugins = [
new CoverHtmlWebpackPlugin(),
new ProgressBarPlugin(),
new CaseSensitivePathsPlugin(),
new MiniCssExtractPlugin({
filename: cssFileName,
}),
!isProd &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
].filter(Boolean);
const resolve = {
symlinks: false,
extensions: ['.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json'],
alias: webpackConfigResolveAlias,
};
if (isProd) {
plugins.push(new CleanWebpackPlugin());
}
if (!isProd) {
resolve.mainFields = ['module', 'browser', 'main'];
}
return {
externals: isProd
? [
/^react$/,
/^react\/lib.*/,
/^react-dom$/,
/.*react-dom.*/,
/^single-spa$/,
/^single-spa-react$/,
/^moment$/,
/^antd$/,
/^lodash$/,
/^echarts$/,
/^react-router$/,
/^react-router-dom$/,
]
: [],
resolve,
plugins,
module: {
rules: [
{
parser: { system: false },
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: babelOptions,
},
],
},
{
test: /\.(ts|tsx)$/,
use: [
{
loader: 'babel-loader',
options: babelOptions,
},
{
loader: 'ts-loader',
options: {
allowTsInNodeModules: true,
},
},
],
},
{
test: /\.(png|svg|jpeg|jpg|gif|ttf|woff|woff2|eot|pdf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/image/',
esModule: false,
},
},
],
},
{
test: /\.(css|less)$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: theme,
},
},
],
},
],
},
optimization: Object.assign(
isProd
? {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vendor',
priority: 10,
enforce: true,
minChunks: 1,
maxSize: 3000000,
},
},
},
minimizer: [
new TerserJSPlugin({
cache: true,
sourceMap: true,
}),
new OptimizeCSSAssetsPlugin({}),
],
}
: {}
),
devtool: isProd ? 'cheap-module-source-map' : '',
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
};
};

View File

@@ -0,0 +1,123 @@
const path = require('path');
const theme = require('./theme');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CoverHtmlWebpackPlugin = require('./CoverHtmlWebpackPlugin.js');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const devMode = process.env.NODE_ENV === 'development';
const babelOptions = {
cacheDirectory: true,
babelrc: false,
presets: [require.resolve('@babel/preset-env'), require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')],
plugins: [
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-property-in-object'), { loose: true }],
[require.resolve('@babel/plugin-proposal-private-methods'), { loose: true }],
require.resolve('@babel/plugin-proposal-export-default-from'),
require.resolve('@babel/plugin-proposal-export-namespace-from'),
require.resolve('@babel/plugin-proposal-object-rest-spread'),
require.resolve('@babel/plugin-transform-runtime'),
devMode && require.resolve('react-refresh/babel'),
devMode && [
'babel-plugin-import',
{
libraryName: 'antd',
style: true,
},
],
].filter(Boolean),
};
module.exports = {
entry: {
layout: ['./src/index.tsx'],
},
resolve: {
symlinks: false,
extensions: ['.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json'],
alias: {
'@src': path.resolve('src'),
},
},
plugins: [
new CoverHtmlWebpackPlugin(),
new ProgressBarPlugin(),
new CaseSensitivePathsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
RUN_ENV: JSON.stringify(process.env.RUN_ENV),
BUSINESS_VERSION: process.env.BUSINESS_VERSION === 'true',
PUBLIC_PATH: JSON.stringify(process.env.PUBLIC_PATH),
},
}),
new HtmlWebpackPlugin({
meta: {
manifest: 'manifest.json',
},
template: './src/index.html',
favicon: path.resolve('favicon.ico'),
inject: 'body',
}),
],
module: {
rules: [
{
parser: { system: false },
},
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: babelOptions,
},
{
loader: 'ts-loader',
options: {
allowTsInNodeModules: true,
},
},
],
},
{
test: /\.(png|svg|jpeg|jpg|gif|ttf|woff|woff2|eot|pdf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/image/',
esModule: false,
},
},
],
},
{
test: /\.(css|less)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: theme,
},
},
],
},
],
},
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
stats: 'errors-warnings',
};

View File

@@ -0,0 +1,45 @@
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
mode: 'development',
plugins: [
new MiniCssExtractPlugin(),
new ReactRefreshWebpackPlugin({
overlay: false,
}),
],
output: {
path: '/',
publicPath: '/',
filename: '[name].js',
chunkFilename: '[name].js',
library: 'layout',
libraryTarget: 'amd',
},
devServer: {
host: 'localhost',
port: 8000,
hot: true,
open: true,
openPage: 'http://localhost:8000/',
inline: true,
historyApiFallback: true,
publicPath: `http://localhost:8000/`,
headers: {
'cache-control': 'no-cache',
pragma: 'no-cache',
'Access-Control-Allow-Origin': '*',
},
proxy: {
'/ks-km/api/v3': {
changeOrigin: true,
target: 'http://localhost:8080/',
},
'/logi-security/api/v1': {
changeOrigin: true,
target: 'http://localhost:8080/',
},
},
},
};

View File

@@ -0,0 +1,79 @@
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CountPlugin = require('./CountComponentWebpackPlugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const outputPath = path.resolve(process.cwd(), `../../../km-rest/src/main/resources/templates/layout`);
module.exports = {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CountPlugin({
pathname: 'knowdesign',
startCount: true,
isExportExcel: false,
}),
new MiniCssExtractPlugin({
filename: '[name]-[chunkhash].css',
}),
new CopyWebpackPlugin([
{
from: path.resolve(process.cwd(), 'static'),
to: path.resolve(outputPath, '../static'),
},
{
from: path.resolve(process.cwd(), 'favicon.ico'),
to: path.resolve(outputPath, '../favicon.ico'),
},
]),
],
externals: [
/^react$/,
/^react\/lib.*/,
/^react-dom$/,
/.*react-dom.*/,
/^single-spa$/,
/^single-spa-react$/,
/^moment$/,
/^antd$/,
/^lodash$/,
/^echarts$/,
/^react-router$/,
/^react-router-dom$/,
],
output: {
path: outputPath,
publicPath: process.env.PUBLIC_PATH + '/layout/',
filename: '[name]-[chunkhash].js',
chunkFilename: '[name]-[chunkhash].js',
library: 'layout',
libraryTarget: 'amd',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vendor',
priority: 10,
enforce: true,
minChunks: 1,
maxSize: 3000000,
},
},
},
minimizer: [
new TerserJSPlugin({
cache: true,
sourceMap: true,
}),
new OptimizeCSSAssetsPlugin({}),
],
},
devtool: 'none',
};

View File

@@ -1,5 +0,0 @@
var path = require('path');
module.exports = {
react: path.resolve('./node_modules/react'),
};

View File

@@ -1387,6 +1387,16 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"@knowdesign/icons": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.1.tgz",
"integrity": "sha512-EI3s25BJt+Slv7/t6B3K3zv7I6TKkk2Wf1y68zuxK80MMkWf8lqqUtyAZbFDoPUfXAjw6vHktMBH44gbMHMRFA==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",
"react": "16.12.0"
}
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -6024,6 +6034,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
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz",
@@ -6562,6 +6578,12 @@
"kind-of": "^4.0.0" "kind-of": "^4.0.0"
}, },
"dependencies": { "dependencies": {
"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
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz",
@@ -6638,18 +6660,6 @@
"resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="
}, },
"hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"requires": {
"@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"
}
},
"he": { "he": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
@@ -6666,11 +6676,6 @@
"resolved": "https://registry.npmmirror.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz", "resolved": "https://registry.npmmirror.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz",
"integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==" "integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg=="
}, },
"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=="
},
"history": { "history": {
"version": "4.10.1", "version": "4.10.1",
"resolved": "https://registry.npmmirror.com/history/-/history-4.10.1.tgz", "resolved": "https://registry.npmmirror.com/history/-/history-4.10.1.tgz",
@@ -6883,6 +6888,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
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz",
@@ -7224,28 +7235,6 @@
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true "dev": true
}, },
"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=="
},
"intl-messageformat": {
"version": "7.8.4",
"resolved": "https://registry.npmmirror.com/intl-messageformat/-/intl-messageformat-7.8.4.tgz",
"integrity": "sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA==",
"requires": {
"intl-format-cache": "^4.2.21",
"intl-messageformat-parser": "^3.6.4"
}
},
"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==",
"requires": {
"@formatjs/intl-unified-numberformat": "^3.2.0"
}
},
"invariant": { "invariant": {
"version": "2.2.4", "version": "2.2.4",
"resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz", "resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz",
@@ -7287,6 +7276,12 @@
"kind-of": "^3.0.2" "kind-of": "^3.0.2"
}, },
"dependencies": { "dependencies": {
"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
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
@@ -7354,16 +7349,10 @@
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
} }
}, },
"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
},
"is-callable": { "is-callable": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.5.tgz", "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.6.tgz",
"integrity": "sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw==" "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q=="
}, },
"is-color-stop": { "is-color-stop": {
"version": "1.1.0", "version": "1.1.0",
@@ -7396,6 +7385,12 @@
"kind-of": "^3.0.2" "kind-of": "^3.0.2"
}, },
"dependencies": { "dependencies": {
"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
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
@@ -7478,9 +7473,9 @@
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
}, },
"is-lite": { "is-lite": {
"version": "0.8.2", "version": "0.9.2",
"resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.8.2.tgz", "resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.9.2.tgz",
"integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" "integrity": "sha512-qZuxbaEiKLOKhX4sbHLfhFN9iA3YciuZLb37/DfXCpWnz8p7qNL2lwkpxYMXfjlS8eEEjpULPZxAUI8N6FYvYQ=="
}, },
"is-negative-zero": { "is-negative-zero": {
"version": "2.0.2", "version": "2.0.2",
@@ -7531,12 +7526,6 @@
"path-is-inside": "^1.0.2" "path-is-inside": "^1.0.2"
} }
}, },
"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
},
"is-plain-object": { "is-plain-object": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz", "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -7849,9 +7838,9 @@
}, },
"dependencies": { "dependencies": {
"rc-util": { "rc-util": {
"version": "5.24.2", "version": "5.24.4",
"resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz",
"integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
"requires": { "requires": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"react-is": "^16.12.0", "react-is": "^16.12.0",
@@ -8287,6 +8276,13 @@
"requires": { "requires": {
"fault": "^1.0.0", "fault": "^1.0.0",
"highlight.js": "~10.7.0" "highlight.js": "~10.7.0"
},
"dependencies": {
"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=="
}
} }
}, },
"lru-cache": { "lru-cache": {
@@ -8799,6 +8795,12 @@
"is-descriptor": "^0.1.0" "is-descriptor": "^0.1.0"
} }
}, },
"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
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
@@ -9092,19 +9094,6 @@
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
} }
}, },
"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==",
"requires": {
"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"
}
},
"parse-json": { "parse-json": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz",
@@ -10259,9 +10248,9 @@
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
}, },
"rc-util": { "rc-util": {
"version": "5.24.2", "version": "5.24.4",
"resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz",
"integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
"requires": { "requires": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"react-is": "^16.12.0", "react-is": "^16.12.0",
@@ -10385,9 +10374,9 @@
}, },
"dependencies": { "dependencies": {
"rc-util": { "rc-util": {
"version": "5.24.2", "version": "5.24.4",
"resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz",
"integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
"requires": { "requires": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"react-is": "^16.12.0", "react-is": "^16.12.0",
@@ -10661,9 +10650,9 @@
}, },
"dependencies": { "dependencies": {
"rc-util": { "rc-util": {
"version": "5.24.2", "version": "5.24.4",
"resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz",
"integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
"requires": { "requires": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"react-is": "^16.12.0", "react-is": "^16.12.0",
@@ -10757,9 +10746,9 @@
}, },
"dependencies": { "dependencies": {
"rc-util": { "rc-util": {
"version": "5.24.2", "version": "5.24.4",
"resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.4.tgz",
"integrity": "sha512-MWd0ZEV7xSwN4HM9jz9BwpnMzwCPjYJ7K90lePsrdgAkrmm8U7b4BOTIsv/84BQsaF7N3ejNkcrZ3AfEwc9HXA==", "integrity": "sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==",
"requires": { "requires": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"react-is": "^16.12.0", "react-is": "^16.12.0",
@@ -10780,6 +10769,18 @@
"rc-util": "^5.7.0" "rc-util": "^5.7.0"
}, },
"dependencies": { "dependencies": {
"rc-tree": {
"version": "5.3.8",
"resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.3.8.tgz",
"integrity": "sha512-YuobEryPymqPmHFUOvsoOrYdm24psaj0CrGEUuDUQUeG/nNcTGw6FA2YmF4NsEaNBvNSJUSzwfZnFHrKa/xv0A==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.0.1",
"rc-util": "^5.16.1",
"rc-virtual-list": "^3.4.1"
}
},
"rc-util": { "rc-util": {
"version": "5.24.2", "version": "5.24.2",
"resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.24.2.tgz",
@@ -11045,6 +11046,13 @@
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-proptype-conditional-require": "^1.0.4", "react-proptype-conditional-require": "^1.0.4",
"tree-changes": "^0.9.1" "tree-changes": "^0.9.1"
},
"dependencies": {
"is-lite": {
"version": "0.8.2",
"resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.8.2.tgz",
"integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw=="
}
} }
}, },
"react-freeze": { "react-freeze": {
@@ -11122,6 +11130,30 @@
"intl-messageformat": "^7.8.4", "intl-messageformat": "^7.8.4",
"intl-messageformat-parser": "^3.6.4", "intl-messageformat-parser": "^3.6.4",
"shallow-equal": "^1.2.1" "shallow-equal": "^1.2.1"
},
"dependencies": {
"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=="
},
"intl-messageformat": {
"version": "7.8.4",
"resolved": "https://registry.npmmirror.com/intl-messageformat/-/intl-messageformat-7.8.4.tgz",
"integrity": "sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA==",
"requires": {
"intl-format-cache": "^4.2.21",
"intl-messageformat-parser": "^3.6.4"
}
},
"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==",
"requires": {
"@formatjs/intl-unified-numberformat": "^3.2.0"
}
}
} }
}, },
"react-is": { "react-is": {
@@ -11130,19 +11162,19 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-joyride": { "react-joyride": {
"version": "2.5.2", "version": "2.5.3",
"resolved": "https://registry.npmmirror.com/react-joyride/-/react-joyride-2.5.2.tgz", "resolved": "https://registry.npmmirror.com/react-joyride/-/react-joyride-2.5.3.tgz",
"integrity": "sha512-wsSYX3PhVrdzdd0/fv5f6ySGvb7QyAzleQv/x9IH+x+SXO6b5MUJUkefS+189bgLPkuHMwtSRcPE/oMupfmCVQ==", "integrity": "sha512-DKKvb/JAAsHm0x/RWO3WI6NOtTMHDso5v8MTauxTSz2dFs7Tu1rWg1BDBWmEMj6pUCvem7hblFbCiDAcvhs8tQ==",
"requires": { "requires": {
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"exenv": "^1.2.2", "exenv": "^1.2.2",
"is-lite": "^0.8.2", "is-lite": "^0.9.2",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-floater": "^0.7.6", "react-floater": "^0.7.6",
"react-is": "^16.13.1", "react-is": "^16.13.1",
"scroll": "^3.0.1", "scroll": "^3.0.1",
"scrollparent": "^2.0.1", "scrollparent": "^2.0.1",
"tree-changes": "^0.9.1" "tree-changes": "^0.9.2"
} }
}, },
"react-lifecycles-compat": { "react-lifecycles-compat": {
@@ -11282,6 +11314,13 @@
"lowlight": "^1.14.0", "lowlight": "^1.14.0",
"prismjs": "^1.21.0", "prismjs": "^1.21.0",
"refractor": "^3.0.0" "refractor": "^3.0.0"
},
"dependencies": {
"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=="
}
} }
}, },
"react-test-renderer": { "react-test-renderer": {
@@ -11449,6 +11488,31 @@
"prismjs": "~1.27.0" "prismjs": "~1.27.0"
}, },
"dependencies": { "dependencies": {
"hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"requires": {
"@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"
}
},
"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==",
"requires": {
"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"
}
},
"prismjs": { "prismjs": {
"version": "1.27.0", "version": "1.27.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz", "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz",
@@ -12286,6 +12350,12 @@
"kind-of": "^3.2.0" "kind-of": "^3.2.0"
}, },
"dependencies": { "dependencies": {
"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
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
@@ -12347,6 +12417,14 @@
"dev": true, "dev": true,
"requires": { "requires": {
"is-plain-obj": "^1.0.0" "is-plain-obj": "^1.0.0"
},
"dependencies": {
"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
}
} }
}, },
"source-list-map": { "source-list-map": {
@@ -13049,6 +13127,12 @@
"kind-of": "^3.0.2" "kind-of": "^3.0.2"
}, },
"dependencies": { "dependencies": {
"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
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
@@ -13098,12 +13182,19 @@
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==" "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
}, },
"tree-changes": { "tree-changes": {
"version": "0.9.1", "version": "0.9.3",
"resolved": "https://registry.npmmirror.com/tree-changes/-/tree-changes-0.9.1.tgz", "resolved": "https://registry.npmmirror.com/tree-changes/-/tree-changes-0.9.3.tgz",
"integrity": "sha512-Un6R1T6eUStAVbN4G+2djuXEk271mDY78ptxZUUo+TVcwvHZeUgk+pwXZjOZLAJ9n0+p47KUijeuNJSmpuG6Dw==", "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==",
"requires": { "requires": {
"@gilbarbara/deep-equal": "^0.1.1", "@gilbarbara/deep-equal": "^0.1.1",
"is-lite": "^0.8.2" "is-lite": "^0.8.2"
},
"dependencies": {
"is-lite": {
"version": "0.8.2",
"resolved": "https://registry.npmmirror.com/is-lite/-/is-lite-0.8.2.tgz",
"integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw=="
}
} }
}, },
"ts-loader": { "ts-loader": {
@@ -13502,12 +13593,6 @@
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"dev": true "dev": true
}, },
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
},
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -13718,6 +13803,13 @@
"binary-extensions": "^1.0.0" "binary-extensions": "^1.0.0"
} }
}, },
"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,
"optional": true
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz",
@@ -13895,6 +13987,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
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz",
@@ -14338,6 +14436,12 @@
"binary-extensions": "^1.0.0" "binary-extensions": "^1.0.0"
} }
}, },
"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
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-3.0.0.tgz",
@@ -14439,6 +14543,14 @@
"requires": { "requires": {
"ansi-colors": "^3.0.0", "ansi-colors": "^3.0.0",
"uuid": "^3.3.2" "uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
} }
}, },
"webpack-merge": { "webpack-merge": {

View File

@@ -35,6 +35,7 @@
"dependencies": { "dependencies": {
"@ant-design/compatible": "^1.0.8", "@ant-design/compatible": "^1.0.8",
"@ant-design/icons": "^4.6.2", "@ant-design/icons": "^4.6.2",
"@knowdesign/icons": "^1.0.1",
"@types/react": "^17.0.39", "@types/react": "^17.0.39",
"@types/react-copy-to-clipboard": "^5.0.2", "@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
@@ -49,6 +50,7 @@
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"html-webpack-plugin": "^4.0.0", "html-webpack-plugin": "^4.0.0",
"knowdesign": "^1.3.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.24.0", "moment": "^2.24.0",
"react": "16.12.0", "react": "16.12.0",
@@ -56,12 +58,10 @@
"react-cron-antd": "^1.1.2", "react-cron-antd": "^1.1.2",
"react-dom": "16.12.0", "react-dom": "16.12.0",
"react-intl": "^3.2.1", "react-intl": "^3.2.1",
"react-joyride": "^2.5.0", "react-joyride": "^2.5.3",
"single-spa": "5.9.3", "single-spa": "5.9.3",
"single-spa-react": "2.14.0", "single-spa-react": "2.14.0",
"webpack-bundle-analyzer": "^4.5.0", "webpack-bundle-analyzer": "^4.5.0"
"knowdesign": "1.3.7",
"tree-changes": "0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.5.5", "@babel/core": "^7.5.5",

View File

@@ -3,7 +3,8 @@ import '@babel/polyfill';
import React, { useState, useEffect, useLayoutEffect } from 'react'; import React, { useState, useEffect, useLayoutEffect } from 'react';
import { BrowserRouter, Switch, Route, useLocation, useHistory } from 'react-router-dom'; import { BrowserRouter, Switch, Route, useLocation, useHistory } from 'react-router-dom';
import { get as lodashGet } from 'lodash'; import { get as lodashGet } from 'lodash';
import { DProLayout, AppContainer, IconFont, Menu, Utils, Page403, Page404, Page500, Modal } from 'knowdesign'; import { DProLayout, AppContainer, Menu, Utils, Page403, Page404, Page500, Modal } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import dantdZhCN from 'knowdesign/es/locale/zh_CN'; import dantdZhCN from 'knowdesign/es/locale/zh_CN';
import dantdEnUS from 'knowdesign/es/locale/en_US'; import dantdEnUS from 'knowdesign/es/locale/en_US';
import { DotChartOutlined } from '@ant-design/icons'; import { DotChartOutlined } from '@ant-design/icons';

View File

@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import CardBar from './index'; import CardBar from './index';
import { IconFont, Tag, Utils, Tooltip, Popover, AppContainer } from 'knowdesign'; import { Tag, Utils, Tooltip, Popover, AppContainer } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import api from '@src/api'; import api from '@src/api';
import StateChart from './StateChart'; import StateChart from './StateChart';
import ClusterNorms from '@src/pages/LoadRebalance/ClusterNorms'; import ClusterNorms from '@src/pages/LoadRebalance/ClusterNorms';
@@ -138,15 +139,15 @@ const LoadRebalanceCardBar = (props: any) => {
// content={ // content={
// <div style={{ color: '#495057' }}> // <div style={{ color: '#495057' }}>
// <div> // <div>
// <IconFont className="cutomIcon" type="icon-chaoguo" /> // <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
// 超过均衡区间的有: {cpu?.bigNu || 0} // 超过均衡区间的有: {cpu?.bigNu || 0}
// </div> // </div>
// <div style={{ margin: '6px 0' }}> // <div style={{ margin: '6px 0' }}>
// <IconFont className="cutomIcon" type="icon-qujian" /> // <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
// 在均衡区间内的有: {cpu?.betweenNu || 0} // 在均衡区间内的有: {cpu?.betweenNu || 0}
// </div> // </div>
// <div> // <div>
// <IconFont className="cutomIcon" type="icon-diyu" /> // <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
// 低于均衡区间的有: {cpu?.smallNu || 0} // 低于均衡区间的有: {cpu?.smallNu || 0}
// </div> // </div>
// </div> // </div>
@@ -202,15 +203,15 @@ const LoadRebalanceCardBar = (props: any) => {
content={ content={
<div style={{ color: '#495057' }}> <div style={{ color: '#495057' }}>
<div> <div>
<IconFont className="cutomIcon" type="icon-chaoguo" /> <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
: {disk?.bigNu || 0} : {disk?.bigNu || 0}
</div> </div>
<div style={{ margin: '6px 0' }}> <div style={{ margin: '6px 0' }}>
<IconFont className="cutomIcon" type="icon-qujian" /> <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
: {disk?.betweenNu || 0} : {disk?.betweenNu || 0}
</div> </div>
<div> <div>
<IconFont className="cutomIcon" type="icon-diyu" /> <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
: {disk?.smallNu || 0} : {disk?.smallNu || 0}
</div> </div>
</div> </div>
@@ -267,15 +268,15 @@ const LoadRebalanceCardBar = (props: any) => {
content={ content={
<div style={{ color: '#495057' }}> <div style={{ color: '#495057' }}>
<div> <div>
<IconFont className="cutomIcon" type="icon-chaoguo" /> <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
: {bytesIn?.bigNu || 0} : {bytesIn?.bigNu || 0}
</div> </div>
<div style={{ margin: '6px 0' }}> <div style={{ margin: '6px 0' }}>
<IconFont className="cutomIcon" type="icon-qujian" /> <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
: {bytesIn?.betweenNu || 0} : {bytesIn?.betweenNu || 0}
</div> </div>
<div> <div>
<IconFont className="cutomIcon" type="icon-diyu" /> <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
: {bytesIn?.smallNu || 0} : {bytesIn?.smallNu || 0}
</div> </div>
</div> </div>
@@ -332,15 +333,15 @@ const LoadRebalanceCardBar = (props: any) => {
content={ content={
<div style={{ color: '#495057' }}> <div style={{ color: '#495057' }}>
<div> <div>
<IconFont className="cutomIcon" type="icon-chaoguo" /> <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
: {bytesOut?.bigNu || 0} : {bytesOut?.bigNu || 0}
</div> </div>
<div style={{ margin: '6px 0' }}> <div style={{ margin: '6px 0' }}>
<IconFont className="cutomIcon" type="icon-qujian" /> <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
: {bytesOut?.betweenNu || 0} : {bytesOut?.betweenNu || 0}
</div> </div>
<div> <div>
<IconFont className="cutomIcon" type="icon-diyu" /> <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
: {bytesOut?.smallNu || 0} : {bytesOut?.smallNu || 0}
</div> </div>
</div> </div>

View File

@@ -59,7 +59,7 @@ const EchartsExample = (props: any) => {
normal: { normal: {
color: (params: any) => { color: (params: any) => {
// 定义一个颜色数组colorList // 定义一个颜色数组colorList
const colorList = ['#00C0A2', '#CED4DA', '#FF7066']; const colorList = ['#FF7066', '#00C0A2', '#FF7066'];
return colorList[params.dataIndex]; return colorList[params.dataIndex];
}, },
}, },

View File

@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import CardBar from '@src/components/CardBar'; import CardBar from '@src/components/CardBar';
import { healthDataProps } from '.'; import { healthDataProps } from '.';
import { IconFont, Utils } from 'knowdesign'; import { Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import api from '@src/api'; import api from '@src/api';
import { healthScoreCondition } from './const'; import { healthScoreCondition } from './const';
import { hashDataParse } from '@src/constants/common'; import { hashDataParse } from '@src/constants/common';

View File

@@ -203,11 +203,19 @@
background: rgba(33, 37, 41, 0.04); background: rgba(33, 37, 41, 0.04);
} }
.cutomIcon { .anticon.cutomIcon {
display: inline-block; display: inline-block;
margin-right: 2px; margin-right: 2px;
} }
.anticon.cutomIcon-red {
color: #ff7066;
}
.anticon.cutomIcon-green {
color: #00c0a2;
}
.rebalance-tooltip { .rebalance-tooltip {
.dcloud-tooltip-inner { .dcloud-tooltip-inner {
min-height: 20px; min-height: 20px;

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Drawer, IconFont, Select, Spin, Table } from 'knowdesign'; import { Drawer, Select, Spin, Table } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { Utils, Progress } from 'knowdesign'; import { Utils, Progress } from 'knowdesign';
import './index.less'; import './index.less';
import api from '@src/api'; import api from '@src/api';
@@ -110,8 +111,8 @@ const CardBar = (props: CardBarProps) => {
const promise = record const promise = record
? Utils.request(path) ? Utils.request(path)
: Utils.request(path, { : Utils.request(path, {
params: { dimensionCode: sceneObj.code }, params: { dimensionCode: sceneObj.code },
}); });
promise.then((data: any[]) => { promise.then((data: any[]) => {
setHealthCheckDetailList(data); setHealthCheckDetailList(data);
}); });

View File

@@ -0,0 +1,32 @@
.content-with-copy {
display: flex;
align-items: center;
.content {
flex: 1;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
.copy-icon {
width: 20px;
height: 20px;
padding-top: 2px;
border-radius: 50%;
margin-left: 4px;
font-size: 16px;
color: #adb5bc;
opacity: 0;
&:hover {
background: rgba(33, 37, 41, 0.04);
color: #74788d;
}
}
}
.dcloud-table-cell-row-hover {
.copy-icon {
opacity: 1;
}
}

View File

@@ -0,0 +1,35 @@
import { CheckCircleFilled } from '@ant-design/icons';
import { Tooltip } from 'knowdesign';
import React, { useState } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { IconFont } from '@knowdesign/icons';
import './index.less';
const ContentWithCopy = (props: { content: string }) => {
const { content } = props;
const [visible, setVisible] = useState(false);
return (
<CopyToClipboard text={content}>
<div className="content-with-copy">
<Tooltip title={content}>
<span className="content">{content}</span>
</Tooltip>
{content && (
<Tooltip
title={
<span>
<CheckCircleFilled style={{ color: '#00b365' }} />
</span>
}
visible={visible}
onVisibleChange={() => setVisible(false)}
>
<IconFont className="copy-icon" type="icon-fuzhi" onClick={() => setVisible(true)} />
</Tooltip>
)}
</div>
</CopyToClipboard>
);
};
export default ContentWithCopy;

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Input, IconFont } from 'knowdesign'; import { Input } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import './style/index.less'; import './style/index.less';
interface IObjectProps { interface IObjectProps {

View File

@@ -2,9 +2,10 @@ import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, use
import { AppContainer, Drawer, Spin, Table, SingleChart, Utils, Tooltip } from 'knowdesign'; import { AppContainer, Drawer, Spin, Table, SingleChart, Utils, Tooltip } from 'knowdesign';
import moment from 'moment'; import moment from 'moment';
import api, { MetricType } from '@src/api'; import api, { MetricType } from '@src/api';
import { MetricDefaultChartDataType, MetricChartDataType, formatChartData } from '@src/constants/chartConfig';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { MetricDefaultChartDataType, MetricChartDataType, formatChartData, getDetailChartConfig } from './config'; import { getDetailChartConfig } from './config';
import { UNIT_MAP } from '@src/constants/chartConfig'; import { UNIT_MAP } from '@src/constants/chartConfig';
import RenderEmpty from '../RenderEmpty'; import RenderEmpty from '../RenderEmpty';
@@ -50,8 +51,6 @@ interface DataZoomEventProps {
const DATA_ZOOM_DEFAULT_SCALE = 0.25; const DATA_ZOOM_DEFAULT_SCALE = 0.25;
// 单次向服务器请求数据的范围(默认 6 小时,超过后采集频率间隔会变长),单位: ms // 单次向服务器请求数据的范围(默认 6 小时,超过后采集频率间隔会变长),单位: ms
const DEFAULT_REQUEST_TIME_RANGE = 6 * 60 * 60 * 1000; const DEFAULT_REQUEST_TIME_RANGE = 6 * 60 * 60 * 1000;
// 采样间隔,影响前端补点逻辑,单位: ms
const DEFAULT_POINT_INTERVAL = 60 * 1000;
// 向服务器每轮请求的数量 // 向服务器每轮请求的数量
const DEFAULT_REQUEST_COUNT = 6; const DEFAULT_REQUEST_COUNT = 6;
// 进入详情页默认展示的时间范围 // 进入详情页默认展示的时间范围
@@ -376,8 +375,6 @@ const ChartDetail = (props: ChartDetailProps) => {
global.getMetricDefine || {}, global.getMetricDefine || {},
metricType, metricType,
timeRange, timeRange,
DEFAULT_POINT_INTERVAL,
false,
chartInfo.current.transformUnit chartInfo.current.transformUnit
) as MetricChartDataType[]; ) as MetricChartDataType[];
// 增量填充图表数据 // 增量填充图表数据
@@ -540,14 +537,7 @@ const ChartDetail = (props: ChartDetailProps) => {
if (res?.length) { if (res?.length) {
// 格式化图表需要的数据 // 格式化图表需要的数据
const formattedMetricData = ( const formattedMetricData = (
formatChartData( formatChartData(res, global.getMetricDefine || {}, metricType, curTimeRange) as MetricChartDataType[]
res,
global.getMetricDefine || {},
metricType,
curTimeRange,
DEFAULT_POINT_INTERVAL,
false
) as MetricChartDataType[]
)[0]; )[0];
// 填充图表数据 // 填充图表数据
let initFullTimeRange = curTimeRange; let initFullTimeRange = curTimeRange;

View File

@@ -1,150 +1,4 @@
import { getUnit, getDataNumberUnit, getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfig'; import { getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfig';
import { MetricType } from '@src/api';
import { MetricsDefine } from '@src/pages/CommonConfig';
export interface MetricInfo {
name: string;
desc: string;
type: number;
set: boolean;
support: boolean;
}
// 接口返回图表原始数据类型
export interface MetricDefaultChartDataType {
metricName: string;
metricLines: {
name: string;
createTime: number;
updateTime: number;
metricPoints: {
aggType: string;
timeStamp: number;
value: number;
createTime: number;
updateTime: number;
}[];
}[];
}
// 格式化后图表数据类型
export interface MetricChartDataType {
metricName: string;
metricUnit: string;
metricLines: {
name: string;
data: (string | number)[][];
}[];
dragKey?: number;
}
// 补点
export const supplementaryPoints = (
lines: MetricChartDataType['metricLines'],
timeRange: readonly [number, number],
interval: number,
extraCallback?: (point: [number, 0]) => any[]
) => {
lines.forEach(({ data }) => {
// 获取未补点前线条的点的个数
let len = data.length;
// 记录当前处理到的点的下标值
let i = 0;
for (; i < len; i++) {
if (i === 0) {
let firstPointTimestamp = data[0][0] as number;
while (firstPointTimestamp - interval > timeRange[0]) {
const prevPointTimestamp = firstPointTimestamp - interval;
data.unshift(extraCallback ? extraCallback([prevPointTimestamp, 0]) : [prevPointTimestamp, 0]);
firstPointTimestamp = prevPointTimestamp;
len++;
i++;
}
}
if (i === len - 1) {
let lastPointTimestamp = data[i][0] as number;
while (lastPointTimestamp + interval < timeRange[1]) {
const nextPointTimestamp = lastPointTimestamp + interval;
data.push(extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
lastPointTimestamp = nextPointTimestamp;
}
break;
}
{
let timestamp = data[i][0] as number;
while (timestamp + interval < data[i + 1][0]) {
const nextPointTimestamp = timestamp + interval;
data.splice(i + 1, 0, extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
timestamp = nextPointTimestamp;
len++;
i++;
}
}
}
});
};
// 格式化图表数据
export const formatChartData = (
metricData: MetricDefaultChartDataType[],
getMetricDefine: (type: MetricType, metric: string) => MetricsDefine[keyof MetricsDefine],
metricType: MetricType,
timeRange: readonly [number, number],
supplementaryInterval: number,
needDrag = false,
transformUnit: [string, number] = undefined
): MetricChartDataType[] => {
return metricData.map(({ metricName, metricLines }) => {
const curMetricInfo = (getMetricDefine && getMetricDefine(metricType, metricName)) || null;
const isByteUnit = curMetricInfo?.unit?.toLowerCase().includes('byte');
let maxValue = -1;
const PointsMapMethod = ({ timeStamp, value }: { timeStamp: number; value: string | number }) => {
let parsedValue: string | number = Number(value);
if (Number.isNaN(parsedValue)) {
parsedValue = value;
} else {
// 为避免出现过小的数字影响图表展示效果,图表值统一只保留到小数点后三位
parsedValue = parseFloat(parsedValue.toFixed(3));
if (maxValue < parsedValue) maxValue = parsedValue;
}
return [timeStamp, parsedValue];
};
const chartData = Object.assign(
{
metricName,
metricUnit: curMetricInfo?.unit || '',
metricLines: metricLines
.sort((a, b) => Number(a.name < b.name) - 0.5)
.map(({ name, metricPoints }) => ({
name,
data: metricPoints.map(PointsMapMethod),
})),
},
needDrag ? { dragKey: 999 } : {}
);
chartData.metricLines.forEach(({ data }) => data.sort((a, b) => (a[0] as number) - (b[0] as number)));
supplementaryPoints(chartData.metricLines, timeRange, supplementaryInterval);
// 将所有图表点的值按单位进行转换
if (maxValue > 0) {
const [unitName, unitSize]: [string, number] = transformUnit || isByteUnit ? getUnit(maxValue) : getDataNumberUnit(maxValue);
chartData.metricUnit = isByteUnit
? chartData.metricUnit.toLowerCase().replace('byte', unitName)
: `${unitName}${chartData.metricUnit}`;
chartData.metricLines.forEach(({ data }) => data.forEach((point: any) => (point[1] /= unitSize)));
}
return chartData;
});
};
const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => { const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => {
const len = CHART_COLOR_LIST.length; const len = CHART_COLOR_LIST.length;

View File

@@ -1,14 +1,21 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { arrayMoveImmutable } from 'array-move'; import { arrayMoveImmutable } from 'array-move';
import { Utils, Empty, IconFont, Spin, AppContainer, SingleChart, Tooltip } from 'knowdesign'; import { Utils, Empty, Spin, AppContainer, SingleChart, Tooltip } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import api, { MetricType } from '@src/api'; import api, { MetricType } from '@src/api';
import {
MetricInfo,
MetricDefaultChartDataType,
MetricChartDataType,
formatChartData,
resolveMetricsRank,
} from '@src/constants/chartConfig';
import SingleChartHeader, { KsHeaderOptions } from '../SingleChartHeader'; import SingleChartHeader, { KsHeaderOptions } from '../SingleChartHeader';
import DragGroup from '../DragGroup'; import DragGroup from '../DragGroup';
import ChartDetail from './ChartDetail'; import ChartDetail from './ChartDetail';
import { MetricInfo, MetricDefaultChartDataType, MetricChartDataType, formatChartData, getChartConfig } from './config'; import { getChartConfig } from './config';
import './index.less'; import './index.less';
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
interface IcustomScope { interface IcustomScope {
label: string; label: string;
@@ -39,8 +46,8 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>(); const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
const [metricChartData, setMetricChartData] = useState<MetricChartDataType[]>([]); // 指标图表数据列表 const [metricChartData, setMetricChartData] = useState<MetricChartDataType[]>([]); // 指标图表数据列表
const [gridNum, setGridNum] = useState<number>(12); // 图表列布局 const [gridNum, setGridNum] = useState<number>(12); // 图表列布局
const metricRankList = useRef<string[]>([]);
const chartDetailRef = useRef(null); const chartDetailRef = useRef(null);
const chartDragOrder = useRef([]);
const curFetchingTimestamp = useRef(0); const curFetchingTimestamp = useRef(0);
// 获取节点范围列表 // 获取节点范围列表
@@ -60,23 +67,33 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
setScopeList(list); setScopeList(list);
}; };
// 更新 rank
const updateRank = (metricList: MetricInfo[]) => {
const { list, listInfo, shouldUpdate } = resolveMetricsRank(metricList);
metricRankList.current = list;
if (shouldUpdate) {
setMetricList(listInfo);
}
};
// 获取指标列表 // 获取指标列表
const getMetricList = () => { const getMetricList = () => {
Utils.request(api.getDashboardMetricList(clusterId, dashboardType)).then((res: MetricInfo[] | null) => { Utils.request(api.getDashboardMetricList(clusterId, dashboardType)).then((res: MetricInfo[] | null) => {
if (!res) return; if (!res) return;
const showMetrics = res.filter((metric) => metric.support); const supportMetrics = res.filter((metric) => metric.support);
const selectedMetrics = showMetrics.filter((metric) => metric.set).map((metric) => metric.name); const selectedMetrics = supportMetrics.filter((metric) => metric.set).map((metric) => metric.name);
setMetricsList(showMetrics); updateRank([...supportMetrics]);
setMetricsList(supportMetrics);
setSelectedMetricNames(selectedMetrics); setSelectedMetricNames(selectedMetrics);
}); });
}; };
// 更新指标 // 更新指标
const setMetricList = (metricsSet: { [name: string]: boolean }) => { const setMetricList = (metricDetailDTOList: { metric: string; rank: number; set: boolean }[]) => {
return Utils.request(api.getDashboardMetricList(clusterId, dashboardType), { return Utils.request(api.getDashboardMetricList(clusterId, dashboardType), {
method: 'POST', method: 'POST',
data: { data: {
metricsSet, metricDetailDTOList,
}, },
}); });
}; };
@@ -84,10 +101,11 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
// 根据筛选项获取图表信息 // 根据筛选项获取图表信息
const getMetricChartData = () => { const getMetricChartData = () => {
!curHeaderOptions.isAutoReload && setLoading(true); !curHeaderOptions.isAutoReload && setLoading(true);
const [startTime, endTime] = curHeaderOptions.rangeTime;
const [startTime, endTime] = curHeaderOptions.rangeTime;
const curTimestamp = Date.now(); const curTimestamp = Date.now();
curFetchingTimestamp.current = curTimestamp; curFetchingTimestamp.current = curTimestamp;
Utils.post(api.getDashboardMetricChartData(clusterId, dashboardType), { Utils.post(api.getDashboardMetricChartData(clusterId, dashboardType), {
startTime, startTime,
endTime, endTime,
@@ -108,36 +126,20 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
setMetricChartData([]); setMetricChartData([]);
} else { } else {
// 格式化图表需要的数据 // 格式化图表需要的数据
const supplementaryInterval = (endTime - startTime > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
const formattedMetricData = formatChartData( const formattedMetricData = formatChartData(
res, res,
global.getMetricDefine || {}, global.getMetricDefine || {},
dashboardType, dashboardType,
curHeaderOptions.rangeTime, curHeaderOptions.rangeTime
supplementaryInterval,
true
) as MetricChartDataType[]; ) as MetricChartDataType[];
// 处理图表的拖拽顺 // 指标排
if (chartDragOrder.current && chartDragOrder.current.length) { formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName));
// 根据当前拖拽顺序排列图表数据
formattedMetricData.forEach((metric) => {
const i = chartDragOrder.current.indexOf(metric.metricName);
metric.dragKey = i === -1 ? 999 : i;
});
formattedMetricData.sort((a, b) => a.dragKey - b.dragKey);
}
// 更新当前拖拽顺序(处理新增或减少图表的情况)
chartDragOrder.current = formattedMetricData.map((data) => data.metricName);
setMetricChartData(formattedMetricData); setMetricChartData(formattedMetricData);
} }
setLoading(false); setLoading(false);
}, },
() => { () => curFetchingTimestamp.current === curTimestamp && setLoading(false)
if (curFetchingTimestamp.current === curTimestamp) {
setLoading(false);
}
}
); );
}; };
@@ -163,11 +165,19 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
// 指标选中项更新回调 // 指标选中项更新回调
const indicatorChangeCallback = (newMetricNames: (string | number)[]) => { const indicatorChangeCallback = (newMetricNames: (string | number)[]) => {
const updateMetrics: { [name: string]: boolean } = {}; const updateMetrics: { metric: string; set: boolean; rank: number }[] = [];
// 需要选中的指标 // 需要选中的指标
newMetricNames.forEach((name) => !selectedMetricNames.includes(name) && (updateMetrics[name] = true)); 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[name] = false)); 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(); const requestPromise = Object.keys(updateMetrics).length ? setMetricList(updateMetrics) : Promise.resolve();
requestPromise.then( requestPromise.then(
@@ -186,7 +196,11 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
// 拖拽结束回调,更新图表顺序,并触发图表的 onDrag 事件( 设置为 false ),允许同步展示图表的 tooltip // 拖拽结束回调,更新图表顺序,并触发图表的 onDrag 事件( 设置为 false ),允许同步展示图表的 tooltip
const dragEnd = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => { const dragEnd = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
busInstance.emit('onDrag', false); busInstance.emit('onDrag', false);
chartDragOrder.current = arrayMoveImmutable(chartDragOrder.current, oldIndex, newIndex); 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)); setMetricChartData(arrayMoveImmutable(metricChartData, oldIndex, newIndex));
}; };

View File

@@ -1,4 +1,5 @@
import { Col, IconFont, Row } from 'knowdesign'; import { Col, Row } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import React from 'react'; import React from 'react';
import { SortableContainer, SortableContainerProps, SortableHandle, SortableElement, SortableElementProps } from 'react-sortable-hoc'; import { SortableContainer, SortableContainerProps, SortableHandle, SortableElement, SortableElementProps } from 'react-sortable-hoc';
import './index.less'; import './index.less';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Drawer, Button, Space, Divider, AppContainer, ProTable, IconFont } from 'knowdesign'; import { Drawer, Button, Space, Divider, AppContainer, ProTable } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { IindicatorSelectModule } from './index'; import { IindicatorSelectModule } from './index';
import './style/indicator-drawer.less'; import './style/indicator-drawer.less';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Radio, Input, Popover, Space, Checkbox, Row, Col, Button, IconFont } from 'knowdesign'; import { Radio, Input, Popover, Space, Checkbox, Row, Col, Button } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { InodeScopeModule } from './index'; import { InodeScopeModule } from './index';
import './style/node-scope.less'; import './style/node-scope.less';

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Tooltip, Select, IconFont, Utils, Divider, Button } from 'knowdesign'; import { Tooltip, Select, Utils, Divider, Button } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import moment from 'moment'; import moment from 'moment';
import { DRangeTime } from 'knowdesign'; import { DRangeTime } from 'knowdesign';
import IndicatorDrawer from './IndicatorDrawer'; import IndicatorDrawer from './IndicatorDrawer';

View File

@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ArrowLeftOutlined } from '@ant-design/icons'; import { ArrowLeftOutlined } from '@ant-design/icons';
import { Button, Divider, Drawer, IconFont, Select, Space, Table, Utils } from 'knowdesign'; import { Button, Divider, Drawer, Select, Space, Table, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import Api, { MetricType } from '@src/api/index'; import Api, { MetricType } from '@src/api/index';
const { Option } = Select; const { Option } = Select;

View File

@@ -18,9 +18,9 @@ import {
Space, Space,
Divider, Divider,
Transfer, Transfer,
IconFont,
Tooltip, Tooltip,
} from 'knowdesign'; } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import './index.less'; import './index.less';
import Api, { MetricType } from '@src/api/index'; import Api, { MetricType } from '@src/api/index';
import moment from 'moment'; import moment from 'moment';

View File

@@ -1,5 +1,44 @@
import moment from 'moment'; import moment from 'moment';
import { MetricType } from '@src/api';
import { MetricsDefine } from '@src/pages/CommonConfig';
export interface MetricInfo {
name: string;
desc: string;
type: number;
set: boolean;
rank: number | null;
support: boolean;
}
// 接口返回图表原始数据类型
export interface MetricDefaultChartDataType {
metricName: string;
metricLines: {
name: string;
createTime: number;
updateTime: number;
metricPoints: {
aggType: string;
timeStamp: number;
value: number;
createTime: number;
updateTime: number;
}[];
}[];
}
// 格式化后图表数据类型
export interface MetricChartDataType {
metricName: string;
metricUnit: string;
metricLines: {
name: string;
data: (string | number)[][];
}[];
}
// 图表颜色库
export const CHART_COLOR_LIST = [ export const CHART_COLOR_LIST = [
'#556ee6', '#556ee6',
'#94BEF2', '#94BEF2',
@@ -16,27 +55,176 @@ export const CHART_COLOR_LIST = [
'#C9E795', '#C9E795',
]; ];
// 图表存储单位换算
export const UNIT_MAP = { export const UNIT_MAP = {
TB: Math.pow(1024, 4), TB: Math.pow(1024, 4),
GB: Math.pow(1024, 3), GB: Math.pow(1024, 3),
MB: Math.pow(1024, 2), MB: Math.pow(1024, 2),
KB: 1024, KB: 1024,
}; };
export const getUnit = (value: number) => Object.entries(UNIT_MAP).find(([, size]) => value / size >= 1) || ['Byte', 1];
// 图表数字单位换算
export const DATA_NUMBER_MAP = { export const DATA_NUMBER_MAP = {
十亿: Math.pow(1000, 3), 十亿: Math.pow(1000, 3),
百万: Math.pow(1000, 2), 百万: Math.pow(1000, 2),
: 1000, : 1000,
}; };
export const getUnit = (value: number) => Object.entries(UNIT_MAP).find(([, size]) => value / size >= 1) || ['Byte', 1];
export const getDataNumberUnit = (value: number) => Object.entries(DATA_NUMBER_MAP).find(([, size]) => value / size >= 1) || ['', 1]; export const getDataNumberUnit = (value: number) => Object.entries(DATA_NUMBER_MAP).find(([, size]) => value / size >= 1) || ['', 1];
// 图表补点间隔计算
export const SUPPLEMENTARY_INTERVAL_MAP = {
'0': 60 * 1000,
// 6 小时10 分钟间隔
'21600000': 10 * 60 * 1000,
// 24 小时1 小时间隔
'86400000': 60 * 60 * 1000,
};
export const getSupplementaryInterval = (range: number) => {
return Object.entries(SUPPLEMENTARY_INTERVAL_MAP)
.reverse()
.find(([curRange]) => range > Number(curRange))[1];
};
// 处理图表排序
export const resolveMetricsRank = (metricList: MetricInfo[]) => {
const isRanked = metricList.some(({ rank }) => rank !== null);
let shouldUpdate = false;
let list: string[] = [];
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;
}
list = [...rankedMetrics.map(({ name }) => name), ...unRankedMetrics.map(({ name }) => name).sort()];
} else {
shouldUpdate = true;
// 按字母先后顺序初始化指标排序
list = metricList.map(({ name }) => name).sort();
}
return {
list,
listInfo: list.map((metric, rank) => ({ metric, rank, set: metricList.find(({ name }) => metric === name)?.set || false })),
shouldUpdate,
};
};
// 补点
export const supplementaryPoints = (
lines: MetricChartDataType['metricLines'],
timeRange: readonly [number, number],
extraCallback?: (point: [number, 0]) => any[]
): void => {
const interval = getSupplementaryInterval(timeRange[1] - timeRange[0]);
lines.forEach(({ data }) => {
// 获取未补点前线条的点的个数
let len = data.length;
// 记录当前处理到的点的下标值
let i = 0;
for (; i < len; i++) {
if (i === 0) {
let firstPointTimestamp = data[0][0] as number;
while (firstPointTimestamp - interval > timeRange[0]) {
const prevPointTimestamp = firstPointTimestamp - interval;
data.unshift(extraCallback ? extraCallback([prevPointTimestamp, 0]) : [prevPointTimestamp, 0]);
firstPointTimestamp = prevPointTimestamp;
len++;
i++;
}
}
if (i === len - 1) {
let lastPointTimestamp = data[i][0] as number;
while (lastPointTimestamp + interval < timeRange[1]) {
const nextPointTimestamp = lastPointTimestamp + interval;
data.push(extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
lastPointTimestamp = nextPointTimestamp;
}
break;
}
{
let timestamp = data[i][0] as number;
while (timestamp + interval < data[i + 1][0]) {
const nextPointTimestamp = timestamp + interval;
data.splice(i + 1, 0, extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
timestamp = nextPointTimestamp;
len++;
i++;
}
}
}
});
};
// 格式化图表数据
export const formatChartData = (
// 图表源数据
metricData: MetricDefaultChartDataType[],
// 获取指标单位
getMetricDefine: (type: MetricType, metric: string) => MetricsDefine[keyof MetricsDefine],
// 指标类型
metricType: MetricType,
// 图表时间范围,用于补点
timeRange: readonly [number, number],
transformUnit: [string, number] = undefined
): MetricChartDataType[] => {
return metricData.map(({ metricName, metricLines }) => {
const curMetricInfo = (getMetricDefine && getMetricDefine(metricType, metricName)) || null;
const isByteUnit = curMetricInfo?.unit?.toLowerCase().includes('byte');
let maxValue = -1;
const PointsMapMethod = ({ timeStamp, value }: { timeStamp: number; value: string | number }) => {
let parsedValue: string | number = Number(value);
if (Number.isNaN(parsedValue)) {
parsedValue = value;
} else {
// 为避免出现过小的数字影响图表展示效果,图表值统一保留小数点后三位
parsedValue = parseFloat(parsedValue.toFixed(3));
if (maxValue < parsedValue) maxValue = parsedValue;
}
return [timeStamp, parsedValue];
};
// 初始化返回结构
const chartData = {
metricName,
metricUnit: curMetricInfo?.unit || '',
metricLines: metricLines
.sort((a, b) => Number(a.name < b.name) - 0.5)
.map(({ name, metricPoints }) => ({
name,
data: metricPoints.map(PointsMapMethod),
})),
};
// 按时间先后进行对图表点排序
chartData.metricLines.forEach(({ data }) => data.sort((a, b) => (a[0] as number) - (b[0] as number)));
// 图表值单位转换
if (maxValue > 0) {
const [unitName, unitSize]: [string, number] = transformUnit || isByteUnit ? getUnit(maxValue) : getDataNumberUnit(maxValue);
chartData.metricUnit = isByteUnit
? chartData.metricUnit.toLowerCase().replace('byte', unitName)
: `${unitName}${chartData.metricUnit}`;
chartData.metricLines.forEach(({ data }) => data.forEach((point: any) => (point[1] /= unitSize)));
}
// 补点
supplementaryPoints(chartData.metricLines, timeRange);
return chartData;
});
};
// 图表 tooltip 基础展示样式 // 图表 tooltip 基础展示样式
const tooltipFormatter = (date: any, arr: any, tooltip: any) => { const tooltipFormatter = (date: any, arr: any, tooltip: any) => {
// 从大到小排序
// arr = arr.sort((a: any, b: any) => b.value - a.value);
const str = arr const str = arr
.map( .map(
(item: any) => `<div style="margin: 3px 0;"> (item: any) => `<div style="margin: 3px 0;">
@@ -121,9 +309,6 @@ export const getBasicChartConfig = (props: any = {}) => {
itemWidth: 8, itemWidth: 8,
itemGap: 8, itemGap: 8,
textStyle: { textStyle: {
// width: 85,
// overflow: 'truncate',
// ellipsis: '...',
fontSize: 11, fontSize: 11,
color: '#74788D', color: '#74788D',
}, },

View File

@@ -37,9 +37,6 @@ export const SMALL_DRAWER_WIDTH = 480;
export const MIDDLE_DRAWER_WIDTH = 728; export const MIDDLE_DRAWER_WIDTH = 728;
export const LARGE_DRAWER_WIDTH = 1080; export const LARGE_DRAWER_WIDTH = 1080;
// 小间隔1 分钟)图表点的最大请求时间范围,单位: ms
export const MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL = 6 * 60 * 60 * 1000;
export const primaryColor = '#556EE6'; export const primaryColor = '#556EE6';
export const numberToFixed = (value: number, num = 2) => { export const numberToFixed = (value: number, num = 2) => {
@@ -261,3 +258,6 @@ export const timeFormater = function formatDuring(mss: number) {
.map((o: any) => `${o.v}${o.unit}`) .map((o: any) => `${o.v}${o.unit}`)
.join(); .join();
}; };
// 列表页Header布局前缀
export const tableHeaderPrefix = 'table-header-layout';

View File

@@ -280,3 +280,38 @@ li {
line-height: 20px; line-height: 20px;
} }
} }
// Table Header 布局样式
.table-header-layout {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
&-left,
&-right {
display: flex;
align-items: center;
}
&-left {
&-refresh{
font-size: 20px;
color: #74788d;
cursor: pointer;
}
}
&-right{
&>*{
margin-left: 8px;
}
.search-input {
width: 248px;
}
}
&-divider{
height: 20px;
top: 0
}
}

View File

@@ -21,7 +21,6 @@ export default {
[`menu.${systemKey}.cluster`]: 'Cluster', [`menu.${systemKey}.cluster`]: 'Cluster',
[`menu.${systemKey}.cluster.overview`]: 'Overview', [`menu.${systemKey}.cluster.overview`]: 'Overview',
[`menu.${systemKey}.cluster.balance`]: 'Load Rebalance',
[`menu.${systemKey}.broker`]: 'Broker', [`menu.${systemKey}.broker`]: 'Broker',
[`menu.${systemKey}.broker.dashbord`]: 'Overview', [`menu.${systemKey}.broker.dashbord`]: 'Overview',
@@ -45,7 +44,7 @@ export default {
[`menu.${systemKey}.consumer-group.group-list`]: 'GroupList', [`menu.${systemKey}.consumer-group.group-list`]: 'GroupList',
[`menu.${systemKey}.operation`]: 'Operation', [`menu.${systemKey}.operation`]: 'Operation',
[`menu.${systemKey}.operation.balance`]: 'Load Rebalance', [`menu.${systemKey}.operation.balance`]: 'Rebalance',
[`menu.${systemKey}.operation.jobs`]: 'Job', [`menu.${systemKey}.operation.jobs`]: 'Job',
[`menu.${systemKey}.acls`]: 'ACLs', [`menu.${systemKey}.acls`]: 'ACLs',

View File

@@ -1,12 +1,14 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams, useHistory, useLocation } from 'react-router-dom'; import { useParams, useHistory, useLocation } from 'react-router-dom';
import { ProTable, Utils, AppContainer } from 'knowdesign'; import { ProTable, Utils, AppContainer, SearchInput } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '../../api'; import API from '../../api';
import { getControllerChangeLogListColumns, defaultPagination } from './config'; import { getControllerChangeLogListColumns, defaultPagination } from './config';
import BrokerDetail from '../BrokerDetail'; import BrokerDetail from '../BrokerDetail';
import BrokerHealthCheck from '@src/components/CardBar/BrokerHealthCheck'; import BrokerHealthCheck from '@src/components/CardBar/BrokerHealthCheck';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
import './index.less'; import './index.less';
import { tableHeaderPrefix } from '@src/constants/common';
const { request } = Utils; const { request } = Utils;
const ControllerChangeLogList: React.FC = (props: any) => { const ControllerChangeLogList: React.FC = (props: any) => {
@@ -89,26 +91,35 @@ const ControllerChangeLogList: React.FC = (props: any) => {
<BrokerHealthCheck /> <BrokerHealthCheck />
</div> </div>
<div className="clustom-table-content"> <div className="clustom-table-content">
<div className={tableHeaderPrefix}>
<div className={`${tableHeaderPrefix}-left`}>
<div
className={`${tableHeaderPrefix}-left-refresh`}
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
>
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
</div>
</div>
<div className={`${tableHeaderPrefix}-right`}>
<SearchInput
onSearch={setSearchKeywords}
attrs={{
placeholder: '请输入Broker Host',
style: { width: '248px', borderRiadus: '8px' },
maxLength: 128,
}}
/>
</div>
</div>
<ProTable <ProTable
showQueryForm={false} showQueryForm={false}
tableProps={{ tableProps={{
showHeader: true, showHeader: false,
rowKey: 'path', rowKey: 'path',
loading: loading, loading: loading,
columns: getControllerChangeLogListColumns(), columns: getControllerChangeLogListColumns(),
dataSource: data, dataSource: data,
paginationProps: { ...pagination }, paginationProps: { ...pagination },
tableHeaderSearchInput: {
// 搜索配置
submit: getSearchKeywords,
searchInputType: 'search',
searchAttr: {
placeholder: '请输入Broker Host',
style: {
width: '248px',
},
},
},
attrs: { attrs: {
onChange: onTableChange, onChange: onTableChange,
bordered: false, bordered: false,

View File

@@ -1,5 +1,6 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Drawer, Form, Input, Space, Button, Checkbox, Utils, Row, Col, IconFont, Divider, message } from 'knowdesign'; import { Drawer, Form, Input, Space, Button, Checkbox, Utils, Row, Col, Divider, message } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import Api from '@src/api'; import Api from '@src/api';
export const ConfigurationEdit = (props: any) => { export const ConfigurationEdit = (props: any) => {

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Utils, IconFont, Tooltip } from 'knowdesign'; import { Utils, Tooltip } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
export const getConfigurationColmns = (arg: any) => { export const getConfigurationColmns = (arg: any) => {
const columns: any = [ const columns: any = [
{ {

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect, memo } from 'react'; import React, { useState, useEffect, memo } from 'react';
import { useParams, useHistory, useLocation } from 'react-router-dom'; import { useParams, useHistory, useLocation } from 'react-router-dom';
import { ProTable, Drawer, Utils, AppContainer } from 'knowdesign'; import { ProTable, Drawer, Utils, AppContainer, SearchInput } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '../../api'; import API from '../../api';
import { getBrokerListColumns, defaultPagination } from './config'; import { getBrokerListColumns, defaultPagination } from './config';
import { dealTableRequestParams } from '../../constants/common'; import { tableHeaderPrefix } from '@src/constants/common';
import BrokerDetail from '../BrokerDetail'; import BrokerDetail from '../BrokerDetail';
import CardBar from '@src/components/CardBar'; import CardBar from '@src/components/CardBar';
import BrokerHealthCheck from '@src/components/CardBar/BrokerHealthCheck'; import BrokerHealthCheck from '@src/components/CardBar/BrokerHealthCheck';
@@ -33,7 +34,6 @@ const BrokerList: React.FC = (props: any) => {
if (urlParams?.clusterId === undefined) return; if (urlParams?.clusterId === undefined) return;
// filters = filters || filteredInfo; // filters = filters || filteredInfo;
setLoading(true); setLoading(true);
// const params = dealTableRequestParams({ searchKeywords, pageNo, pageSize });
const params = { const params = {
searchKeywords: searchKeywords.slice(0, 128), searchKeywords: searchKeywords.slice(0, 128),
pageNo, pageNo,
@@ -99,29 +99,36 @@ const BrokerList: React.FC = (props: any) => {
<BrokerHealthCheck /> <BrokerHealthCheck />
</div> </div>
<div className="clustom-table-content"> <div className="clustom-table-content">
<div className={tableHeaderPrefix}>
<div className={`${tableHeaderPrefix}-left`}>
<div
className={`${tableHeaderPrefix}-left-refresh`}
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
>
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
</div>
</div>
<div className={`${tableHeaderPrefix}-right`}>
<SearchInput
onSearch={setSearchKeywords}
attrs={{
placeholder: '请输入Broker Host',
style: { width: '248px', borderRiadus: '8px' },
maxLength: 128,
}}
/>
</div>
</div>
<ProTable <ProTable
key="brokerTable" key="brokerTable"
showQueryForm={false} showQueryForm={false}
tableProps={{ tableProps={{
showHeader: true, showHeader: false,
rowKey: 'broker_list', rowKey: 'broker_list',
loading: loading, loading: loading,
columns: getBrokerListColumns(), columns: getBrokerListColumns(),
dataSource: data, dataSource: data,
paginationProps: { ...pagination }, paginationProps: { ...pagination },
tableHeaderSearchInput: {
// 搜索配置
submit: getSearchKeywords,
searchInputType: 'search',
searchAttr: {
placeholder: '请输入Broker Host',
maxLength: 128,
style: {
width: '248px',
borderRiadus: '8px',
},
},
},
attrs: { attrs: {
onChange: onTableChange, onChange: onTableChange,
scroll: { x: 'max-content', y: 'calc(100vh - 400px)' }, scroll: { x: 'max-content', y: 'calc(100vh - 400px)' },

View File

@@ -1,15 +1,15 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import CopyToClipboard from 'react-copy-to-clipboard'; import { AppContainer, Divider, Drawer, ProTable, Select, SingleChart, Space, Tooltip, Utils } from 'knowdesign';
import { AppContainer, Divider, Drawer, IconFont, ProTable, Select, SingleChart, Space, Tooltip, Utils } from 'knowdesign'; import { IconFont } from '@knowdesign/icons';
import { DRangeTime } from 'knowdesign'; import { DRangeTime } from 'knowdesign';
import { CHART_COLOR_LIST, getBasicChartConfig } from '@src/constants/chartConfig'; import { CHART_COLOR_LIST, getBasicChartConfig } from '@src/constants/chartConfig';
import Api from '@src/api/index'; import Api from '@src/api/index';
import { hashDataParse } from '@src/constants/common'; import { hashDataParse } from '@src/constants/common';
import { ClustersPermissionMap } from '../CommonConfig'; import { ClustersPermissionMap } from '../CommonConfig';
import ResetOffsetDrawer from './ResetOffsetDrawer'; import ResetOffsetDrawer from './ResetOffsetDrawer';
import { CheckCircleFilled } from '@ant-design/icons';
import SwitchTab from '@src/components/SwitchTab'; import SwitchTab from '@src/components/SwitchTab';
import ContentWithCopy from '@src/components/CopyContent';
const { Option } = Select; const { Option } = Select;
@@ -44,33 +44,6 @@ const metricWithType = [
{ metricName: 'Lag', metricType: 102 }, { metricName: 'Lag', metricType: 102 },
]; ];
const ContentWithCopy = (props: { content: string }) => {
const { content } = props;
const [visible, setVisible] = useState(false);
return (
<CopyToClipboard text={content}>
<div className="content-with-copy">
<Tooltip title={content}>
<span className="content">{content}</span>
</Tooltip>
{content && (
<Tooltip
title={
<span>
<CheckCircleFilled style={{ color: '#00b365' }} />
</span>
}
visible={visible}
onVisibleChange={() => setVisible(false)}
>
<IconFont className="copy-icon" type="icon-fuzhi" onClick={() => setVisible(true)} />
</Tooltip>
)}
</div>
</CopyToClipboard>
);
};
export default (props: any) => { export default (props: any) => {
const { scene } = props; const { scene } = props;
const params = useParams<{ const params = useParams<{
@@ -104,6 +77,9 @@ export default (props: any) => {
title: 'Topic Partition', title: 'Topic Partition',
dataIndex: 'partitionId', dataIndex: 'partitionId',
key: 'partitionId', key: 'partitionId',
lineClampOne: true,
needTooltip: true,
width: 180,
render: (v: string, record: any) => { render: (v: string, record: any) => {
return `${record.topicName}-${v}`; return `${record.topicName}-${v}`;
}, },

View File

@@ -1,12 +1,8 @@
.operating-state { .operating-state {
.operation-bar { .consumers-search{
.left { display: contents;
.dcloud-form-item { .search-input-short{
margin-bottom: 0; margin-right: 8px;
}
.dcloud-form-item:first-of-type {
margin-right: 12px;
}
} }
} }
.pro-table-wrap { .pro-table-wrap {
@@ -53,6 +49,10 @@
align-items: center; align-items: center;
.d-range-time-input { .d-range-time-input {
height: 27px !important; height: 27px !important;
padding: 0 11px;
input{
line-height: 100%;
}
} }
.divider { .divider {
width: 1px; width: 1px;

View File

@@ -1,6 +1,7 @@
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { AppContainer, Form, Input, ProTable, Select, Utils } from 'knowdesign'; import { AppContainer, Divider, Form, Input, ProTable, Select, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import './index.less'; import './index.less';
import Api from '@src/api/index'; import Api from '@src/api/index';
import { getOperatingStateListParams } from './interface'; import { getOperatingStateListParams } from './interface';
@@ -8,7 +9,7 @@ import { useParams } from 'react-router-dom';
import ConsumerGroupDetail from './ConsumerGroupDetail'; import ConsumerGroupDetail from './ConsumerGroupDetail';
import ConsumerGroupHealthCheck from '@src/components/CardBar/ConsumerGroupHealthCheck'; import ConsumerGroupHealthCheck from '@src/components/CardBar/ConsumerGroupHealthCheck';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
import { hashDataParse } from '@src/constants/common'; import { hashDataParse, tableHeaderPrefix } from '@src/constants/common';
const { Option } = Select; const { Option } = Select;
@@ -181,17 +182,13 @@ const AutoPage = (props: any) => {
<div className={`operating-state ${scene !== 'topicDetail' && 'clustom-table-content'}`}> <div className={`operating-state ${scene !== 'topicDetail' && 'clustom-table-content'}`}>
{/* <CardBar cardColumns={data}></CardBar> */} {/* <CardBar cardColumns={data}></CardBar> */}
{scene !== 'topicDetail' && ( {scene !== 'topicDetail' && (
<div className="operation-bar"> <div className={tableHeaderPrefix}>
<div className="left"> <div className={`${tableHeaderPrefix}-left`}>
{/* <Radio.Group <div className={`${tableHeaderPrefix}-left-refresh`} onClick={() => searchFn()}>
options={showModes} <IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
optionType="button" </div>
onChange={(e) => { <Divider type="vertical" className={`${tableHeaderPrefix}-divider`} />
setShowMode(e.target.value); <div className="consumers-search">
}}
value={showMode}
/> */}
<Form.Item label="">
<Input <Input
className="search-input-short" className="search-input-short"
placeholder="请输入Consumer Group" placeholder="请输入Consumer Group"
@@ -201,8 +198,6 @@ const AutoPage = (props: any) => {
}} }}
onPressEnter={searchFn} onPressEnter={searchFn}
/> />
</Form.Item>
<Form.Item label="">
<Input <Input
className="search-input-short" className="search-input-short"
placeholder="请输入Topic name" placeholder="请输入Topic name"
@@ -212,12 +207,12 @@ const AutoPage = (props: any) => {
}} }}
onPressEnter={searchFn} onPressEnter={searchFn}
/> />
</Form.Item> </div>
{/* <Button type="primary" className="add-btn" onClick={searchFn}> {/* <Button type="primary" className="add-btn" onClick={searchFn}>
查询 查询
</Button> */} </Button> */}
</div> </div>
<div className="right"></div> {/* <div className="right"></div> */}
</div> </div>
)} )}
{/* <Table columns={columns} dataSource={consumerGroupList} scroll={{ x: 1500 }} /> {/* <Table columns={columns} dataSource={consumerGroupList} scroll={{ x: 1500 }} />

View File

@@ -13,8 +13,8 @@ interface PropsType {
} }
const typeObj: any = { const typeObj: any = {
1: '周期均衡', 1: '立即均衡',
2: '立即均衡', 2: '周期均衡',
}; };
const { request, post } = Utils; const { request, post } = Utils;

View File

@@ -1,6 +1,7 @@
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Alert, Badge, Dropdown, IconFont, ProTable, Space, Table, Utils } from 'knowdesign'; import { Alert, Badge, Dropdown, ProTable, Space, Table, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import Api from '@src/api'; import Api from '@src/api';
import { getTaskDetailsColumns, getMoveBalanceColumns } from './config'; import { getTaskDetailsColumns, getMoveBalanceColumns } from './config';

View File

@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Button, Drawer, Utils, Descriptions, Tabs, Input, IconFont, message, Spin, InputNumber } from 'knowdesign'; import { Button, Drawer, Utils, Descriptions, Tabs, Input, message, Spin, InputNumber } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import TaskDetails from './TeskDetails'; import TaskDetails from './TeskDetails';
import NodeTraffic from './NodeTraffic'; import NodeTraffic from './NodeTraffic';
import RebalancePlan from './RebalancePlan'; import RebalancePlan from './RebalancePlan';

View File

@@ -89,12 +89,9 @@ export const getJobsListColumns = (arg?: any) => {
title: '任务执行对象', title: '任务执行对象',
dataIndex: 'target', dataIndex: 'target',
key: 'target', key: 'target',
width: 232,
render(t: any, r: any) { render(t: any, r: any) {
return ( return <TagsWithHide placement="bottom" list={t.split(',')} expandTagContent={(num: any) => `共有${num}`} />;
<div style={{ width: '232px' }}>
<TagsWithHide placement="bottom" list={t.split(',')} expandTagContent={(num: any) => `共有${num}`} />
</div>
);
}, },
}, },
{ {

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, memo } from 'react'; import React, { useState, useEffect, memo } from 'react';
import { useParams, useHistory, useLocation } from 'react-router-dom'; import { useParams, useHistory, useLocation } from 'react-router-dom';
import { ProTable, Drawer, Utils, AppContainer, Form, Select, Input, Button, message, Modal } from 'knowdesign'; import { ProTable, Drawer, Utils, AppContainer, Form, Select, Input, Button, message, Modal, Divider } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '../../api'; import API from '../../api';
import { getJobsListColumns, defaultPagination, runningStatus, jobType } from './config'; import { getJobsListColumns, defaultPagination, runningStatus, jobType } from './config';
import JobsCheck from '@src/components/CardBar/JobsCheck'; import JobsCheck from '@src/components/CardBar/JobsCheck';
@@ -10,6 +11,7 @@ import './index.less';
import ReplicaChange from '@src/components/TopicJob/ReplicaChange'; import ReplicaChange from '@src/components/TopicJob/ReplicaChange';
import ReplicaMove from '@src/components/TopicJob/ReplicaMove'; import ReplicaMove from '@src/components/TopicJob/ReplicaMove';
import BalanceDrawer from '../LoadRebalance/BalanceDrawer'; import BalanceDrawer from '../LoadRebalance/BalanceDrawer';
import { tableHeaderPrefix } from '@src/constants/common';
const { request } = Utils; const { request } = Utils;
const JobsList: React.FC = (props: any) => { const JobsList: React.FC = (props: any) => {
@@ -171,35 +173,44 @@ const JobsList: React.FC = (props: any) => {
</div> </div>
{/* <Form form={form} layout="inline" onFinish={onFinish}> */} {/* <Form form={form} layout="inline" onFinish={onFinish}> */}
<div className="clustom-table-content"> <div className="clustom-table-content">
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '12px' }}> <div className={tableHeaderPrefix}>
<Form form={form} layout="inline" onFinish={onFinish}> <div className={`${tableHeaderPrefix}-left`}>
<Form.Item name="type"> <div
<Select options={jobType} style={{ width: '190px' }} className={'detail-table-select'} placeholder="选择任务类型" /> className={`${tableHeaderPrefix}-left-refresh`}
</Form.Item> onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
<Form.Item name="jobTarget"> >
<Input allowClear style={{ width: '190px' }} placeholder="请输入执行任务对象" /> <IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
</Form.Item> </div>
<Form.Item name="status"> <Divider type="vertical" className={`${tableHeaderPrefix}-divider`} />
<Select <Form form={form} layout="inline" onFinish={onFinish}>
mode="multiple" <Form.Item name="type">
maxTagCount={'responsive'} <Select options={jobType} style={{ width: '190px' }} className={'detail-table-select'} placeholder="选择任务类型" />
options={runningStatus} </Form.Item>
style={{ width: '190px' }} <Form.Item name="jobTarget">
className={'detail-table-select'} <Input allowClear style={{ width: '190px' }} placeholder="请输入执行任务对象" />
placeholder="选择运行状态" </Form.Item>
showArrow <Form.Item name="status">
allowClear <Select
/> mode="multiple"
</Form.Item> maxTagCount={'responsive'}
</Form> options={runningStatus}
<div> style={{ width: '190px' }}
<Form style={{ justifyContent: 'flex-end' }} form={form} layout="inline" onFinish={onFinish}> className={'detail-table-select'}
<Form.Item style={{ marginRight: 0 }}> placeholder="选择运行状态"
<Button type="primary" ghost htmlType="submit"> showArrow
allowClear
</Button> />
</Form.Item> </Form.Item>
</Form> </Form>
<div>
<Form style={{ justifyContent: 'flex-end' }} form={form} layout="inline" onFinish={onFinish}>
<Form.Item style={{ marginRight: 0 }}>
<Button type="primary" ghost htmlType="submit">
</Button>
</Form.Item>
</Form>
</div>
</div> </div>
</div> </div>
{/* </Form> */} {/* </Form> */}

View File

@@ -12,10 +12,9 @@ import {
Transfer, Transfer,
Select, Select,
message, message,
IconFont,
Tooltip, Tooltip,
} from 'knowdesign'; } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import CronInput from './CronInput'; import CronInput from './CronInput';
import BalanceEditTable from './BalanceEditTable'; import BalanceEditTable from './BalanceEditTable';
import PlanDrawer from './PlanDrawer'; import PlanDrawer from './PlanDrawer';

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Popover, IconFont, Row, Col, Select } from 'knowdesign'; import { Button, Popover, Row, Col, Select } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
const balancePrefix = 'custom-popover-balance'; const balancePrefix = 'custom-popover-balance';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Utils, Drawer, Button, Form, Space, Divider, AppContainer, Input, Transfer, message, IconFont, InputNumber } from 'knowdesign'; import { Utils, Drawer, Button, Form, Space, Divider, AppContainer, Input, Transfer, message, InputNumber } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import api from '../../api'; import api from '../../api';
import './style/BalanceDrawer.less'; import './style/BalanceDrawer.less';

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { message, Drawer, Button, Space, Divider, AppContainer, IconFont } from 'knowdesign'; import { message, Drawer, Button, Space, Divider, AppContainer } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import RebalancePlan from '../Jobs/RebalancePlan'; import RebalancePlan from '../Jobs/RebalancePlan';
interface PropsType extends React.HTMLAttributes<HTMLDivElement> { interface PropsType extends React.HTMLAttributes<HTMLDivElement> {

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Select, Form, Utils, AppContainer, Input, Button, ProTable, Badge, Tag, SearchInput } from 'knowdesign'; import { Select, Form, Utils, AppContainer, Input, Button, ProTable, Badge, Tag, SearchInput, Divider } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import BalanceDrawer from './BalanceDrawer'; import BalanceDrawer from './BalanceDrawer';
import HistoryDrawer from './HistoryDrawer'; import HistoryDrawer from './HistoryDrawer';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
@@ -9,6 +10,7 @@ import './index.less';
import LoadRebalanceCardBar from '@src/components/CardBar/LoadRebalanceCardBar'; import LoadRebalanceCardBar from '@src/components/CardBar/LoadRebalanceCardBar';
import { BalanceFilter } from './BalanceFilter'; import { BalanceFilter } from './BalanceFilter';
import { ClustersPermissionMap } from '../CommonConfig'; import { ClustersPermissionMap } from '../CommonConfig';
import { tableHeaderPrefix } from '@src/constants/common';
const Balance_Status_OPTIONS = [ const Balance_Status_OPTIONS = [
{ {
@@ -132,7 +134,7 @@ const LoadBalance: React.FC = (props: any) => {
key: 'disk_spec', key: 'disk_spec',
width: '150px', width: '150px',
render: (text: any, row: any) => { render: (text: any, row: any) => {
return text !== null ? `${text}GB` : '-'; return text !== null ? `${text.toLocaleString()}GB` : '-';
}, },
}, },
{ {
@@ -144,7 +146,10 @@ const LoadBalance: React.FC = (props: any) => {
return text !== null ? ( return text !== null ? (
<span> <span>
<Badge status={row?.disk_status === 0 ? 'success' : 'error'} /> <Badge status={row?.disk_status === 0 ? 'success' : 'error'} />
{`${getSizeAndUnit(text, 'B').valueWithUnit} (${((row.disk_avg * 100) / Utils.transGBToB(row.disk_spec)).toFixed(2)}%)`} {`${getSizeAndUnit(text, 'B').valueWithUnit.toLocaleString()} (${(
(row.disk_avg * 100) /
Utils.transGBToB(row.disk_spec)
).toFixed(2)}%)`}
</span> </span>
) : ( ) : (
'-' '-'
@@ -157,7 +162,7 @@ const LoadBalance: React.FC = (props: any) => {
key: 'bytesIn_spec', key: 'bytesIn_spec',
width: '150px', width: '150px',
render: (text: any, row: any) => { render: (text: any, row: any) => {
return text !== null ? `${text}MB/s` : '-'; return text !== null ? `${text.toLocaleString()}MB/s` : '-';
}, },
}, },
{ {
@@ -169,7 +174,10 @@ const LoadBalance: React.FC = (props: any) => {
return text !== null ? ( return text !== null ? (
<span> <span>
<Badge status={row?.bytesIn_status === 0 ? 'success' : 'error'} /> <Badge status={row?.bytesIn_status === 0 ? 'success' : 'error'} />
{`${getSizeAndUnit(text, 'B/s').valueWithUnit} (${((row.bytesIn_avg * 100) / (row.bytesIn_spec * 1024 * 1024)).toFixed(2)}%)`} {`${getSizeAndUnit(text, 'B/s').valueWithUnit.toLocaleString()} (${(
(row.bytesIn_avg * 100) /
(row.bytesIn_spec * 1024 * 1024)
).toFixed(2)}%)`}
</span> </span>
) : ( ) : (
'-' '-'
@@ -182,7 +190,7 @@ const LoadBalance: React.FC = (props: any) => {
key: 'bytesOut_spec', key: 'bytesOut_spec',
width: '150px', width: '150px',
render: (text: any, row: any) => { render: (text: any, row: any) => {
return text !== null ? `${text}MB/s` : '-'; return text !== null ? `${text.toLocaleString()}MB/s` : '-';
}, },
}, },
{ {
@@ -195,7 +203,10 @@ const LoadBalance: React.FC = (props: any) => {
return text !== null ? ( return text !== null ? (
<span> <span>
<Badge status={row?.bytesOut_status === 0 ? 'success' : 'error'} /> <Badge status={row?.bytesOut_status === 0 ? 'success' : 'error'} />
{`${getSizeAndUnit(text, 'B/s').valueWithUnit} (${((row.bytesOut_avg * 100) / (row.bytesOut_spec * 1024 * 1024)).toFixed(2)}%)`} {`${getSizeAndUnit(text, 'B/s').valueWithUnit.toLocaleString()} (${(
(row.bytesOut_avg * 100) /
(row.bytesOut_spec * 1024 * 1024)
).toFixed(2)}%)`}
</span> </span>
) : ( ) : (
'-' '-'
@@ -330,7 +341,7 @@ const LoadBalance: React.FC = (props: any) => {
breadcrumbs={[ breadcrumbs={[
{ label: '多集群管理', aHref: '/' }, { label: '多集群管理', aHref: '/' },
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` }, { label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
{ label: 'Load Rebalance', aHref: `` }, { label: 'Rebalance', aHref: `` },
]} ]}
/> />
</div> </div>
@@ -339,7 +350,17 @@ const LoadBalance: React.FC = (props: any) => {
</div> </div>
<div className="load-rebalance-container"> <div className="load-rebalance-container">
<div className="balance-main clustom-table-content"> <div className="balance-main clustom-table-content">
<div className="header-con"> <div className={tableHeaderPrefix}>
<div className={`${tableHeaderPrefix}-left`}>
<div
className={`${tableHeaderPrefix}-left-refresh`}
onClick={() => getList({ searchKeywords: searchValue, stateParam: balanceList })}
>
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
</div>
<Divider type="vertical" className={`${tableHeaderPrefix}-divider`} />
<BalanceFilter title="负载均衡列表筛选" data={[]} getNorms={getNorms} filterList={filterList} />
</div>
{/* <Form form={form} layout="inline" onFinish={resetList}> {/* <Form form={form} layout="inline" onFinish={resetList}>
<Form.Item name="status"> <Form.Item name="status">
<Select className="grid-select" placeholder="请选择状态" style={{ width: '180px' }} options={Balance_Status_OPTIONS} /> <Select className="grid-select" placeholder="请选择状态" style={{ width: '180px' }} options={Balance_Status_OPTIONS} />
@@ -354,8 +375,7 @@ const LoadBalance: React.FC = (props: any) => {
</Button> </Button>
</Form.Item> </Form.Item>
</Form> */} </Form> */}
<BalanceFilter title="负载均衡列表筛选" data={[]} getNorms={getNorms} filterList={filterList} /> <div className={`${tableHeaderPrefix}-right`}>
<div className="float-r">
<SearchInput <SearchInput
onSearch={hostSearch} onSearch={hostSearch}
attrs={{ attrs={{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 374 KiB

View File

@@ -6,9 +6,8 @@ import { regClusterName, regUsername } from '@src/constants/reg';
import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config'; import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config';
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem'; import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
const rows = 4; const LOW_KAFKA_VERSION = '2.8.0';
const lowKafkaVersion = '2.8.0'; const CLIENT_PROPERTIES_PLACEHOLDER = `用于创建Kafka客户端进行信息获取的相关配置
const clientPropertiesPlaceholder = `用于创建Kafka客户端进行信息获取的相关配置
例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置 例如开启SCRAM-SHA-256安全管控模式的集群需输入如下配置
未开启安全管控可不进行任何输入: 未开启安全管控可不进行任何输入:
{ {
@@ -26,37 +25,25 @@ const AccessClusters = (props: any): JSX.Element => {
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [curClusterInfo, setCurClusterInfo] = React.useState<any>({}); const [curClusterInfo, setCurClusterInfo] = React.useState<any>({});
const [security, setSecurity] = React.useState(curClusterInfo?.security || 'None');
const [extra, setExtra] = React.useState({ const [extra, setExtra] = React.useState({
versionExtra: '', versionExtra: '',
zooKeeperExtra: '', zooKeeperExtra: '',
bootstrapExtra: '', bootstrapExtra: '',
jmxExtra: '', jmxExtra: '',
}); });
const [isLowVersion, setIsLowVersion] = React.useState<boolean>(false);
const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState<boolean>(false);
const lastFormItemValue = React.useRef({ const lastFormItemValue = React.useRef({
bootstrap: curClusterInfo?.bootstrapServers || '', bootstrapServers: curClusterInfo?.bootstrapServers || '',
zookeeper: curClusterInfo?.zookeeper || '', zookeeper: curClusterInfo?.zookeeper || '',
clientProperties: curClusterInfo?.clientProperties || {}, clientProperties: curClusterInfo?.clientProperties || {},
}); });
const onHandleValuesChange = (value: any, allValues: any) => { const onHandleValuesChange = (changedValue: string[]) => {
Object.keys(value).forEach((key) => { Object.keys(changedValue).forEach((key) => {
switch (key) { switch (key) {
case 'security':
setSecurity(value.security);
break;
case 'zookeeper': case 'zookeeper':
setExtra({
...extra,
zooKeeperExtra: '',
bootstrapExtra: '',
jmxExtra: '',
});
break;
case 'bootstrapServers': case 'bootstrapServers':
setExtra({ setExtra({
...extra, ...extra,
@@ -78,21 +65,19 @@ const AccessClusters = (props: any): JSX.Element => {
const onCancel = () => { const onCancel = () => {
form.resetFields(); form.resetFields();
setLoading(false); setLoading(false);
setZookeeperErrorStatus(false);
setIsLowVersion(false);
setSecurity('None');
setExtra({ setExtra({
versionExtra: '', versionExtra: '',
zooKeeperExtra: '', zooKeeperExtra: '',
bootstrapExtra: '', bootstrapExtra: '',
jmxExtra: '', jmxExtra: '',
}); });
lastFormItemValue.current = { bootstrap: '', zookeeper: '', clientProperties: {} }; lastFormItemValue.current = { bootstrapServers: '', zookeeper: '', clientProperties: {} };
props.setVisible && props.setVisible(false); props.setVisible && props.setVisible(false);
}; };
const onSubmit = () => { const onSubmit = () => {
form.validateFields().then((res) => { form.validateFields().then((res) => {
setConfirmLoading(true);
let clientProperties = null; let clientProperties = null;
try { try {
clientProperties = res.clientProperties && JSON.parse(res.clientProperties); clientProperties = res.clientProperties && JSON.parse(res.clientProperties);
@@ -107,7 +92,7 @@ const AccessClusters = (props: any): JSX.Element => {
jmxProperties: { jmxProperties: {
jmxPort: res.jmxPort, jmxPort: res.jmxPort,
maxConn: res.maxConn, maxConn: res.maxConn,
openSSL: res.security === 'Password', openSSL: res.openSSL || false,
token: res.token, token: res.token,
username: res.username, username: res.username,
}, },
@@ -115,7 +100,7 @@ const AccessClusters = (props: any): JSX.Element => {
name: res.name, name: res.name,
zookeeper: res.zookeeper || '', zookeeper: res.zookeeper || '',
}; };
setLoading(true);
if (!isNaN(curClusterInfo?.id)) { if (!isNaN(curClusterInfo?.id)) {
Utils.put(api.phyCluster, { Utils.put(api.phyCluster, {
...params, ...params,
@@ -127,7 +112,7 @@ const AccessClusters = (props: any): JSX.Element => {
onCancel(); onCancel();
}) })
.finally(() => { .finally(() => {
setLoading(false); setConfirmLoading(false);
}); });
} else { } else {
Utils.post(api.phyCluster, params) Utils.post(api.phyCluster, params)
@@ -137,7 +122,7 @@ const AccessClusters = (props: any): JSX.Element => {
onCancel(); onCancel();
}) })
.finally(() => { .finally(() => {
setLoading(false); setConfirmLoading(false);
}); });
} }
}); });
@@ -154,125 +139,224 @@ const AccessClusters = (props: any): JSX.Element => {
} }
setLoading(true); setLoading(true);
setIsLowVersion(false);
setZookeeperErrorStatus(false);
return Utils.post(api.kafkaValidator, { return Utils.post(api.kafkaValidator, {
bootstrapServers: bootstrapServers || '', bootstrapServers: bootstrapServers || '',
zookeeper: zookeeper || '', zookeeper: zookeeper || '',
clientProperties, clientProperties,
}) })
.then((res: any) => { .then(
form.setFieldsValue({ (res: {
jmxPort: res.jmxPort, errList: { code: number; message: string; data: any }[];
}); jmxPort: number | null;
kafkaVersion: string | null;
zookeeper: string | null;
}) => {
const changedValue: { jmxPort?: number; kafkaVersion?: string; zookeeper: string } = {
zookeeper: zookeeper || res.zookeeper,
};
if (res.kafkaVersion && props.kafkaVersion.includes(res.kafkaVersion)) {
changedValue.kafkaVersion = res.kafkaVersion;
}
if (res.jmxPort) {
changedValue.jmxPort = res.jmxPort;
}
form.setFieldsValue(changedValue);
if (props.kafkaVersion.indexOf(res.kafkaVersion) > -1) { const extraMsg = {
form.setFieldsValue({ ...extra,
kafkaVersion: res.kafkaVersion, // 重置默认信息为连接成功
}); bootstrapExtra: bootstrapServers ? '连接成功' : '',
} else { zooKeeperExtra: zookeeper ? '连接成功' : '',
form.setFieldsValue({ };
kafkaVersion: undefined,
const errList = res.errList || [];
// 处理错误信息
errList.forEach((item: any) => {
const { code, message } = item;
let modifyKey: 'bootstrapExtra' | 'zooKeeperExtra' | 'jmxExtra' | undefined;
if (bootstrapServersErrCodes.includes(code)) {
modifyKey = 'bootstrapExtra';
} else if (zkErrCodes.includes(code)) {
modifyKey = 'zooKeeperExtra';
} else if (jmxErrCodes.includes(code)) {
modifyKey = 'jmxExtra';
}
if (modifyKey) {
extraMsg[modifyKey] = message;
}
}); });
setExtra(extraMsg);
return res;
} }
)
form.setFieldsValue({
zookeeper: zookeeper || res.zookeeper,
});
const errList = res.errList || [];
const extraMsg = extra;
// 初始化信息为连接成功
extraMsg.bootstrapExtra = bootstrapServers ? '连接成功' : '';
extraMsg.zooKeeperExtra = zookeeper ? '连接成功' : '';
// 处理错误信息
errList.forEach((item: any) => {
const { code, message } = item;
let modifyKey: 'bootstrapExtra' | 'zooKeeperExtra' | 'jmxExtra' | undefined;
if (bootstrapServersErrCodes.includes(code)) {
modifyKey = 'bootstrapExtra';
} else if (zkErrCodes.includes(code)) {
modifyKey = 'zooKeeperExtra';
} else if (jmxErrCodes.includes(code)) {
modifyKey = 'jmxExtra';
}
if (modifyKey) {
extraMsg[modifyKey] = `连接失败。${message}`;
}
});
// 如果kafkaVersion小于最低版本则提示
const showLowVersion = !(
curClusterInfo?.zookeeper ||
!curClusterInfo?.kafkaVersion ||
curClusterInfo?.kafkaVersion >= lowKafkaVersion
);
setIsLowVersion(showLowVersion);
setExtra({
...extraMsg,
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
});
return res;
})
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
}); });
}; };
// 更新表单状态
React.useEffect(() => { React.useEffect(() => {
const showLowVersion = !(curClusterInfo?.zookeeper || !curClusterInfo?.kafkaVersion || curClusterInfo?.kafkaVersion >= lowKafkaVersion);
lastFormItemValue.current = { lastFormItemValue.current = {
bootstrap: curClusterInfo?.bootstrapServers || '', bootstrapServers: curClusterInfo?.bootstrapServers || '',
zookeeper: curClusterInfo?.zookeeper || '', zookeeper: curClusterInfo?.zookeeper || '',
clientProperties: curClusterInfo?.clientProperties || {}, clientProperties: curClusterInfo?.clientProperties || {},
}; };
setIsLowVersion(showLowVersion);
setExtra({
...extra,
versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '',
});
form.setFieldsValue({ ...curClusterInfo }); form.setFieldsValue({ ...curClusterInfo });
if (curClusterInfo?.kafkaVersion) {
form.validateFields(['kafkaVersion']);
}
}, [curClusterInfo]); }, [curClusterInfo]);
// 获取集群详情数据
React.useEffect(() => { React.useEffect(() => {
if (visible) { if (visible) {
if (clusterInfo?.id) { if (clusterInfo?.id) {
setLoading(true); 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);
}
return res;
};
Utils.request(api.getPhyClusterBasic(clusterInfo.id)) Utils.request(api.getPhyClusterBasic(clusterInfo.id))
.then((res: any) => { .then((res: any) => {
let jmxProperties = null; setCurClusterInfo(resolveJmxProperties(res));
try {
jmxProperties = JSON.parse(res?.jmxProperties);
} catch (err) {
console.error(err);
}
// 转化值对应成表单值
if (jmxProperties?.openSSL) {
jmxProperties.security = 'Password';
}
if (jmxProperties) {
res = Object.assign({}, res || {}, jmxProperties);
}
setCurClusterInfo(res);
setLoading(false);
}) })
.catch((err) => { .catch((err) => {
setCurClusterInfo(clusterInfo); setCurClusterInfo(resolveJmxProperties(clusterInfo));
})
.finally(() => {
setLoading(false); setLoading(false);
}); });
} else { } else {
setCurClusterInfo(clusterInfo); setCurClusterInfo({});
} }
} }
}, [visible, clusterInfo]); }, [visible, clusterInfo]);
const validators = {
name: async (_: any, value: string) => {
if (!value) {
return Promise.reject('集群名称不能为空');
}
if (value === curClusterInfo?.name) {
return Promise.resolve();
}
if (value?.length > 128) {
return Promise.reject('集群名称长度限制在1128字符');
}
if (!new RegExp(regClusterName).test(value)) {
return Promise.reject('集群名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~');
}
return Utils.request(api.getClusterBasicExit(value))
.then((res: any) => {
const data = res || {};
return data?.exist ? Promise.reject('集群名称重复') : Promise.resolve();
})
.catch(() => Promise.reject('连接超时! 请重试或检查服务'));
},
bootstrapServers: async (_: any, value: string) => {
if (!value) {
return Promise.reject('Bootstrap Servers不能为空');
}
if (value.length > 2000) {
return Promise.reject('Bootstrap Servers长度限制在2000字符');
}
if (value && value !== lastFormItemValue.current.bootstrapServers) {
lastFormItemValue.current.bootstrapServers = value;
return connectTest().catch(() => (lastFormItemValue.current.bootstrapServers = ''));
}
return Promise.resolve('');
},
zookeeper: async (_: any, value: string) => {
if (!value) {
return Promise.resolve('');
}
if (value.length > 2000) {
return Promise.reject('Zookeeper长度限制在2000字符');
}
if (value && value !== lastFormItemValue.current.zookeeper) {
lastFormItemValue.current.zookeeper = value;
return connectTest().catch(() => (lastFormItemValue.current.zookeeper = ''));
}
return Promise.resolve('');
},
securityUserName: async (_: any, value: string) => {
if (!value) {
return Promise.reject('用户名不能为空');
}
if (!new RegExp(regUsername).test(value)) {
return Promise.reject('仅支持大小写、下划线、短划线(-');
}
if (value.length > 128) {
return Promise.reject('用户名长度限制在1128字符');
}
return Promise.resolve();
},
securityToken: async (_: any, value: string) => {
if (!value) {
return Promise.reject('密码不能为空');
}
if (!new RegExp(regUsername).test(value)) {
return Promise.reject('密码只能由大小写、下划线、短划线(-)组成');
}
if (value.length < 6 || value.length > 32) {
return Promise.reject('密码长度限制在632字符');
}
return Promise.resolve();
},
kafkaVersion: async (_: any, value: any) => {
if (!value) {
return Promise.reject('版本号不能为空');
}
// 检测版本号小于2.8.0如果没有填zookeeper信息才会提示
const zookeeper = form.getFieldValue('zookeeper');
let versionExtra = '';
if (value < LOW_KAFKA_VERSION && !zookeeper) {
versionExtra = intl.formatMessage({ id: 'access.cluster.low.version.tip' });
}
setExtra({
...extra,
versionExtra,
});
return Promise.resolve();
},
clientProperties: async (_: any, value: string) => {
try {
if (value) {
JSON.parse(value);
}
return Promise.resolve();
} catch (e) {
return Promise.reject(new Error('输入内容必须为 JSON'));
}
},
description: async (_: any, value: string) => {
if (!value) {
return Promise.resolve('');
}
if (value && value.length > 200) {
return Promise.reject('集群描述长度限制在200字符');
}
return Promise.resolve();
},
};
return ( return (
<> <>
<Drawer <Drawer
@@ -285,14 +369,14 @@ const AccessClusters = (props: any): JSX.Element => {
<Button size="small" onClick={onCancel}> <Button size="small" onClick={onCancel}>
</Button> </Button>
<Button size="small" type="primary" onClick={onSubmit}> <Button size="small" type="primary" loading={confirmLoading} onClick={onSubmit}>
</Button> </Button>
<Divider type="vertical" /> <Divider type="vertical" />
</Space> </Space>
</div> </div>
} }
title={intl.formatMessage({ id: props.title || 'access.cluster' })} title={intl.formatMessage({ id: props.title || clusterInfo?.id ? 'edit.cluster' : 'access.cluster' })}
visible={props.visible} visible={props.visible}
placement="right" placement="right"
width={480} width={480}
@@ -306,30 +390,7 @@ const AccessClusters = (props: any): JSX.Element => {
rules={[ rules={[
{ {
required: true, required: true,
validator: async (rule: any, value: string) => { validator: validators.name,
if (!value) {
return Promise.reject('集群名称不能为空');
}
if (value === curClusterInfo?.name) {
return Promise.resolve();
}
if (value?.length > 128) {
return Promise.reject('集群名称长度限制在1128字符');
}
if (!new RegExp(regClusterName).test(value)) {
return Promise.reject(
'集群名称支持中英文、数字、特殊字符 ! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~'
);
}
return Utils.request(api.getClusterBasicExit(value)).then((res: any) => {
const data = res || {};
if (data?.exist) {
return Promise.reject('集群名称重复');
} else {
return Promise.resolve();
}
});
},
}, },
]} ]}
> >
@@ -338,31 +399,12 @@ const AccessClusters = (props: any): JSX.Element => {
<Form.Item <Form.Item
name="bootstrapServers" name="bootstrapServers"
label="Bootstrap Servers" label="Bootstrap Servers"
extra={<span className={extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>} extra={<span className={!extra.bootstrapExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.bootstrapExtra}</span>}
validateTrigger={'onBlur'} validateTrigger={'onBlur'}
rules={[ rules={[
{ {
required: true, required: true,
validator: async (rule: any, value: string) => { validator: validators.bootstrapServers,
if (!value) {
return Promise.reject('Bootstrap Servers不能为空');
}
if (value.length > 2000) {
return Promise.reject('Bootstrap Servers长度限制在2000字符');
}
if (value && value !== lastFormItemValue.current.bootstrap) {
return connectTest()
.then((res: any) => {
lastFormItemValue.current.bootstrap = value;
return Promise.resolve('');
})
.catch((err) => {
return Promise.reject('连接失败');
});
}
return Promise.resolve('');
},
}, },
]} ]}
> >
@@ -374,36 +416,11 @@ const AccessClusters = (props: any): JSX.Element => {
<Form.Item <Form.Item
name="zookeeper" name="zookeeper"
label="Zookeeper" label="Zookeeper"
extra={<span className={extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>} extra={<span className={!extra.zooKeeperExtra.includes('连接成功') ? 'error-extra-info' : ''}>{extra.zooKeeperExtra}</span>}
validateStatus={zookeeperErrorStatus ? 'error' : 'success'}
validateTrigger={'onBlur'} validateTrigger={'onBlur'}
rules={[ rules={[
{ {
required: false, validator: validators.zookeeper,
validator: async (rule: any, value: string) => {
if (!value) {
setZookeeperErrorStatus(false);
return Promise.resolve('');
}
if (value.length > 2000) {
return Promise.reject('Zookeeper长度限制在2000字符');
}
if (value && value !== lastFormItemValue.current.zookeeper) {
return connectTest()
.then((res: any) => {
lastFormItemValue.current.zookeeper = value;
setZookeeperErrorStatus(false);
return Promise.resolve('');
})
.catch((err) => {
setZookeeperErrorStatus(true);
return Promise.reject('连接失败');
});
}
return Promise.resolve('');
},
}, },
]} ]}
> >
@@ -412,142 +429,65 @@ const AccessClusters = (props: any): JSX.Element => {
placeholder="请输入Zookeeper地址例如192.168.0.1:2181,192.168.0.2:2181,192.168.0.2:2181/ks-kafka" placeholder="请输入Zookeeper地址例如192.168.0.1:2181,192.168.0.2:2181,192.168.0.2:2181/ks-kafka"
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item className="metrics-form-item" label="Metrics">
className="no-item-control" <div className="horizontal-form-container">
name="Metrics" <div className="inline-items">
label="Metrics" <Form.Item name="jmxPort" label="JMX Port :" extra={extra.jmxExtra}>
rules={[ <InputNumber min={0} max={99999} style={{ width: 129 }} />
{ </Form.Item>
required: false, <Form.Item name="maxConn" label="Max Conn :">
message: '', <InputNumber addonAfter="个" min={0} max={99999} style={{ width: 124 }} />
}, </Form.Item>
]} </div>
> <Form.Item name="openSSL" label="Security :">
<></> <Radio.Group>
</Form.Item> <Radio value={false}>None</Radio>
<Form.Item <Radio value={true}>Password Authentication</Radio>
name="jmxPort" </Radio.Group>
label="JMX Port"
className="inline-item adjust-height-style"
extra={extra.jmxExtra}
rules={[
{
required: false,
message: '',
},
]}
>
<InputNumber style={{ width: 134 }} min={0} max={99999} />
</Form.Item>
<Form.Item
name="maxConn"
label="MaxConn"
className="inline-item adjust-height-style"
rules={[
{
required: false,
message: '',
},
]}
>
<InputNumber style={{ width: 134 }} min={0} max={99999} />
</Form.Item>
<Form.Item
name="security"
label="Security"
className="inline-item adjust-height-style"
rules={[
{
required: false,
message: '',
},
]}
>
<Radio.Group>
<Radio value="None">None</Radio>
<Radio value="Password">Password Authentication</Radio>
</Radio.Group>
</Form.Item>
{security === 'Password' ? (
<>
<Form.Item
className="inline-item max-width-66"
name="username"
label="User Info"
style={{ width: '58%' }}
rules={[
{
required: security === 'Password' || curClusterInfo?.security === 'Password',
validator: async (rule: any, value: string) => {
if (!value) {
return Promise.reject('用户名不能为空');
}
if (!new RegExp(regUsername).test(value)) {
return Promise.reject('仅支持大小写、下划线、短划线(-');
}
if (value.length > 128) {
return Promise.reject('用户名长度限制在1128字符');
}
return Promise.resolve();
},
},
]}
>
<Input placeholder="请输入用户名" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item dependencies={['openSSL']} noStyle>
className="inline-item" {({ getFieldValue }) => {
name="token" return getFieldValue('openSSL') ? (
label="" <div className="user-info-form-items">
style={{ width: '38%', marginRight: 0 }} <Form.Item className="user-info-label" label="User Info :" required />
rules={[ <div className="inline-items">
{ <Form.Item
required: security === 'Password' || curClusterInfo?.security === 'Password', name="username"
validator: async (rule: any, value: string) => { rules={[
if (!value) { {
return Promise.reject('密码不能为空'); validator: validators.securityUserName,
} },
if (!new RegExp(regUsername).test(value)) { ]}
return Promise.reject('密码只能由大小写、下划线、短划线(-)组成'); >
} <Input placeholder="请输入用户名" />
if (value.length < 6 || value.length > 32) { </Form.Item>
return Promise.reject('密码长度限制在632字符'); <Form.Item
} className="token-form-item"
return Promise.resolve(); name="token"
}, rules={[
}, {
]} validator: validators.securityToken,
> },
<Input placeholder="请输入密码" /> ]}
>
<Input placeholder="请输入密码" />
</Form.Item>
</div>
</div>
) : null;
}}
</Form.Item> </Form.Item>
</> </div>
) : null} </Form.Item>
<Form.Item <Form.Item
name="kafkaVersion" name="kafkaVersion"
label="Version" label="Version"
dependencies={['zookeeper']}
extra={<span className="error-extra-info">{extra.versionExtra}</span>} extra={<span className="error-extra-info">{extra.versionExtra}</span>}
validateStatus={isLowVersion ? 'error' : 'success'}
rules={[ rules={[
{ {
required: true, required: true,
validator: async (rule: any, value: any) => { validator: validators.kafkaVersion,
if (!value) {
setIsLowVersion(true);
return Promise.reject('版本号不能为空');
}
// 检测版本号小于2.8.0如果没有填zookeeper信息才会提示
const zookeeper = form.getFieldValue('zookeeper');
if (value < lowKafkaVersion && !zookeeper) {
setIsLowVersion(true);
setExtra({
...extra,
versionExtra: intl.formatMessage({ id: 'access.cluster.low.version.tip' }),
});
return Promise.resolve();
}
setIsLowVersion(false);
return Promise.resolve();
},
}, },
]} ]}
> >
@@ -565,29 +505,15 @@ const AccessClusters = (props: any): JSX.Element => {
label="集群配置" label="集群配置"
rules={[ rules={[
{ {
required: false, validator: validators.clientProperties,
message: '请输入集群配置',
}, },
() => ({
validator(_, value) {
try {
if (value) {
JSON.parse(value);
}
return Promise.resolve();
} catch (e) {
return Promise.reject(new Error('输入内容必须为 JSON'));
}
},
}),
]} ]}
> >
<div> <div>
<CodeMirrorFormItem <CodeMirrorFormItem
resize resize
defaultInput={form.getFieldValue('clientProperties')} defaultInput={form.getFieldValue('clientProperties')}
placeholder={clientPropertiesPlaceholder} placeholder={CLIENT_PROPERTIES_PLACEHOLDER}
onBeforeChange={(clientProperties: string) => { onBeforeChange={(clientProperties: string) => {
form.setFieldsValue({ clientProperties }); form.setFieldsValue({ clientProperties });
form.validateFields(['clientProperties']); form.validateFields(['clientProperties']);
@@ -621,20 +547,11 @@ const AccessClusters = (props: any): JSX.Element => {
label="集群描述" label="集群描述"
rules={[ rules={[
{ {
required: false, validator: validators.description,
validator: async (rule: any, value: string) => {
if (!value) {
return Promise.resolve('');
}
if (value && value.length > 200) {
return Promise.reject('集群描述长度限制在200字符');
}
return Promise.resolve();
},
}, },
]} ]}
> >
<Input.TextArea rows={rows} /> <Input.TextArea rows={4} />
</Form.Item> </Form.Item>
</Form> </Form>
</Spin> </Spin>

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, IconFont, AppContainer } from 'knowdesign'; import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '@src/api'; import API from '@src/api';
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide'; import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
import './index.less'; import './index.less';

View File

@@ -1,4 +1,5 @@
import { AppContainer, Divider, Form, IconFont, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign'; import { AppContainer, Divider, Form, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import moment from 'moment'; import moment from 'moment';
import API from '@src/api'; import API from '@src/api';
import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
@@ -16,6 +17,10 @@ import { SearchParams } from './HomePage';
const DEFAULT_PAGE_SIZE = 10; const DEFAULT_PAGE_SIZE = 10;
enum ClusterRunState {
Raft = 2,
}
const DeleteCluster = React.forwardRef((_, ref) => { const DeleteCluster = React.forwardRef((_, ref) => {
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -245,6 +250,7 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
metricPoints.push(line); metricPoints.push(line);
}); });
const runState = itemData.runState;
const { const {
Brokers: brokers, Brokers: brokers,
Zookeepers: zks, Zookeepers: zks,
@@ -345,18 +351,21 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
</div> </div>
<div className="indicator-left-item-value">{brokers}</div> <div className="indicator-left-item-value">{brokers}</div>
</div> </div>
<div className="indicator-left-item"> {/* 2: raft 模式 无zk */}
<div className="indicator-left-item-title"> {runState !== ClusterRunState.Raft && (
<span <div className="indicator-left-item">
className="indicator-left-item-title-dot" <div className="indicator-left-item-title">
style={{ <span
background: zookeepersAvailable === -1 ? '#e9e7e7' : zookeepersAvailable === 0 ? '#FF7066' : '#34C38F', className="indicator-left-item-title-dot"
}} style={{
></span> background: zookeepersAvailable === -1 ? '#e9e7e7' : zookeepersAvailable === 0 ? '#FF7066' : '#34C38F',
ZK }}
></span>
ZK
</div>
<div className="indicator-left-item-value">{zookeepersAvailable === -1 ? '-' : zks}</div>
</div> </div>
<div className="indicator-left-item-value">{zookeepersAvailable === -1 ? '-' : zks}</div> )}
</div>
</div> </div>
<div className="indicator-right"> <div className="indicator-right">
{metricPoints.map((row, index) => { {metricPoints.map((row, index) => {

View File

@@ -656,43 +656,37 @@
color: @error-color; color: @error-color;
} }
} }
.inline-item.dcloud-form-item { .horizontal-form-container {
display: -webkit-inline-box; padding-left: 16px;
margin-right: 16px; .inline-items {
display: flex;
&.adjust-height-style { justify-content: space-between;
.dcloud-form-item-label {
padding: 0;
label {
height: 36px;
}
}
.dcloud-form-item-control {
&-input {
height: 36px;
}
}
} }
.dcloud-form-item {
&.max-width-66 { flex-direction: row;
.dcloud-form-item-control { align-items: center;
max-width: 66%; &-label {
} padding: 0 12px 0 0;
} font-size: 13px;
.dcloud-form-item-label {
margin-right: 12px;
label {
font-family: @font-family; font-family: @font-family;
color: #74788d;
} }
} }
} .metrics-form-item {
margin-top: 8px;
.no-item-control { }
margin-bottom: 8px !important; .user-info-form-items {
.dcloud-form-item-control { display: flex;
display: none; align-items: flex-start;
.user-info-label {
padding-top: 4px;
}
.inline-items {
flex: 0 0 80%;
.token-form-item {
margin-left: 16px;
}
}
} }
} }
} }

View File

@@ -10,11 +10,6 @@
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.01), 0 3px 6px 3px rgba(0, 0, 0, 0.01), 0 2px 6px 0 rgba(0, 0, 0, 0.03); box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.01), 0 3px 6px 3px rgba(0, 0, 0, 0.01), 0 2px 6px 0 rgba(0, 0, 0, 0.03);
// border-radius: 12px; // border-radius: 12px;
} }
.operate-bar {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
}
} }
.acls-edit-drawer { .acls-edit-drawer {

View File

@@ -1,7 +1,9 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Button, Form, Input, Select, Modal, message, ProTable, AppContainer, DKSBreadcrumb, Utils } from 'knowdesign'; import { Button, Form, Input, Select, Modal, message, ProTable, AppContainer, DKSBreadcrumb, Utils, Divider } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import ACLsCardBar from '@src/components/CardBar/ACLsCardBar'; import ACLsCardBar from '@src/components/CardBar/ACLsCardBar';
import api from '@src/api'; import api from '@src/api';
import { tableHeaderPrefix } from '@src/constants/common';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import AddACLDrawer, { import AddACLDrawer, {
ACL_OPERATION, ACL_OPERATION,
@@ -205,37 +207,45 @@ const SecurityACLs = (): JSX.Element => {
<ACLsCardBar /> <ACLsCardBar />
</div> </div>
<div className="security-acls-page-list clustom-table-content"> <div className="security-acls-page-list clustom-table-content">
<div className="operate-bar"> <div className={tableHeaderPrefix}>
<Form form={form} layout="inline" onFinish={() => getACLs({ page: 1 })}> <div className={`${tableHeaderPrefix}-left`}>
<Form.Item name="kafkaUser"> <div className={`${tableHeaderPrefix}-left-refresh`} onClick={() => getACLs()}>
<Input placeholder="请输入 Principal" /> <IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
</Form.Item> </div>
<Form.Item name="resourceType"> <Divider type="vertical" className={`${tableHeaderPrefix}-divider`} />
<Select <Form form={form} layout="inline" onFinish={() => getACLs({ page: 1 })}>
placeholder="选择 ResourceType" <Form.Item name="kafkaUser">
options={Object.keys(RESOURCE_TO_OPERATIONS_MAP).map((key) => ({ label: key, value: key }))} <Input placeholder="请输入 Principal" />
mode="multiple" </Form.Item>
maxTagCount="responsive" <Form.Item name="resourceType">
allowClear <Select
style={{ width: 200 }} placeholder="选择 ResourceType"
/> options={Object.keys(RESOURCE_TO_OPERATIONS_MAP).map((key) => ({ label: key, value: key }))}
</Form.Item> mode="multiple"
<Form.Item name="resourceName"> maxTagCount="responsive"
<Input placeholder="请输入 Resource" /> allowClear
</Form.Item> style={{ width: 200 }}
<Form.Item> />
<Button type="primary" ghost htmlType="submit"> </Form.Item>
<Form.Item name="resourceName">
</Button> <Input placeholder="请输入 Resource" />
</Form.Item> </Form.Item>
</Form> <Form.Item>
<Button <Button type="primary" ghost htmlType="submit">
type="primary"
// icon={<PlusOutlined />} </Button>
onClick={() => editDrawerRef.current.onOpen(true, getACLs)} </Form.Item>
> </Form>
ACL </div>
</Button> <div className={`${tableHeaderPrefix}-right`}>
<Button
type="primary"
// icon={<PlusOutlined />}
onClick={() => editDrawerRef.current.onOpen(true, getACLs)}
>
ACL
</Button>
</div>
</div> </div>
<ProTable <ProTable
tableProps={{ tableProps={{

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