Compare commits

...

111 Commits

Author SHA1 Message Date
zengqiao
b8c07a966f bump version to v2.4.1 2021-05-18 21:00:20 +08:00
EricZeng
c6bcc0e3aa Merge pull request #297 from didi/dev
split op util controller to topic controller and leader controller, and add authority controller, quota controller
2021-05-18 20:21:18 +08:00
zengqiao
7719339f23 split op util controller to topic controller and leader controller, and add authority controller, quota controller 2021-05-18 20:18:08 +08:00
EricZeng
8ad64722ed Merge pull request #296 from tcf1207239873/dev
迭代优化2.4.1-配额调整
2021-05-18 19:37:58 +08:00
tangcongfa_v@didichuxing.com
611f8b8865 调整配额与权限 2021-05-18 18:25:03 +08:00
tangcongfa_v@didichuxing.com
38bdc173e8 Merge remote-tracking branch 'origin/dev' into dev 2021-05-18 16:07:40 +08:00
tangcongfa_v@didichuxing.com
52244325d9 权限调整 2021-05-18 16:03:24 +08:00
tcf1207239873
3fd3d99b8c Merge pull request #4 from didi/dev
Dev-0518-2
2021-05-18 15:45:55 +08:00
tangcongfa_v@didichuxing.com
d4ee5e91a2 配额调整 2021-05-18 14:49:00 +08:00
EricZeng
c2ad2d7238 Merge pull request #295 from didi/dev
简化sd配置枚举类的长度
2021-05-18 14:36:29 +08:00
zengqiao
892e195f0e code format 2021-05-18 14:33:02 +08:00
zengqiao
c5b1bed7dc 简化sd配置枚举类的长度 2021-05-18 14:13:23 +08:00
EricZeng
0e388d7aa7 Merge pull request #294 from didi/dev
增加登录绕过的接口调用方式
2021-05-18 11:48:55 +08:00
zengqiao
c3a0dbbe48 增加登录绕过的接口调用方式 2021-05-18 11:45:15 +08:00
tangcongfa_v@didichuxing.com
8b95b3ffc7 配额调整 2021-05-18 11:25:41 +08:00
tcf1207239873
42b78461cd Merge pull request #3 from didi/dev
Dev-0518
2021-05-18 10:30:57 +08:00
EricZeng
9190a41ca5 Merge pull request #293 from didi/dev
增加监控指标说明
2021-05-17 18:57:58 +08:00
zengqiao
28a7251319 增加监控指标说明 2021-05-17 15:31:08 +08:00
EricZeng
20565866ef Merge pull request #292 from didi/master
merge master
2021-05-17 15:20:50 +08:00
EricZeng
246f10aee5 Merge pull request #258 from Huyueeer/patch-2
更新Topic资源治理部分
2021-05-17 09:58:09 +08:00
EricZeng
960017280d 增加Topic资源治理的配置说明
增加Topic资源治理的配置说明
2021-05-17 09:57:56 +08:00
tangcongfa_v@didichuxing.com
7218aaf52e 权限调整 2021-05-13 17:49:42 +08:00
zengqiao
62050cc7b6 Merge branch 'master' of https://github.com/didi/Logi-KafkaManager 2021-05-13 16:22:16 +08:00
zengqiao
f88a14ac0a 补充说明为什么删除Topic成功之后, 又立马出现的原因 2021-05-13 16:22:05 +08:00
EricZeng
9286761c30 更新体验地址
更新体验地址
2021-05-13 10:26:07 +08:00
zengqiao
07c3273247 增加v2.4.0更新内容 2021-05-12 20:16:47 +08:00
tangcongfa_v@didichuxing.com
eb8fe77582 配额调整 2021-05-12 11:31:41 +08:00
tangcongfa_v@didichuxing.com
b68ba0bff6 配额调整 2021-05-11 16:58:44 +08:00
tangcongfa_v@didichuxing.com
696657c09e 处理冲突 2021-05-11 10:40:58 +08:00
tcf1207239873
12bea9b60a Merge pull request #1 from didi/dev
Dev
2021-05-11 10:28:02 +08:00
tangcongfa_v@didichuxing.com
9334e9552f 处理冲突 2021-05-11 10:25:25 +08:00
tangcongfa_v@didichuxing.com
a43b04a98b 处理冲突 2021-05-11 10:23:25 +08:00
zengqiao
f359ff995d faq中增加应用下线失败的说明 2021-05-11 08:41:37 +08:00
tangcongfa_v@didichuxing.com
9185d2646b 权限申请 2021-05-10 16:22:58 +08:00
EricZeng
33e61c762c Merge pull request #285 from didi/dev
bump commons-beanutils version to 1.9.4 & faq补充app的使用说明
2021-05-08 13:51:26 +08:00
zengqiao
e342e646ff 优化因DB时区不对导致周期任务不能被触发的日志 2021-05-08 13:50:04 +08:00
zengqiao
ed163a80e0 bump commons-beanutils version to 1.9.4 2021-05-08 11:45:27 +08:00
zengqiao
b390df08b5 faq补充app的使用说明 2021-05-08 11:28:52 +08:00
tangcongfa_v@didichuxing.com
f0b3b9f7f4 扩分区 2021-05-08 11:23:06 +08:00
EricZeng
a67d732507 Merge pull request #284 from didi/dev
faq中补充说明heartbeat表的作用及集群删除是否影响物理集群的说明
2021-05-08 11:20:51 +08:00
zengqiao
ca0ebe0d75 faq中补充说明heartbeat表的作用及集群删除是否影响物理集群的说明 2021-05-08 11:15:25 +08:00
EricZeng
94d113cbe0 Merge pull request #283 from didi/master
merge master
2021-05-08 11:07:53 +08:00
zengqiao
f1627b214c FAQ:新建告警组说明优化 2021-05-07 14:03:09 +08:00
EricZeng
d9265ec7ea Merge pull request #280 from didi/dev
连接信息写DB优化为批量写入
2021-05-07 13:52:43 +08:00
zengqiao
663e871bed 连接信息写DB优化为批量写入 2021-05-07 13:51:34 +08:00
shirenchuang
5c5eaddef7 readme add 社区km文章 2021-05-07 11:34:32 +08:00
shirenchuang
edaec4f1ae readme add 社区km文章 2021-05-07 11:34:18 +08:00
EricZeng
6d19acaa6c 更新FAQ,增加磁盘信息没有数据的说明
更新FAQ,增加磁盘信息没有数据的说明
2021-05-06 18:39:30 +08:00
EricZeng
d29a619fbf 优化页面无数据的FAQ
优化页面无数据的FAQ
2021-05-06 17:54:46 +08:00
EricZeng
b17808dd91 Merge pull request #275 from Huyueeer/master
修改maxMetricsSaveDays字段为Long类型
2021-04-30 18:48:54 +08:00
HuYueeer
c5321a3667 修改maxMetricsSaveDays字段为Long类型 2021-04-30 13:47:49 +08:00
EricZeng
8836691510 Merge pull request #273 from didi/dev
bump jetty-util version to 9.4.39.v20210325
2021-04-29 17:26:40 +08:00
zengqiao
6568f6525d bump jetty-util version to 9.4.39.v20210325 2021-04-29 17:25:08 +08:00
tangcongfa_v@didichuxing.com
473fc27b49 创建topic 2021-04-28 18:05:58 +08:00
EricZeng
74aeb55acb Merge pull request #266 from Liu-XinYuan/responsecode-fix
In case of authentication failure, add a clear response code
2021-04-28 17:25:55 +08:00
Liu-XinYuan
8efcf0529f In case of authentication failure, add a clear response code 2021-04-28 17:14:03 +08:00
EricZeng
06071c2f9c Merge pull request #264 from didi/dev_2.4.0
修复Topic工单选择Broker方式进行审批时展示的还是Region的问题
2021-04-28 14:19:26 +08:00
EricZeng
5eb4eca487 Merge pull request #263 from lucasun/master
修复Topic工单审批时,切换到Broker时展示还是Region的问题
2021-04-28 14:17:11 +08:00
孙超
33f6153e12 V2.4 bugfix 2021-04-28 14:09:54 +08:00
tangcongfa_v@didichuxing.com
df3283f526 删除topic 2021-04-28 11:22:53 +08:00
tangcongfa_v@didichuxing.com
b5901a2819 创建topic 2021-04-28 11:08:01 +08:00
EricZeng
6d5f1402fe Merge pull request #262 from didi/dev
增加App与Topic自动化审批开关
2021-04-28 10:51:12 +08:00
zengqiao
65e3782b2e 增加App与Topic自动化审批开关 2021-04-28 10:48:17 +08:00
EricZeng
135981dd30 Merge pull request #261 from didi/dev
开放接口集群ID开始调整, op对应的是物理集群ID, normal对应的是逻辑集群ID, 第一步, util接口调整
2021-04-28 10:15:42 +08:00
zengqiao
fe5cf2d922 开放接口集群ID开始调整, op对应的是物理集群ID, normal对应的是逻辑集群ID, 第一步, util接口调整 2021-04-28 10:10:16 +08:00
EricZeng
e15425cc2e 修复错别字
修复错别字
2021-04-27 17:36:44 +08:00
EricZeng
c3cb0a4e33 优化FAQ中告警组创建的说明
优化FAQ中告警组创建的说明
2021-04-27 17:30:18 +08:00
lucasun
cc32976bdd Merge pull request #259 from JokerQueue/master
用户管理页面,前端限制用户删除自己
2021-04-27 14:08:31 +08:00
Joker
bc08318716 用户管理页面,前端限制用户删除自己 2021-04-26 21:15:27 +08:00
tangcongfa_v@didichuxing.com
ee1ab30c2c 创建topic 2021-04-26 17:48:32 +08:00
HuYueeer
7fa1a66f7e 更新Topic资源治理部分 2021-04-26 14:51:12 +08:00
EricZeng
946bf37406 Merge pull request #257 from Huyueeer/patch-1
更新提问:heartbeat表相关
2021-04-26 14:27:10 +08:00
HuYueeer
8706f6931a Update faq.md
更新提问:heartbeat表相关
2021-04-26 14:15:32 +08:00
tangcongfa_v@didichuxing.com
f551674860 创建topic 2021-04-26 11:16:53 +08:00
EricZeng
d90fe0ef07 Merge pull request #253 from didi/master
merge master
2021-04-26 10:01:19 +08:00
EricZeng
bf979fa3b3 Merge pull request #252 from didi/dev_2.4.0
Dev 2.4.0
2021-04-26 09:55:30 +08:00
EricZeng
b3b88891e9 Merge pull request #251 from lucasun/master
v2.4.0
2021-04-25 21:01:33 +08:00
lucasun
01c5de60dc Merge branch 'master' into master 2021-04-25 20:54:10 +08:00
孙超
47b8fe5022 V2.4.1 FE 2021-04-25 20:43:20 +08:00
zengqiao
324b37b875 v2.4.0 be code 2021-04-25 18:11:52 +08:00
zengqiao
76e7e192d8 bump version to 2.4.0 2021-04-25 17:40:47 +08:00
EricZeng
f9f3c4d923 Merge pull request #240 from yangvipguang/docker-dev
Docker容器镜像优化
2021-04-25 17:23:36 +08:00
杨光
a476476bd1 Update Dockerfile
添加进程管理器tini 防止僵尸应用
升级基础镜像到Java 16 alpine 
默认使用官方jar 包
默认开启JMX 监控
2021-04-23 14:10:40 +08:00
杨光
82a60a884a Add files via upload 2021-04-23 14:06:55 +08:00
杨光
f17727de18 Merge pull request #1 from didi/master
同步提交
2021-04-23 11:31:35 +08:00
shirenchuang
f1f33c79f4 Merge branch 'shirc_dev' into dev 2021-04-23 10:26:43 +08:00
shirenchuang
d52eaafdbb 修正一下 用户手册中的 共享集群和独享集群的概念 2021-04-23 10:25:36 +08:00
shirenchuang
e7a3e50ed1 Merge branch 'shirc_dev' into dev 2021-04-23 10:20:01 +08:00
shirenchuang
2e09a87baa Merge remote-tracking branch 'origin/master' into shirc_dev 2021-04-23 10:18:30 +08:00
shirenchuang
b92ae7e47e 修正一个注释 2021-04-23 10:18:18 +08:00
EricZeng
f98446e139 Merge pull request #239 from Liu-XinYuan/i238
fix  create topic failed when not specify peak_byte_in
2021-04-22 19:02:48 +08:00
Liu-XinYuan
57a48dadaa modify from obj ==null to ValidateUtils.isNull 2021-04-22 18:44:34 +08:00
Liu-XinYuan
c65ec68e46 fix create topic failed when not specify peak_byte_in 2021-04-22 18:30:23 +08:00
zengqiao
d6559be3fc 部分后台任务获取Topic列表时不走缓存 2021-04-22 16:06:37 +08:00
shirenchuang
6fbf67f9a9 Merge branch 'dev' into shirc_dev 2021-04-22 14:13:32 +08:00
zengqiao
59df5b24fe broker元信息中增加Rack信息 2021-04-20 19:28:36 +08:00
zengqiao
3e1544294b 删除无效代码 2021-04-20 17:22:26 +08:00
EricZeng
a12c398816 Merge pull request #232 from didi/dev
应用下线功能权限列表获取优化
2021-04-20 13:54:49 +08:00
EricZeng
0bd3e28348 Merge pull request #228 from PengShuaixin/dev
应用下线审批功能优化
2021-04-20 13:51:32 +08:00
PengShuaixin
ad4e39c088 应用下线功能权限列表获取优化 2021-04-20 11:22:11 +08:00
PengShuaixin
2668d96e6a Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	kafka-manager-extends/kafka-manager-bpm/src/main/java/com/xiaojukeji/kafka/manager/bpm/order/impl/DeleteAppOrder.java
2021-04-20 11:19:49 +08:00
shirenchuang
357c496aad 下线应用的时候 判断先下线topic 2021-04-20 11:18:07 +08:00
shirenchuang
22a513ba22 升级mysql驱动;支持Mysql 8.0+ 2021-04-20 11:18:07 +08:00
zengqiao
e6dd1119be 通过获取类的RequestMapping注解来判断当前请求是否需要登录 2021-04-20 11:18:07 +08:00
EricZeng
2dbe454e04 Merge pull request #231 from didi/master
merge master
2021-04-20 10:46:03 +08:00
zengqiao
e3a59b76eb 修复数据删空之后, 缓存不能被更新的BUG 2021-04-19 20:31:40 +08:00
shirenchuang
01008acfcd 本地sql配置 2021-04-19 15:35:48 +08:00
shirenchuang
8bfde9fbaf Merge branch 'shirc_dev' into dev 2021-04-19 10:19:11 +08:00
shirenchuang
1fdecf8def 下线应用的时候 判断先下线topic 2021-04-19 10:17:29 +08:00
PengShuaixin
f41e29ab3a 应用下线功能优化 2021-04-14 12:29:59 +08:00
shirenchuang
5f1a839620 升级mysql驱动;支持Mysql 8.0+ 2021-04-06 12:09:52 +08:00
136 changed files with 1766 additions and 653 deletions

View File

@@ -14,7 +14,8 @@
滴滴Logi-KafkaManager脱胎于滴滴内部多年的Kafka运营实践经验是面向Kafka用户、Kafka运维人员打造的共享多租户Kafka云平台。专注于Kafka运维管控、监控告警、资源治理等核心场景经历过大规模集群、海量大数据的考验。内部满意度高达90%的同时,还与多家知名企业达成商业化合作。 滴滴Logi-KafkaManager脱胎于滴滴内部多年的Kafka运营实践经验是面向Kafka用户、Kafka运维人员打造的共享多租户Kafka云平台。专注于Kafka运维管控、监控告警、资源治理等核心场景经历过大规模集群、海量大数据的考验。内部满意度高达90%的同时,还与多家知名企业达成商业化合作。
### 1.1 快速体验地址 ### 1.1 快速体验地址
- 体验地址 http://117.51.146.109:8080 账号密码 admin/admin
- 体验地址 http://117.51.150.133:8080 账号密码 admin/admin
### 1.2 体验地图 ### 1.2 体验地图
相比较于同类产品的用户视角单一大多为管理员视角滴滴Logi-KafkaManager建立了基于分角色、多场景视角的体验地图。分别是**用户体验地图、运维体验地图、运营体验地图** 相比较于同类产品的用户视角单一大多为管理员视角滴滴Logi-KafkaManager建立了基于分角色、多场景视角的体验地图。分别是**用户体验地图、运维体验地图、运营体验地图**
@@ -66,6 +67,7 @@
- [滴滴Logi-KafkaManager 开源之路](https://xie.infoq.cn/article/0223091a99e697412073c0d64) - [滴滴Logi-KafkaManager 开源之路](https://xie.infoq.cn/article/0223091a99e697412073c0d64)
- [滴滴Logi-KafkaManager 系列视频教程](https://mp.weixin.qq.com/s/9X7gH0tptHPtfjPPSdGO8g) - [滴滴Logi-KafkaManager 系列视频教程](https://mp.weixin.qq.com/s/9X7gH0tptHPtfjPPSdGO8g)
- [kafka实践十五滴滴开源Kafka管控平台 Logi-KafkaManager研究--A叶子叶来](https://blog.csdn.net/yezonggang/article/details/113106244) - [kafka实践十五滴滴开源Kafka管控平台 Logi-KafkaManager研究--A叶子叶来](https://blog.csdn.net/yezonggang/article/details/113106244)
- [kafka的灵魂伴侣Logi-KafkaManager系列文章专栏 --石臻](https://blog.csdn.net/u010634066/category_10977588.html)
## 3 滴滴Logi开源用户交流群 ## 3 滴滴Logi开源用户交流群

View File

@@ -7,6 +7,33 @@
--- ---
## v2.4.0
版本上线时间2021-04-26
### 能力提升
- 增加App与Topic自动化审批开关
- Broker元信息中增加Rack信息
- 升级MySQL 驱动支持MySQL 8+
- 增加操作记录查询界面
### 体验优化
- FAQ告警组说明优化
- 用户手册共享及 独享集群概念优化
- 用户管理界面,前端限制用户删除自己
### bug修复
- 修复op-util类中创建Topic失败的接口
- 周期同步Topic到DB的任务修复将Topic列表查询从缓存调整为直接查DB
- 应用下线审批失败的功能修复将权限为0(无权限)的数据进行过滤
- 修复登录及权限绕过的漏洞
- 修复研发角色展示接入集群、暂停监控等按钮的问题
## v2.3.0 ## v2.3.0
版本上线时间2021-02-08 版本上线时间2021-02-08

View File

@@ -4,7 +4,7 @@ cd $workspace
## constant ## constant
OUTPUT_DIR=./output OUTPUT_DIR=./output
KM_VERSION=2.3.1 KM_VERSION=2.4.1
APP_NAME=kafka-manager APP_NAME=kafka-manager
APP_DIR=${APP_NAME}-${KM_VERSION} APP_DIR=${APP_NAME}-${KM_VERSION}

View File

@@ -1,11 +1,8 @@
FROM openjdk:8-jdk-alpine3.9 FROM openjdk:16-jdk-alpine3.13
LABEL author="yangvipguang" LABEL author="yangvipguang"
ENV VERSION 2.1.0 ENV VERSION 2.3.1
ENV JAR_PATH kafka-manager-web/target
COPY $JAR_PATH/kafka-manager-web-$VERSION-SNAPSHOT.jar /tmp/app.jar
COPY $JAR_PATH/application.yml /km/
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --no-cache --virtual .build-deps \ RUN apk add --no-cache --virtual .build-deps \
@@ -19,26 +16,28 @@ RUN apk add --no-cache --virtual .build-deps \
tomcat-native \ tomcat-native \
&& apk del .build-deps && apk del .build-deps
RUN apk add --no-cache tini
ENV AGENT_HOME /opt/agent/ ENV AGENT_HOME /opt/agent/
WORKDIR /tmp WORKDIR /tmp
COPY $JAR_PATH/kafka-manager.jar app.jar
# COPY application.yml application.yml ##默认使用helm 挂载,防止敏感配置泄露
COPY docker-depends/config.yaml $AGENT_HOME COPY docker-depends/config.yaml $AGENT_HOME
COPY docker-depends/jmx_prometheus_javaagent-0.14.0.jar $AGENT_HOME COPY docker-depends/jmx_prometheus_javaagent-0.15.0.jar $AGENT_HOME
ENV JAVA_AGENT="-javaagent:$AGENT_HOME/jmx_prometheus_javaagent-0.14.0.jar=9999:$AGENT_HOME/config.yaml"
ENV JAVA_AGENT="-javaagent:$AGENT_HOME/jmx_prometheus_javaagent-0.15.0.jar=9999:$AGENT_HOME/config.yaml"
ENV JAVA_HEAP_OPTS="-Xms1024M -Xmx1024M -Xmn100M " ENV JAVA_HEAP_OPTS="-Xms1024M -Xmx1024M -Xmn100M "
ENV JAVA_OPTS="-verbose:gc \ ENV JAVA_OPTS="-verbose:gc \
-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:/tmp/gc.log -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps \
-XX:MaxMetaspaceSize=256M -XX:+DisableExplicitGC -XX:+UseStringDeduplication \ -XX:MaxMetaspaceSize=256M -XX:+DisableExplicitGC -XX:+UseStringDeduplication \
-XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:-UseContainerSupport" -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:-UseContainerSupport"
#-Xlog:gc -Xlog:gc* -Xlog:gc+heap=trace -Xlog:safepoint
EXPOSE 8080 9999 EXPOSE 8080 9999
ENTRYPOINT ["sh","-c","java -jar $JAVA_HEAP_OPTS $JAVA_OPTS /tmp/app.jar --spring.config.location=/km/application.yml"] ENTRYPOINT ["tini", "--"]
## 默认不带Prometheus JMX监控需要可以自行取消以下注释并注释上面一行默认Entrypoint 命令。
## ENTRYPOINT ["sh","-c","java -jar $JAVA_AGENT $JAVA_HEAP_OPTS $JAVA_OPTS /tmp/app.jar --spring.config.location=/km/application.yml"]
CMD ["sh","-c","java -jar $JAVA_AGENT $JAVA_HEAP_OPTS $JAVA_OPTS app.jar --spring.config.location=application.yml"]

View File

@@ -120,3 +120,23 @@ TOPIC_INSUFFICIENT_PARTITION_CONFIG
] ]
} }
``` ```
## 4、专家服务——Topic资源治理
首先我们认为在一定的时间长度内Topic的分区offset没有任何变化的Topic即没有数据写入的Topic为过期的Topic。
Topic分区不足相关的动态配置(页面在运维管控->平台管理->配置管理)
配置Key
```
EXPIRED_TOPIC_CONFIG
```
配置Value
```json
{
"minExpiredDay": 30, #过期时间大于此值才显示
"ignoreClusterIdList": [ # 忽略的集群
50
]
}
```

View File

@@ -210,11 +210,11 @@ CREATE TABLE `gateway_config` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uniq_type_name` (`type`,`name`) UNIQUE KEY `uniq_type_name` (`type`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='gateway配置'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='gateway配置';
INSERT INTO gateway_config(type, name, value, `version`) values('SERVICE_DISCOVERY_QUEUE_SIZE', 'SERVICE_DISCOVERY_QUEUE_SIZE', 100000000, 1); INSERT INTO gateway_config(type, name, value, `version`, `description`) values('SD_QUEUE_SIZE', 'SD_QUEUE_SIZE', 100000000, 1, '任意集群队列大小');
INSERT INTO gateway_config(type, name, value, `version`) values('SERVICE_DISCOVERY_APPID_RATE', 'SERVICE_DISCOVERY_APPID_RATE', 100000000, 1); INSERT INTO gateway_config(type, name, value, `version`, `description`) values('SD_APP_RATE', 'SD_APP_RATE', 100000000, 1, '任意一个App限速');
INSERT INTO gateway_config(type, name, value, `version`) values('SERVICE_DISCOVERY_IP_RATE', 'SERVICE_DISCOVERY_IP_RATE', 100000000, 1); INSERT INTO gateway_config(type, name, value, `version`, `description`) values('SD_IP_RATE', 'SD_IP_RATE', 100000000, 1, '任意一个IP限速');
INSERT INTO gateway_config(type, name, value, `version`) values('SERVICE_DISCOVERY_SP_RATE', 'app_01234567', 100000000, 1); INSERT INTO gateway_config(type, name, value, `version`, `description`) values('SD_SP_RATE', 'app_01234567', 100000000, 1, '指定App限速');
INSERT INTO gateway_config(type, name, value, `version`) values('SERVICE_DISCOVERY_SP_RATE', '192.168.0.1', 100000000, 1); INSERT INTO gateway_config(type, name, value, `version`, `description`) values('SD_SP_RATE', '192.168.0.1', 100000000, 1, '指定IP限速');
-- --
-- Table structure for table `heartbeat` -- Table structure for table `heartbeat`

View File

@@ -0,0 +1,49 @@
---
![kafka-manager-logo](../assets/images/common/logo_name.png)
**一站式`Apache Kafka`集群指标监控与运维管控平台**
---
# 登录绕过
## 背景
现在除了开放出来的第三方接口,其他接口都需要走登录认证。
但是第三方接口不多,开放出来的能力有限,但是登录的接口又需要登录,非常的麻烦。
因此,新增了一个登录绕过的功能,为一些紧急临时的需求,提供一个调用不需要登录的能力。
## 使用方式
步骤一接口调用时在header中增加如下信息
```shell
# 表示开启登录绕过
Trick-Login-Switch : on
# 登录绕过的用户, 这里可以是admin, 或者是其他的, 但是必须在运维管控->平台管理->用户管理中设置了该用户。
Trick-Login-User : admin
```
 
步骤二:在运维管控->平台管理->平台配置上,设置允许了该用户以绕过的方式登录
```shell
# 设置的key必须是这个
SECURITY.TRICK_USERS
# 设置的value是json数组的格式例如
[ "admin", "logi"]
```
 
步骤三:解释说明
设置完成上面两步之后,就可以直接调用需要登录的接口了。
但是还有一点需要注意,绕过的用户仅能调用他有权限的接口,比如一个普通用户,那么他就只能调用普通的接口,不能去调用运维人员的接口。

View File

@@ -20,8 +20,14 @@
- 8、`topic biz data not exist`错误及处理方式 - 8、`topic biz data not exist`错误及处理方式
- 9、进程启动后如何查看API文档 - 9、进程启动后如何查看API文档
- 10、如何创建告警组 - 10、如何创建告警组
- 11、连接信息、耗时信息为什么没有数据 - 11、连接信息、耗时信息、磁盘信息为什么没有数据?
- 12、逻辑集群申请审批通过之后为什么看不到逻辑集群 - 12、逻辑集群申请审批通过之后为什么看不到逻辑集群
- 13、heartbeat表关联业务和使用场景是什么
- 14、集群的删除是否会真正的删除集群
- 15、APP(应用)如何被使用起来?
- 16、为什么下线应用提示operation forbidden
- 17、删除Topic成功为什么过一会儿之后又出现了
- 18、如何在不登录的情况下调用一些需要登录的接口
--- ---
@@ -70,7 +76,7 @@
- 3、数据库时区问题。 - 3、数据库时区问题。
检查MySQL的topic_metrics表查看是否有数据如果有数据那么再检查设置的时区是否正确 检查MySQL的cluster表的gmt_modify字段做一个update动作看这个gmt_modify时间是否是当前时间如果不是那么就是时区问题了。时区不对问题具体可以搜索一下看如何解决
--- ---
@@ -113,11 +119,14 @@
### 10、如何创建告警组 ### 10、如何创建告警组
这块需要配合监控系统进行使用,现在默认已经实现了夜莺的对接当然也可以对接自己内部的监控系统,不过需要实现一些接口。 告警组的创建需要到Logi-KM对接的监控系统中创建比如我们现在默认是对接了夜莺那么告警组需要到夜莺中创建如果没有安装夜莺那么需要安装一下夜莺并进行对接当然,这里也可以对接自己内部的监控系统,不过需要实现一些接口。
具体的文档可见:[监控功能对接夜莺](../dev_guide/monitor_system_integrate_with_n9e.md)、[监控功能对接其他系统](../dev_guide/monitor_system_integrate_with_self.md) 具体的文档可见:[监控功能对接夜莺](../dev_guide/monitor_system_integrate_with_n9e.md)、[监控功能对接其他系统](../dev_guide/monitor_system_integrate_with_self.md)
### 11、连接信息、耗时信息为什么没有数据 那么在夜莺中,如何创建告警组呢
需要前往夜莺平台-用户资源中心-团队管理中新建团队。新建过团队之后再次回到Logi-KM中刷新页面就可以在该下拉框中选择告警接收组了。
### 11、连接信息、耗时信息、磁盘信息为什么没有数据
这块需要结合滴滴内部的kafka-gateway一同使用才会有数据滴滴kafka-gateway暂未开源。 这块需要结合滴滴内部的kafka-gateway一同使用才会有数据滴滴kafka-gateway暂未开源。
@@ -126,3 +135,68 @@
逻辑集群的申请与审批仅仅只是一个工单流程,并不会去实际创建逻辑集群,逻辑集群的创建还需要手动去创建。 逻辑集群的申请与审批仅仅只是一个工单流程,并不会去实际创建逻辑集群,逻辑集群的创建还需要手动去创建。
具体的操作可见:[kafka-manager 接入集群](add_cluster/add_cluster.md)。 具体的操作可见:[kafka-manager 接入集群](add_cluster/add_cluster.md)。
### 13、heartbeat表关联业务和使用场景是什么
做任务抢占用的。
KM支持HA的方式部署那么部署多台的时候就会出现每一台都可能去做指标收集的事情这块就使用heartbeat表做KM的存活性判断然后进行任务的抢占或者是均衡。
更多详细的内容可以看一下源码中heartbeat表在哪里被使用了。
### 14、集群的删除是否会真正的删除集群
Logi-KM的运维管控集群列表中的集群删除仅仅只是将该集群从Logi-KM中进行删除并不会对真正的物理集群做什么操作。
### 15、APP(应用)如何被使用起来?
app在Logi-KM中可以近似理解为租户或者是kafka里面的一个账号的概念。
界面中显示的app信息、权限信息等在平台层面仅仅只是控制Topic或集群在平台上的可见性如果使用的是社区版本的Kafka那么实际上是不能真正的管控到客户端对Topic的生产和消费。
但是如果是使用的滴滴的Kafka-Gateway那么是可以做到对客户端的生产和消费的权限管控。滴滴的Kafka-Gateway暂未开源属于企业服务具体的可以入群交流群地址在README中。
### 16、为什么下线应用提示operation forbidden
**原因一:**
该应用还存在对Topic的权限因此导致下线失败。具体查看的位置在"Topic管理-》应用管理-》详情"可以看到应用对哪些Topic还有权限。
只有当权限全部去除之后,才可以下线应用。
**原因二:**
使用的是2.4.0之前的旧版本旧版本存在缓存更新的BUG建议升级至最新的版本或者简单粗暴的就是重启一下KM。
### 17、删除Topic成功为什么过一会儿之后又出现了
**原因说明:**
Logi-KM会去请求Topic的endoffset信息要获取这个信息就需要发送metadata请求发送metadata请求的时候如果集群允许自动创建Topic那么当Topic不存在时就会自动将该Topic创建出来。
**问题解决:**
因为在Logi-KM上禁止Kafka客户端内部元信息获取这个动作非常的难做到因此短时间内这个问题不好从Logi-KM上解决。
当然对于不存在的TopicLogi-KM是不会进行元信息请求的因此也不用担心会莫名其妙的创建一个Topic出来。
但是另外一点对于开启允许Topic自动创建的集群建议是关闭该功能开启是非常危险的如果关闭之后Logi-KM也不会有这个问题。
最后这里举个开启这个配置后,非常危险的代码例子吧:
```java
for (int i= 0; i < 100000; ++i) {
// 如果是客户端类似这样写的那么一启动那么将创建10万个Topic出来集群元信息瞬间爆炸controller可能就不可服务了。
producer.send(new ProducerRecord<String, String>("logi_km" + i,"hello logi_km"));
}
```
### 18、如何在不登录的情况下调用一些需要登录的接口
具体见:[登录绕过](./call_api_bypass_login.md)

View File

@@ -4,11 +4,16 @@
--- ---
## 报警策略-监控指标说明
| 指标 | 含义 |备注 |
| --- | --- | --- |
| online-kafka-consumer-lag | 消费时按照分区的维度进行监控lag数 | lag表示有多少数据没有被消费因为按照分区的维度监控所以告警时一般会有分区信息 |
| online-kafka-consumer-maxLag | 消费时按照整个Topic的维度监控Topic所有的分区里面的那个最大的lag | 比如每个分区的lag分别是3、5、7那么maxLag的值就是max(3,5,7)=7 |
| online-kafka-consumer-maxDelayTime | 消费时按照Topic维度监控预计的消费延迟 | 这块是按照lag和messagesIn之间的关系计算出来的可能会有误差 |
## 报警策略-报警函数介绍 ## 报警策略-报警函数介绍
| 类别 | 函数 | 含义 |函数文案 |备注 | | 类别 | 函数 | 含义 |函数文案 |备注 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| 发生次数 |alln | 最近$n个周期内全发生 | 连续发生(all) | | | 发生次数 |alln | 最近$n个周期内全发生 | 连续发生(all) | |

View File

@@ -609,10 +609,10 @@ Lag表示该消费客户端是否有堆积等于 partition offset-consume
集群类型:选择创建的集群为“独享”还是“独立”。 集群类型:选择创建的集群为“独享”还是“独立”。
* 独享集群意味着,您独自拥有一个 * 独享集群意味着, 您拥有一个集群中部分broker的使用权限。
集群;
* 独立集群意味着,您拥有一个集群中部分broker的使用权限。 * 独立集群意味着,您独自拥有一个
物理集群;
* <font color = red>共享集群意味着大家共用一个集群及其中broker。</font> * <font color = red>共享集群意味着大家共用一个集群及其中broker。</font>

View File

@@ -1,19 +0,0 @@
package com.xiaojukeji.kafka.manager.common.bizenum;
/**
* @author zengqiao
* @date 20/7/27
*/
public enum ApiLevelEnum {
LEVEL_0(0),
LEVEL_1(1),
LEVEL_2(2),
LEVEL_3(3)
;
private int level;
ApiLevelEnum(int level) {
this.level = level;
}
}

View File

@@ -24,18 +24,10 @@ public enum ConsumeHealthEnum {
return code; return code;
} }
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() { public String getMessage() {
return message; return message;
} }
public void setMessage(String message) {
this.message = message;
}
@Override @Override
public String toString() { public String toString() {
return "ConsumeHealthEnum{" + return "ConsumeHealthEnum{" +

View File

@@ -19,7 +19,10 @@ public enum DBStatusEnum {
return status; return status;
} }
public void setStatus(int status) { @Override
this.status = status; public String toString() {
return "DBStatusEnum{" +
"status=" + status +
'}';
} }
} }

View File

@@ -21,18 +21,10 @@ public enum IDCEnum {
return idc; return idc;
} }
public void setIdc(String idc) {
this.idc = idc;
}
public String getName() { public String getName() {
return name; return name;
} }
public void setName(String name) {
this.name = name;
}
@Override @Override
public String toString() { public String toString() {
return "IDCEnum{" + return "IDCEnum{" +

View File

@@ -21,10 +21,6 @@ public enum KafkaBrokerRoleEnum {
return role; return role;
} }
public void setRole(String role) {
this.role = role;
}
@Override @Override
public String toString() { public String toString() {
return "KafkaBrokerRoleEnum{" + return "KafkaBrokerRoleEnum{" +

View File

@@ -24,18 +24,10 @@ public enum KafkaClientEnum {
return code; return code;
} }
public void setCode(Integer code) {
this.code = code;
}
public String getName() { public String getName() {
return name; return name;
} }
public void setName(String name) {
this.name = name;
}
@Override @Override
public String toString() { public String toString() {
return "KafkaClientEnum{" + return "KafkaClientEnum{" +

View File

@@ -18,4 +18,11 @@ public enum OffsetResetTypeEnum {
public Integer getCode() { public Integer getCode() {
return code; return code;
} }
@Override
public String toString() {
return "OffsetResetTypeEnum{" +
"code=" + code +
'}';
}
} }

View File

@@ -46,7 +46,7 @@ public enum OperateEnum {
public static boolean validate(Integer code) { public static boolean validate(Integer code) {
if (code == null) { if (code == null) {
return false; return true;
} }
for (OperateEnum state : OperateEnum.values()) { for (OperateEnum state : OperateEnum.values()) {
if (state.getCode() == code) { if (state.getCode() == code) {

View File

@@ -27,4 +27,12 @@ public enum OperationStatusEnum {
public String getMessage() { public String getMessage() {
return message; return message;
} }
@Override
public String toString() {
return "OperationStatusEnum{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
} }

View File

@@ -15,9 +15,9 @@ public enum PeakFlowStatusEnum {
; ;
public Integer code; private Integer code;
public String message; private String message;
PeakFlowStatusEnum(Integer code, String message) { PeakFlowStatusEnum(Integer code, String message) {
this.code = code; this.code = code;
@@ -28,18 +28,10 @@ public enum PeakFlowStatusEnum {
return code; return code;
} }
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() { public String getMessage() {
return message; return message;
} }
public void setMessage(String message) {
this.message = message;
}
@Override @Override
public String toString() { public String toString() {
return "PeakFlowStatusEnum{" + return "PeakFlowStatusEnum{" +

View File

@@ -29,4 +29,12 @@ public enum RebalanceDimensionEnum {
public String getMessage() { public String getMessage() {
return message; return message;
} }
@Override
public String toString() {
return "RebalanceDimensionEnum{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
} }

View File

@@ -43,18 +43,10 @@ public enum TaskStatusEnum {
return code; return code;
} }
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() { public String getMessage() {
return message; return message;
} }
public void setMessage(String message) {
this.message = message;
}
@Override @Override
public String toString() { public String toString() {
return "TaskStatusEnum{" + return "TaskStatusEnum{" +
@@ -64,9 +56,6 @@ public enum TaskStatusEnum {
} }
public static Boolean isFinished(Integer code) { public static Boolean isFinished(Integer code) {
if (code >= FINISHED.getCode()) { return code >= FINISHED.getCode();
return true;
}
return false;
} }
} }

View File

@@ -45,11 +45,6 @@ public enum TaskStatusReassignEnum {
} }
public static Boolean isFinished(Integer code) { public static Boolean isFinished(Integer code) {
if (SUCCEED.getCode().equals(code) return SUCCEED.getCode().equals(code) || FAILED.getCode().equals(code) || CANCELED.getCode().equals(code);
|| FAILED.getCode().equals(code)
|| CANCELED.getCode().equals(code)) {
return true;
}
return false;
} }
} }

View File

@@ -33,4 +33,12 @@ public enum TopicAuthorityEnum {
public String getMessage() { public String getMessage() {
return message; return message;
} }
@Override
public String toString() {
return "TopicAuthorityEnum{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
} }

View File

@@ -29,4 +29,12 @@ public enum TopicExpiredStatusEnum {
public String getMessage() { public String getMessage() {
return message; return message;
} }
@Override
public String toString() {
return "TopicExpiredStatusEnum{" +
"status=" + status +
", message='" + message + '\'' +
'}';
}
} }

View File

@@ -23,18 +23,10 @@ public enum TopicOffsetChangedEnum {
return code; return code;
} }
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() { public String getMessage() {
return message; return message;
} }
public void setMessage(String message) {
this.message = message;
}
@Override @Override
public String toString() { public String toString() {
return "TopicOffsetChangedEnum{" + return "TopicOffsetChangedEnum{" +

View File

@@ -5,11 +5,11 @@ package com.xiaojukeji.kafka.manager.common.bizenum.gateway;
* @date 20/7/28 * @date 20/7/28
*/ */
public enum GatewayConfigKeyEnum { public enum GatewayConfigKeyEnum {
SD_CLUSTER_ID("SERVICE_DISCOVERY_CLUSTER_ID", "SERVICE_DISCOVERY_CLUSTER_ID"), SD_CLUSTER_ID("SD_CLUSTER_ID", "SD_CLUSTER_ID"),
SD_QUEUE_SIZE("SERVICE_DISCOVERY_QUEUE_SIZE", "SERVICE_DISCOVERY_QUEUE_SIZE"), SD_QUEUE_SIZE("SD_QUEUE_SIZE", "SD_QUEUE_SIZE"),
SD_APP_ID_RATE("SERVICE_DISCOVERY_APPID_RATE", "SERVICE_DISCOVERY_APPID_RATE"), SD_APP_RATE("SD_APP_RATE", "SD_APP_RATE"),
SD_IP_RATE("SERVICE_DISCOVERY_IP_RATE", "SERVICE_DISCOVERY_IP_RATE"), SD_IP_RATE("SD_IP_RATE", "SD_IP_RATE"),
SD_SP_RATE("SERVICE_DISCOVERY_SP_RATE", "SERVICE_DISCOVERY_SP_RATE"), SD_SP_RATE("SD_SP_RATE", "SD_SP_RATE"),
; ;
@@ -26,18 +26,10 @@ public enum GatewayConfigKeyEnum {
return configType; return configType;
} }
public void setConfigType(String configType) {
this.configType = configType;
}
public String getConfigName() { public String getConfigName() {
return configName; return configName;
} }
public void setConfigName(String configName) {
this.configName = configName;
}
@Override @Override
public String toString() { public String toString() {
return "GatewayConfigKeyEnum{" + return "GatewayConfigKeyEnum{" +

View File

@@ -12,4 +12,7 @@ public class ApiLevelContent {
public static final int LEVEL_NORMAL_3 = 3; public static final int LEVEL_NORMAL_3 = 3;
public static final int LEVEL_DEFAULT_4 = 4; public static final int LEVEL_DEFAULT_4 = 4;
private ApiLevelContent() {
}
} }

View File

@@ -20,6 +20,15 @@ public class ApiPrefix {
// open // open
public static final String API_V1_THIRD_PART_PREFIX = API_V1_PREFIX + "third-part/"; public static final String API_V1_THIRD_PART_PREFIX = API_V1_PREFIX + "third-part/";
// 开放给OP的接口, 后续对 应的接口的集群都需要是物理集群
public static final String API_V1_THIRD_PART_OP_PREFIX = API_V1_THIRD_PART_PREFIX + "op/";
// 开放给Normal的接口, 后续对应的接口的集群,都需要是逻辑集群
public static final String API_V1_THIRD_PART_NORMAL_PREFIX = API_V1_THIRD_PART_PREFIX + "normal/";
// gateway // gateway
public static final String GATEWAY_API_V1_PREFIX = "/gateway" + API_V1_PREFIX; public static final String GATEWAY_API_V1_PREFIX = "/gateway" + API_V1_PREFIX;
private ApiPrefix() {
}
} }

View File

@@ -30,4 +30,7 @@ public class ConfigConstant {
public static final String BROKER_CAPACITY_LIMIT_CONFIG_KEY = "BROKER_CAPACITY_LIMIT_CONFIG"; public static final String BROKER_CAPACITY_LIMIT_CONFIG_KEY = "BROKER_CAPACITY_LIMIT_CONFIG";
public static final String KAFKA_CLUSTER_DO_CONFIG_KEY = "KAFKA_CLUSTER_DO_CONFIG"; public static final String KAFKA_CLUSTER_DO_CONFIG_KEY = "KAFKA_CLUSTER_DO_CONFIG";
private ConfigConstant() {
}
} }

View File

@@ -45,4 +45,7 @@ public class Constant {
public static final Integer DEFAULT_MAX_CAL_TOPIC_EXPIRED_DAY = 90; public static final Integer DEFAULT_MAX_CAL_TOPIC_EXPIRED_DAY = 90;
public static final Integer INVALID_CODE = -1; public static final Integer INVALID_CODE = -1;
private Constant() {
}
} }

View File

@@ -16,4 +16,7 @@ public class KafkaConstant {
public static final String CLIENT_VERSION_NAME_UNKNOWN = "unknown"; public static final String CLIENT_VERSION_NAME_UNKNOWN = "unknown";
public static final String RETENTION_MS_KEY = "retention.ms"; public static final String RETENTION_MS_KEY = "retention.ms";
private KafkaConstant() {
}
} }

View File

@@ -39,4 +39,7 @@ public class KafkaMetricsCollections {
* Broker信息 * Broker信息
*/ */
public static final int BROKER_VERSION = 400; public static final int BROKER_VERSION = 400;
private KafkaMetricsCollections() {
}
} }

View File

@@ -10,4 +10,7 @@ public class LogConstant {
public static final String API_METRICS_LOGGER = "API_METRICS_LOGGER"; public static final String API_METRICS_LOGGER = "API_METRICS_LOGGER";
public static final String SCHEDULED_TASK_LOGGER = "SCHEDULED_TASK_LOGGER"; public static final String SCHEDULED_TASK_LOGGER = "SCHEDULED_TASK_LOGGER";
private LogConstant() {
}
} }

View File

@@ -11,4 +11,7 @@ public class LoginConstant {
public static final String COOKIE_CHINESE_USERNAME_KEY = "chineseName"; public static final String COOKIE_CHINESE_USERNAME_KEY = "chineseName";
public static final Integer COOKIE_OR_SESSION_MAX_AGE_UNIT_MS = 24 * 60 * 60 * 1000; public static final Integer COOKIE_OR_SESSION_MAX_AGE_UNIT_MS = 24 * 60 * 60 * 1000;
private LoginConstant() {
}
} }

View File

@@ -6,4 +6,7 @@ package com.xiaojukeji.kafka.manager.common.constant;
*/ */
public class SystemCodeConstant { public class SystemCodeConstant {
public static final String KAFKA_MANAGER = "kafka-manager"; public static final String KAFKA_MANAGER = "kafka-manager";
private SystemCodeConstant() {
}
} }

View File

@@ -54,4 +54,7 @@ public class TopicCreationConstant {
* 单次自动化审批, 最多允许的通过单子 * 单次自动化审批, 最多允许的通过单子
*/ */
public static final Integer MAX_PASSED_ORDER_NUM_PER_TASK = 200; public static final Integer MAX_PASSED_ORDER_NUM_PER_TASK = 200;
private TopicCreationConstant() {
}
} }

View File

@@ -16,4 +16,7 @@ public class TopicSampleConstant {
public static final Integer MAX_TIMEOUT_UNIT_MS = 10000; public static final Integer MAX_TIMEOUT_UNIT_MS = 10000;
public static final Integer POLL_TIME_OUT_UNIT_MS = 2000; public static final Integer POLL_TIME_OUT_UNIT_MS = 2000;
public static final Integer MAX_DATA_LENGTH_UNIT_BYTE = 2048; public static final Integer MAX_DATA_LENGTH_UNIT_BYTE = 2048;
private TopicSampleConstant() {
}
} }

View File

@@ -0,0 +1,24 @@
package com.xiaojukeji.kafka.manager.common.constant;
public class TrickLoginConstant {
/**
* HTTP Header key
*/
public static final String TRICK_LOGIN_SWITCH = "Trick-Login-Switch";
public static final String TRICK_LOGIN_USER = "Trick-Login-User";
/**
* 配置允许 trick 登录用户名单
*/
public static final String TRICK_LOGIN_LEGAL_USER_CONFIG_KEY = "SECURITY.TRICK_USERS";
/**
* 开关状态值
*/
public static final String TRICK_LOGIN_SWITCH_ON = "on";
public static final String TRICK_LOGIN_SWITCH_OFF = "off";
private TrickLoginConstant() {
}
}

View File

@@ -25,6 +25,9 @@ public enum ResultStatus {
CHANGE_ZOOKEEPER_FORBIDDEN(1405, "change zookeeper forbidden"), CHANGE_ZOOKEEPER_FORBIDDEN(1405, "change zookeeper forbidden"),
APP_OFFLINE_FORBIDDEN(1406, "先下线topic才能下线应用"),
TOPIC_OPERATION_PARAM_NULL_POINTER(1450, "参数错误"), TOPIC_OPERATION_PARAM_NULL_POINTER(1450, "参数错误"),
TOPIC_OPERATION_PARTITION_NUM_ILLEGAL(1451, "分区数错误"), TOPIC_OPERATION_PARTITION_NUM_ILLEGAL(1451, "分区数错误"),
TOPIC_OPERATION_BROKER_NUM_NOT_ENOUGH(1452, "Broker数不足错误"), TOPIC_OPERATION_BROKER_NUM_NOT_ENOUGH(1452, "Broker数不足错误"),

View File

@@ -1,5 +1,7 @@
package com.xiaojukeji.kafka.manager.common.entity.ao.gateway; package com.xiaojukeji.kafka.manager.common.entity.ao.gateway;
import com.xiaojukeji.kafka.manager.common.entity.dto.gateway.TopicQuotaDTO;
/** /**
* @author zhongyuankai * @author zhongyuankai
* @date 2020/4/27 * @date 2020/4/27
@@ -65,4 +67,15 @@ public class TopicQuota {
", consumeQuota=" + consumeQuota + ", consumeQuota=" + consumeQuota +
'}'; '}';
} }
public static TopicQuota buildFrom(TopicQuotaDTO dto) {
TopicQuota topicQuota = new TopicQuota();
topicQuota.setAppId(dto.getAppId());
topicQuota.setClusterId(dto.getClusterId());
topicQuota.setTopicName(dto.getTopicName());
topicQuota.setProduceQuota(dto.getProduceQuota());
topicQuota.setConsumeQuota(dto.getConsumeQuota());
return topicQuota;
}
} }

View File

@@ -0,0 +1,47 @@
package com.xiaojukeji.kafka.manager.common.entity.dto.gateway;
import com.xiaojukeji.kafka.manager.common.entity.dto.ClusterTopicDTO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(description = "配额调整")
public class TopicQuotaDTO extends ClusterTopicDTO {
@ApiModelProperty(value = "appId")
private String appId;
@ApiModelProperty(value = "发送数据速率B/s")
private Long produceQuota;
@ApiModelProperty(value = "消费数据速率B/s")
private Long consumeQuota;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Long getProduceQuota() {
return produceQuota;
}
public void setProduceQuota(Long produceQuota) {
this.produceQuota = produceQuota;
}
public Long getConsumeQuota() {
return consumeQuota;
}
public void setConsumeQuota(Long consumeQuota) {
this.consumeQuota = consumeQuota;
}
@Override
public boolean paramLegal() {
return !ValidateUtils.isNullOrLessThanZero(clusterId) && !ValidateUtils.isBlank(topicName) && !ValidateUtils.isBlank(appId);
}
}

View File

@@ -40,6 +40,9 @@ public class TopicCreationDTO extends ClusterTopicDTO {
@ApiModelProperty(value = "Topic属性列表") @ApiModelProperty(value = "Topic属性列表")
private Properties properties; private Properties properties;
@ApiModelProperty(value = "最大写入字节数")
private Long peakBytesIn;
public String getAppId() { public String getAppId() {
return appId; return appId;
} }
@@ -104,6 +107,14 @@ public class TopicCreationDTO extends ClusterTopicDTO {
this.properties = properties; this.properties = properties;
} }
public Long getPeakBytesIn() {
return peakBytesIn;
}
public void setPeakBytesIn(Long peakBytesIn) {
this.peakBytesIn = peakBytesIn;
}
@Override @Override
public String toString() { public String toString() {
return "TopicCreationDTO{" + return "TopicCreationDTO{" +

View File

@@ -81,11 +81,6 @@ public class OperateRecordDTO {
} }
public boolean legal() { public boolean legal() {
if (!ModuleEnum.validate(moduleId) || return !ValidateUtils.isNull(moduleId) && ModuleEnum.validate(moduleId) && OperateEnum.validate(operateId);
(!ValidateUtils.isNull(operateId) && OperateEnum.validate(operateId))
) {
return false;
}
return true;
} }
} }

View File

@@ -1,6 +1,7 @@
package com.xiaojukeji.kafka.manager.common.entity.pojo; package com.xiaojukeji.kafka.manager.common.entity.pojo;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.TopicCreationDTO; import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.TopicCreationDTO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import java.util.Date; import java.util.Date;
@@ -95,6 +96,7 @@ public class TopicDO {
topicDO.setClusterId(dto.getClusterId()); topicDO.setClusterId(dto.getClusterId());
topicDO.setTopicName(dto.getTopicName()); topicDO.setTopicName(dto.getTopicName());
topicDO.setDescription(dto.getDescription()); topicDO.setDescription(dto.getDescription());
topicDO.setPeakBytesIn(ValidateUtils.isNull(dto.getPeakBytesIn()) ? -1L : dto.getPeakBytesIn());
return topicDO; return topicDO;
} }
} }

View File

@@ -33,7 +33,7 @@ public class BrokerOverviewVO {
@ApiModelProperty(value = "分区数") @ApiModelProperty(value = "分区数")
private Integer partitionCount; private Integer partitionCount;
@ApiModelProperty(value = "已同步副本") @ApiModelProperty(value = "失效副本分区的个")
private Integer underReplicatedPartitions; private Integer underReplicatedPartitions;
@ApiModelProperty(value = "未同步") @ApiModelProperty(value = "未同步")

View File

@@ -1,4 +1,4 @@
package com.xiaojukeji.kafka.manager.openapi.common.vo; package com.xiaojukeji.kafka.manager.common.entity.vo.normal.topic;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
@@ -14,7 +14,6 @@ public class TopicStatisticMetricsVO {
public TopicStatisticMetricsVO(Double peakBytesIn) { public TopicStatisticMetricsVO(Double peakBytesIn) {
this.peakBytesIn = peakBytesIn; this.peakBytesIn = peakBytesIn;
} }
public Double getPeakBytesIn() { public Double getPeakBytesIn() {

View File

@@ -2,6 +2,7 @@ package com.xiaojukeji.kafka.manager.common.utils;
import com.xiaojukeji.kafka.manager.common.constant.Constant; import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.constant.LoginConstant; import com.xiaojukeji.kafka.manager.common.constant.LoginConstant;
import com.xiaojukeji.kafka.manager.common.constant.TrickLoginConstant;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@@ -53,13 +54,6 @@ public class SpringTool implements ApplicationContextAware, DisposableBean {
return getApplicationContext().getBeansOfType(type); return getApplicationContext().getBeansOfType(type);
} }
// /**
// * 从静态变量applicationContext中去的Bean自动转型为所复制对象的类型
// */
// public static <T> T getBean(Class<T> requiredType) {
// return (T) applicationContext.getBean(requiredType);
// }
/** /**
* 清除SpringContextHolder中的ApplicationContext为Null * 清除SpringContextHolder中的ApplicationContext为Null
*/ */
@@ -87,10 +81,18 @@ public class SpringTool implements ApplicationContextAware, DisposableBean {
} }
public static String getUserName(){ public static String getUserName(){
HttpServletRequest request = HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String username = null;
if (TrickLoginConstant.TRICK_LOGIN_SWITCH_ON.equals(request.getHeader(TrickLoginConstant.TRICK_LOGIN_SWITCH))) {
// trick登录方式的获取用户
username = request.getHeader(TrickLoginConstant.TRICK_LOGIN_USER);
} else {
// 走页面登录方式登录的获取用户
HttpSession session = request.getSession(); HttpSession session = request.getSession();
String username = (String) session.getAttribute(LoginConstant.SESSION_USERNAME_KEY); username = (String) session.getAttribute(LoginConstant.SESSION_USERNAME_KEY);
}
if (ValidateUtils.isNull(username)) { if (ValidateUtils.isNull(username)) {
return Constant.DEFAULT_USER_NAME; return Constant.DEFAULT_USER_NAME;
} }

View File

@@ -119,4 +119,7 @@ public class ZkPathUtil {
public static String getControllerCandidatePath(Integer brokerId) { public static String getControllerCandidatePath(Integer brokerId) {
return D_CONTROLLER_CANDIDATES + ZOOKEEPER_SEPARATOR + brokerId; return D_CONTROLLER_CANDIDATES + ZOOKEEPER_SEPARATOR + brokerId;
} }
private ZkPathUtil() {
}
} }

View File

@@ -1,8 +1,5 @@
package com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers; package com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
/** /**
@@ -18,12 +15,11 @@ import java.util.List;
* "host":null, * "host":null,
* "timestamp":"1546632983233", * "timestamp":"1546632983233",
* "port":-1, * "port":-1,
* "version":4 * "version":4,
* "rack": "CY"
* } * }
*/ */
public class BrokerMetadata implements Cloneable { public class BrokerMetadata implements Cloneable {
private final static Logger LOGGER = LoggerFactory.getLogger(TopicMetadata.class);
private long clusterId; private long clusterId;
private int brokerId; private int brokerId;
@@ -43,6 +39,8 @@ public class BrokerMetadata implements Cloneable {
private long timestamp; private long timestamp;
private String rack;
public long getClusterId() { public long getClusterId() {
return clusterId; return clusterId;
} }
@@ -107,14 +105,12 @@ public class BrokerMetadata implements Cloneable {
this.timestamp = timestamp; this.timestamp = timestamp;
} }
@Override public String getRack() {
public Object clone() { return rack;
try {
return super.clone();
} catch (CloneNotSupportedException var3) {
LOGGER.error("clone BrokerMetadata failed.", var3);
} }
return null;
public void setRack(String rack) {
this.rack = rack;
} }
@Override @Override
@@ -128,6 +124,7 @@ public class BrokerMetadata implements Cloneable {
", jmxPort=" + jmx_port + ", jmxPort=" + jmx_port +
", version='" + version + '\'' + ", version='" + version + '\'' +
", timestamp=" + timestamp + ", timestamp=" + timestamp +
", rack='" + rack + '\'' +
'}'; '}';
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "logi-kafka", "name": "logi-kafka",
"version": "2.3.1", "version": "2.4.1",
"description": "", "description": "",
"scripts": { "scripts": {
"start": "webpack-dev-server", "start": "webpack-dev-server",

View File

@@ -68,8 +68,8 @@ export class StatusGraghCom<T extends IFlowInfo> extends React.Component {
public render() { public render() {
const statusData = this.getData(); const statusData = this.getData();
const loading = this.getLoading(); const loading = this.getLoading();
if (!statusData) return null;
const data: any[] = []; const data: any[] = [];
if (!statusData) return <Table columns={flowColumns} dataSource={data} />;
Object.keys(statusData).map((key) => { Object.keys(statusData).map((key) => {
if (statusData[key]) { if (statusData[key]) {
const v = key === 'byteIn' || key === 'byteOut' ? statusData[key].map(i => i && (i / 1024).toFixed(2)) : const v = key === 'byteIn' || key === 'byteOut' ? statusData[key].map(i => i && (i / 1024).toFixed(2)) :

View File

@@ -1,4 +1,4 @@
.ant-input-number { .ant-input-number, .ant-form-item-children .ant-select {
width: 314px width: 314px
} }

View File

@@ -59,6 +59,10 @@ export const adminMenu = [{
href: `/admin/bill`, href: `/admin/bill`,
i: 'k-icon-renwuliebiao', i: 'k-icon-renwuliebiao',
title: '用户账单', title: '用户账单',
},{
href: `/admin/operation-record`,
i: 'k-icon-operationrecord',
title: '操作记录',
}] as ILeftMenu[]; }] as ILeftMenu[];
export const expertMenu = [{ export const expertMenu = [{

View File

@@ -172,7 +172,7 @@ export class ClusterTopic extends SearchAndFilterContainer {
key: 'appName', key: 'appName',
// width: '10%', // width: '10%',
render: (val: string, record: IClusterTopics) => ( render: (val: string, record: IClusterTopics) => (
<Tooltip placement="bottomLeft" title={record.appId} > <Tooltip placement="bottomLeft" title={val} >
{val} {val}
</Tooltip> </Tooltip>
), ),

View File

@@ -314,8 +314,7 @@ export class ExclusiveCluster extends SearchAndFilterContainer {
> >
<div className="region-prompt"> <div className="region-prompt">
<span> <span>
Region已被逻辑集群 {this.state.logicalClusterName} 使 Region已被逻辑集群 {this.state.logicalClusterName} 使Region与逻辑集群的关系
Region与逻辑集群的关系
</span> </span>
</div> </div>
</Modal> </Modal>

View File

@@ -16,7 +16,7 @@
.traffic-table { .traffic-table {
margin: 10px 0; margin: 10px 0;
min-height: 450px; min-height: 330px;
.traffic-header { .traffic-header {
width: 100%; width: 100%;
height: 44px; height: 44px;

View File

@@ -4,6 +4,7 @@ import { wrapper } from 'store';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { IXFormWrapper, IMetaData, IRegister } from 'types/base-type'; import { IXFormWrapper, IMetaData, IRegister } from 'types/base-type';
import { admin } from 'store/admin'; import { admin } from 'store/admin';
import { users } from 'store/users';
import { registerCluster, createCluster, pauseMonitoring } from 'lib/api'; import { registerCluster, createCluster, pauseMonitoring } from 'lib/api';
import { SearchAndFilterContainer } from 'container/search-filter'; import { SearchAndFilterContainer } from 'container/search-filter';
import { cluster } from 'store/cluster'; import { cluster } from 'store/cluster';
@@ -78,34 +79,34 @@ export class ClusterList extends SearchAndFilterContainer {
disabled: item ? true : false, disabled: item ? true : false,
}, },
}, },
{ // {
key: 'idc', // key: 'idc',
label: '数据中心', // label: '数据中心',
defaultValue: region.regionName, // defaultValue: region.regionName,
rules: [{ required: true, message: '请输入数据中心' }], // rules: [{ required: true, message: '请输入数据中心' }],
attrs: { // attrs: {
placeholder: '请输入数据中心', // placeholder: '请输入数据中心',
disabled: true, // disabled: true,
}, // },
}, // },
{ // {
key: 'mode', // key: 'mode',
label: '集群类型', // label: '集群类型',
type: 'select', // type: 'select',
options: cluster.clusterModes.map(ele => { // options: cluster.clusterModes.map(ele => {
return { // return {
label: ele.message, // label: ele.message,
value: ele.code, // value: ele.code,
}; // };
}), // }),
rules: [{ // rules: [{
required: true, // required: true,
message: '请选择集群类型', // message: '请选择集群类型',
}], // }],
attrs: { // attrs: {
placeholder: '请选择集群类型', // placeholder: '请选择集群类型',
}, // },
}, // },
{ {
key: 'kafkaVersion', key: 'kafkaVersion',
label: 'kafka版本', label: 'kafka版本',
@@ -148,7 +149,7 @@ export class ClusterList extends SearchAndFilterContainer {
attrs: { attrs: {
placeholder: `请输入JMX认证例如 placeholder: `请输入JMX认证例如
{ {
"maxConn": 10, #KM对单台Broker最大连接数 "maxConn": 10, #KM对单台Broker最大jmx连接数
"username": "xxxxx", #用户名 "username": "xxxxx", #用户名
"password": "xxxxx", #密码 "password": "xxxxx", #密码
"openSSL": true, #开启SSLtrue表示开启SSLfalse表示关闭 "openSSL": true, #开启SSLtrue表示开启SSLfalse表示关闭
@@ -276,10 +277,13 @@ export class ClusterList extends SearchAndFilterContainer {
public getColumns = () => { public getColumns = () => {
const cols = getAdminClusterColumns(); const cols = getAdminClusterColumns();
const role = users.currentUser.role;
const col = { const col = {
title: '操作', title: '操作',
render: (value: string, item: IMetaData) => ( render: (value: string, item: IMetaData) => (
<> <>
{
role && role === 2 ? <>
<a <a
onClick={this.createOrRegisterCluster.bind(this, item)} onClick={this.createOrRegisterCluster.bind(this, item)}
className="action-button" className="action-button"
@@ -291,7 +295,7 @@ export class ClusterList extends SearchAndFilterContainer {
cancelText="取消" cancelText="取消"
okText="确认" okText="确认"
> >
<Tooltip title="暂停监控将无法正常监控指标信息,建议开启监控"> <Tooltip placement="left" title="暂停监控将无法正常监控指标信息,建议开启监控">
<a <a
className="action-button" className="action-button"
> >
@@ -302,6 +306,12 @@ export class ClusterList extends SearchAndFilterContainer {
<a onClick={this.showMonitor.bind(this, item)}> <a onClick={this.showMonitor.bind(this, item)}>
</a> </a>
</> : <Tooltip placement="left" title="该功能只对运维人员开放">
<a style={{ color: '#a0a0a0' }} className="action-button"></a>
<a className="action-button" style={{ color: '#a0a0a0' }}>{item.status === 1 ? '暂停监控' : '开始监控'}</a>
<a style={{ color: '#a0a0a0' }}></a>
</Tooltip>
}
</> </>
), ),
}; };
@@ -310,6 +320,7 @@ export class ClusterList extends SearchAndFilterContainer {
} }
public renderClusterList() { public renderClusterList() {
const role = users.currentUser.role;
return ( return (
<> <>
<div className="container"> <div className="container">
@@ -318,7 +329,14 @@ export class ClusterList extends SearchAndFilterContainer {
{this.renderSearch('', '请输入集群名称')} {this.renderSearch('', '请输入集群名称')}
<li className="right-btn-1"> <li className="right-btn-1">
<a style={{ display: 'inline-block', marginRight: '20px' }} href={indexUrl.cagUrl} target="_blank"></a> <a style={{ display: 'inline-block', marginRight: '20px' }} href={indexUrl.cagUrl} target="_blank"></a>
{
role && role === 2 ?
<Button type="primary" onClick={this.createOrRegisterCluster.bind(this, null)}></Button> <Button type="primary" onClick={this.createOrRegisterCluster.bind(this, null)}></Button>
:
<Tooltip placement="left" title="该功能只对运维人员开放" trigger='hover'>
<Button disabled type="primary"></Button>
</Tooltip>
}
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -28,6 +28,7 @@ export const getUserColumns = () => {
<span className="table-operation"> <span className="table-operation">
<a onClick={() => showApplyModal(record)}></a> <a onClick={() => showApplyModal(record)}></a>
<a onClick={() => showApplyModalModifyPassword(record)}></a> <a onClick={() => showApplyModalModifyPassword(record)}></a>
{record.username == users.currentUser.username ? "" :
<Popconfirm <Popconfirm
title="确定删除?" title="确定删除?"
onConfirm={() => users.deleteUser(record.username)} onConfirm={() => users.deleteUser(record.username)}
@@ -36,6 +37,7 @@ export const getUserColumns = () => {
> >
<a></a> <a></a>
</Popconfirm> </Popconfirm>
}
</span>); </span>);
}, },
}, },

View File

@@ -11,3 +11,5 @@ export * from './operation-management/migration-detail';
export * from './configure-management'; export * from './configure-management';
export * from './individual-bill'; export * from './individual-bill';
export * from './bill-detail'; export * from './bill-detail';
export * from './operation-record';

View File

@@ -0,0 +1,134 @@
import * as React from 'react';
import { cellStyle } from 'constants/table';
import { Tooltip } from 'antd';
import { admin } from 'store/admin';
import moment = require('moment');
const moduleList = [
{ moduleId: 0, moduleName: 'Topic' },
{ moduleId: 1, moduleName: '应用' },
{ moduleId: 2, moduleName: '配额' },
{ moduleId: 3, moduleName: '权限' },
{ moduleId: 4, moduleName: '集群' },
{ moduleId: 5, moduleName: '分区' },
{ moduleId: 6, moduleName: 'Gateway配置' },
]
export const operateList = {
0: '新增',
1: '删除',
2: '修改'
}
// [
// { operate: '新增', operateId: 0 },
// { operate: '删除', operateId: 1 },
// { operate: '修改', operateId: 2 },
// ]
export const getJarFuncForm: any = (props: any) => {
const formMap = [
{
key: 'moduleId',
label: '模块',
type: 'select',
attrs: {
style: {
width: '130px'
},
placeholder: '请选择模块',
},
options: moduleList.map(item => {
return {
label: item.moduleName,
value: item.moduleId
}
}),
formAttrs: {
initialvalue: 0,
},
},
{
key: 'operator',
label: '操作人',
type: 'input',
attrs: {
style: {
width: '170px'
},
placeholder: '请输入操作人'
},
getvaluefromevent: (event: any) => {
return event.target.value.replace(/\s+/g, '')
},
},
// {
// key: 'resource',
// label: '资源名称',
// type: 'input',
// attrs: {
// style: {
// width: '170px'
// },
// placeholder: '请输入资源名称'
// },
// },
// {
// key: 'content',
// label: '操作内容',
// type: 'input',
// attrs: {
// style: {
// width: '170px'
// },
// placeholder: '请输入操作内容'
// },
// },
]
return formMap;
}
export const getOperateColumns = () => {
const columns: any = [
{
title: '模块',
dataIndex: 'module',
key: 'module',
align: 'center',
width: '12%'
},
{
title: '资源名称',
dataIndex: 'resource',
key: 'resource',
align: 'center',
width: '12%'
},
{
title: '操作内容',
dataIndex: 'content',
key: 'content',
align: 'center',
width: '25%',
onCell: () => ({
style: {
maxWidth: 350,
...cellStyle,
},
}),
render: (text: string, record: any) => {
return (
<Tooltip placement="topLeft" title={text} >{text}</Tooltip>);
},
},
{
title: '操作人',
dataIndex: 'operator',
align: 'center',
width: '12%'
},
];
return columns
}

View File

@@ -0,0 +1,130 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import { SearchAndFilterContainer } from 'container/search-filter';
import { IXFormWrapper, IMetaData, IRegister } from 'types/base-type';
import { admin } from 'store/admin';
import { customPagination, cellStyle } from 'constants/table';
import { Table, Tooltip } from 'component/antd';
import { timeFormat } from 'constants/strategy';
import { SearchFormComponent } from '../searchForm';
import { getJarFuncForm, operateList, getOperateColumns } from './config'
import moment = require('moment');
import { tableFilter } from 'lib/utils';
@observer
export class OperationRecord extends SearchAndFilterContainer {
public state: any = {
searchKey: '',
filteredInfo: null,
sortedInfo: null,
};
public getData<T extends IMetaData>(origin: T[]) {
let data: T[] = origin;
let { searchKey } = this.state;
searchKey = (searchKey + '').trim().toLowerCase();
data = searchKey ? origin.filter((item: IMetaData) =>
(item.clusterName !== undefined && item.clusterName !== null) && item.clusterName.toLowerCase().includes(searchKey as string),
) : origin;
return data;
};
public searchForm = (params: any) => {
// this.props.setFuncSubValue(params)
// getSystemFuncList(params).then(res => {
// this.props.setSysFuncList(res.data)
// this.props.setPagination(res.pagination)
// })
const { operator, moduleId } = params || {}
operator ? admin.getOperationRecordData(params) : admin.getOperationRecordData({ moduleId })
// getJarList(params).then(res => {
// this.props.setJarList(res.data)
// this.props.setPagination(res.pagination)
// })
}
public clearAll = () => {
this.setState({
filteredInfo: null,
sortedInfo: null,
});
};
public setHandleChange = (pagination: any, filters: any, sorter: any) => {
this.setState({
filteredInfo: filters,
sortedInfo: sorter,
});
}
public renderOperationRecordList() {
let { sortedInfo, filteredInfo } = this.state;
sortedInfo = sortedInfo || {};
filteredInfo = filteredInfo || {};
const operatingTime = Object.assign({
title: '操作时间',
dataIndex: 'modifyTime',
key: 'modifyTime',
align: 'center',
sorter: (a: any, b: any) => a.modifyTime - b.modifyTime,
render: (t: number) => moment(t).format(timeFormat),
width: '15%',
sortOrder: sortedInfo.columnKey === 'modifyTime' && sortedInfo.order,
});
const operatingPractice = Object.assign({
title: '行为',
dataIndex: 'operate',
key: 'operate',
align: 'center',
width: '12%',
filters: tableFilter<any>(this.getData(admin.oRList), 'operateId', operateList),
// filteredValue: filteredInfo.operate || null,
onFilter: (value: any, record: any) => {
return record.operateId === value
}
}, this.renderColumnsFilter('modifyTime'))
const columns = getOperateColumns()
columns.splice(0, 0, operatingTime);
columns.splice(3, 0, operatingPractice);
return (
<>
<div className="container">
<div className="table-operation-panel">
<SearchFormComponent
formMap={getJarFuncForm()}
onSubmit={(params: any) => this.searchForm(params)}
clearAll={() => this.clearAll()}
isReset={true}
/>
</div>
<div className="table-wrapper">
<Table
rowKey="key"
loading={admin.loading}
dataSource={this.getData(admin.oRList)}
columns={columns}
pagination={customPagination}
bordered
onChange={this.setHandleChange}
/>
</div>
</div>
</>
)
};
componentDidMount() {
admin.getOperationRecordData({ moduleId: 0 });
}
render() {
return <div>
{
this.renderOperationRecordList()
}
</div>
}
}

View File

@@ -0,0 +1,120 @@
import * as React from 'react';
import { Select, Input, InputNumber, Form, Switch, Checkbox, DatePicker, Radio, Upload, Button, Icon, Tooltip } from 'component/antd';
// import './index.less';
const Search = Input.Search;
export interface IFormItem {
key: string;
label: string;
type: string;
value?: string;
// 内部组件属性注入
attrs?: any;
// form属性注入
formAttrs?: any;
defaultValue?: string | number | any[];
rules?: any[];
invisible?: boolean;
getvaluefromevent: Function;
}
interface SerachFormProps {
formMap: IFormItem[];
// formData: any;
form: any;
onSubmit: Function;
isReset?: boolean;
clearAll: Function;
layout?: 'inline' | 'horizontal' | 'vertical';
}
export interface IFormSelect extends IFormItem {
options: Array<{ key?: string | number, value: string | number, label: string }>;
}
class SearchForm extends React.Component<SerachFormProps>{
public onSubmit = () => {
// this.props.onSubmit()
//
}
public renderFormItem(item: IFormItem) {
switch (item.type) {
default:
case 'input':
return <Input key={item.key} {...item.attrs} />;
case 'select':
return (
<Select
// size="small"
key={item.key}
{...item.attrs}
invisibleValue={item.formAttrs.invisibleValue}
>
{(item as IFormSelect).options && (item as IFormSelect).options.map((v, index) => (
<Select.Option
key={v.value || v.key || index}
value={v.value}
>
{v.label}
{/* <Tooltip placement='left' title={v.value}>
{v.label}
</Tooltip> */}
</Select.Option>
))}
</Select>
);
}
}
public theQueryClick = (value: any) => {
this.props.onSubmit(value)
this.props.clearAll()
// this.props.form.resetFields()
}
public resetClick = () => {
this.props.form.resetFields()
this.props.clearAll()
this.theQueryClick(this.props.form.getFieldsValue())
}
public render() {
const { form, formMap, isReset } = this.props;
const { getFieldDecorator, getFieldsValue } = form;
return (
<Form layout='inline' onSubmit={this.onSubmit}>
{
formMap.map(formItem => {
// const { initialValue, valuePropName } = this.handleFormItem(formItem, formData);
// const getFieldValue = {
// initialValue,
// rules: formItem.rules || [{ required: false, message: '' }],
// valuePropName,
// };
return (
<Form.Item
key={formItem.key}
label={formItem.label}
{...formItem.formAttrs}
>
{getFieldDecorator(formItem.key, {
initialValue: formItem.formAttrs?.initialvalue,
getValueFromEvent: formItem?.getvaluefromevent,
})(
this.renderFormItem(formItem),
)}
</Form.Item>
);
})
}
<Form.Item>
{
isReset && <Button style={{ width: '80px', marginRight: '20px' }} type="primary" onClick={() => this.resetClick()}></Button>
}
<Button style={{ width: '80px' }} type="primary" onClick={() => this.theQueryClick(getFieldsValue())}></Button>
</Form.Item>
</Form>
);
}
}
export const SearchFormComponent = Form.create<SerachFormProps>({ name: 'search-form' })(SearchForm);

View File

@@ -149,9 +149,9 @@ export class DynamicSetFilter extends React.Component<IDynamicProps> {
public handleSelectChange = (e: string, type: 'topic' | 'consumerGroup' | 'location') => { public handleSelectChange = (e: string, type: 'topic' | 'consumerGroup' | 'location') => {
switch (type) { switch (type) {
case 'topic': case 'topic':
if (!this.clusterId) { // if (!this.clusterId) {
return message.info('请选择集群'); // return message.info('请选择集群');
} // }
this.topicName = e; this.topicName = e;
const type = this.dealMonitorType(); const type = this.dealMonitorType();
if (['kafka-consumer-maxLag', 'kafka-consumer-maxDelayTime', 'kafka-consumer-lag'].indexOf(type) > -1) { if (['kafka-consumer-maxLag', 'kafka-consumer-maxDelayTime', 'kafka-consumer-lag'].indexOf(type) > -1) {

View File

@@ -52,8 +52,7 @@ export class CommonAppList extends SearchAndFilterContainer {
}, },
}), }),
render: (text: string, record: IAppItem) => { render: (text: string, record: IAppItem) => {
return ( return (<Tooltip placement="bottomLeft" title={record.name}>{text}</Tooltip>);
<Tooltip placement="bottomLeft" title={record.name}>{text}</Tooltip>);
}, },
}, },
{ {
@@ -103,7 +102,7 @@ export class CommonAppList extends SearchAndFilterContainer {
} }
public getOnlineConnect(record: IAppItem) { public getOnlineConnect(record: IAppItem) {
modal.showOfflineAppModal(record.appId); modal.showOfflineAppNewModal(record.appId);
} }
public getData<T extends IAppItem>(origin: T[]) { public getData<T extends IAppItem>(origin: T[]) {

View File

@@ -29,16 +29,16 @@ export class MyCluster extends SearchAndFilterContainer {
public applyCluster() { public applyCluster() {
const xFormModal = { const xFormModal = {
formMap: [ formMap: [
{ // {
key: 'idc', // key: 'idc',
label: '数据中心', // label: '数据中心',
defaultValue: region.regionName, // defaultValue: region.regionName,
rules: [{ required: true, message: '请输入数据中心' }], // rules: [{ required: true, message: '请输入数据中心' }],
attrs: { // attrs: {
placeholder: '请输入数据中心', // placeholder: '请输入数据中心',
disabled: true, // disabled: true,
}, // },
}, // },
{ {
key: 'appId', key: 'appId',
label: '所属应用', label: '所属应用',

View File

@@ -16,6 +16,14 @@
line-height: 64px; line-height: 64px;
vertical-align: middle; vertical-align: middle;
} }
.kafka-header-version{
display: inline-block;
vertical-align: middle;
padding-top:5px;
font-size: 12px;
margin-left:10px;
color:#a0a0a0;
}
} }
.mid-content { .mid-content {

View File

@@ -145,6 +145,8 @@ export const Header = observer((props: IHeader) => {
<div className="left-content"> <div className="left-content">
<img className="kafka-header-icon" src={logoUrl} alt="" /> <img className="kafka-header-icon" src={logoUrl} alt="" />
<span className="kafka-header-text">Kafka Manager</span> <span className="kafka-header-text">Kafka Manager</span>
<a className='kafka-header-version' href="https://github.com/didi/Logi-KafkaManager/releases" target='_blank'>v2.4.0</a>
{/* 添加版本超链接 */}
</div> </div>
<div className="mid-content"> <div className="mid-content">
{headerMenu.map((item: IMenuItem, index: number) => {headerMenu.map((item: IMenuItem, index: number) =>

View File

@@ -22,11 +22,11 @@ export const showEditClusterTopic = (item: IClusterTopics) => {
}, },
{ {
key: 'appId', key: 'appId',
label: '应用ID', label: '应用名称',
type: 'select', type: 'select',
options: app.adminAppData.map(item => { options: app.adminAppData.map(item => {
return { return {
label: item.appId, label: item.name,
value: item.appId, value: item.appId,
}; };
}), }),

View File

@@ -35,7 +35,6 @@ class CustomForm extends React.Component<IXFormProps> {
this.props.form.validateFields((err: any, values: any) => { this.props.form.validateFields((err: any, values: any) => {
const deleteData = this.props.formData; const deleteData = this.props.formData;
if (!err) { if (!err) {
// console.log('values', values);
if (values.topicName !== this.props.formData.topicName) { if (values.topicName !== this.props.formData.topicName) {
notification.error({ message: 'topic名称不正确请重新输入' }); notification.error({ message: 'topic名称不正确请重新输入' });
} else { } else {
@@ -77,7 +76,6 @@ class CustomForm extends React.Component<IXFormProps> {
} }
public render() { public render() {
// console.log('props', this.props);
const { formData = {} as any, visible } = this.props; const { formData = {} as any, visible } = this.props;
const { getFieldDecorator } = this.props.form; const { getFieldDecorator } = this.props.form;
let metadata = [] as IBrokersMetadata[]; let metadata = [] as IBrokersMetadata[];

View File

@@ -111,11 +111,11 @@ class CustomForm extends React.Component<IXFormProps> {
})(<Input placeholder="请输入分区数" />)} })(<Input placeholder="请输入分区数" />)}
</Form.Item> </Form.Item>
<Form.Item label="类型"> <Form.Item label="类型">
{/* <Form.Item label={this.state.checked ? 'Region类型' : 'Borker类型'} > */} {/* <Form.Item label={this.state.checked ? 'Region类型' : 'Broker类型'} > */}
{/* <Switch onChange={(checked) => this.onSwitchChange(checked)} /> */} {/* <Switch onChange={(checked) => this.onSwitchChange(checked)} /> */}
<Radio.Group value={this.state.checked ? 'region' : 'broker'} onChange={(e) => { this.onSwitchChange(e.target.value === 'region' ? true : false); }}> <Radio.Group value={this.state.checked ? 'region' : 'broker'} onChange={(e) => { this.onSwitchChange(e.target.value === 'region' ? true : false); }}>
<Radio.Button value="region">Region类型</Radio.Button> <Radio.Button value="region">Region类型</Radio.Button>
<Radio.Button value="broker">Borker类型</Radio.Button> <Radio.Button value="broker">Broker类型</Radio.Button>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label="brokerIdList" style={{ display: this.state.checked ? 'none' : '' }}> <Form.Item label="brokerIdList" style={{ display: this.state.checked ? 'none' : '' }}>

View File

@@ -103,7 +103,7 @@ export const createMigrationTasks = () => {
label: 'Region', label: 'Region',
value: 'region', value: 'region',
}, { }, {
label: 'Borker', label: 'Broker',
value: 'broker', value: 'broker',
}], }],
rules: [{ rules: [{

View File

@@ -0,0 +1,78 @@
import * as React from 'react';
import { Table, Modal, Tooltip, Icon, message, notification, Alert, Button } from 'component/antd';
import { app } from 'store/app';
import { getApplyOnlineColumns } from 'container/topic/config';
import { observer } from 'mobx-react';
import { modal } from 'store/modal';
import { users } from 'store/users';
import { urlPrefix } from 'constants/left-menu';
import { region } from 'store';
@observer
export class ConnectAppNewList extends React.Component {
public componentDidMount() {
app.getAppsConnections(modal.params);
}
public handleCancel = () => {
app.setAppsConnections([]);
modal.close();
}
public handleSubmit = () => {
const connectionList = app.appsConnections;
if (connectionList && connectionList.length) {
return message.warning('存在连接信息,无法申请下线!');
}
const offlineParams = {
type: 11,
applicant: users.currentUser.username,
description: '',
extensions: JSON.stringify({ appId: modal.params }),
};
app.applyAppOffline(offlineParams).then((data: any) => {
notification.success({ message: '申请下线成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
modal.close();
}
public render() {
const connectionList = app.appsConnections;
return (
<>
<Modal
visible={true}
className="stream-debug-modal"
title="提示"
maskClosable={false}
onCancel={this.handleCancel}
// onOk={this.handleSubmit}
// okText="确认"
// cancelText="取消"
okButtonProps={{ disabled: app.connectLoading || !!app.appsConnections.length }}
footer={connectionList && connectionList.length ?
<Button type="primary" onClick={this.handleCancel}></Button>
:
<>
<Button onClick={this.handleCancel}></Button>
<Button type="primary" onClick={this.handleSubmit}></Button>
</>
}
width={500}
>
<div style={{ textAlign: 'center', fontWeight: "bolder" }}>
{
connectionList && connectionList.length
?
<span>Topic关联Topic之间的关系<a href={`${urlPrefix}/topic/app-detail?appId=${modal.params}`}></a></span>
:
<span>线AppID</span>
}
</div>
</Modal>
</>
);
}
}

View File

@@ -4,13 +4,14 @@ import { message, Icon, notification, Modal, Table, Tooltip } from 'component/an
import { IApprovalOrder, IBaseOrder, IOrderInfo } from 'types/base-type'; import { IApprovalOrder, IBaseOrder, IOrderInfo } from 'types/base-type';
import { admin } from 'store/admin'; import { admin } from 'store/admin';
import { modal } from 'store/modal'; import { modal } from 'store/modal';
import { cluster } from 'store/cluster';
import { cellStyle } from 'constants/table'; import { cellStyle } from 'constants/table';
import * as React from 'react'; import * as React from 'react';
const updateInputModal = (status: string, type: number) => { const updateInputModal = (status: string, type: number) => {
const formMap = wrapper.xFormWrapper.formMap; const formMap = wrapper.xFormWrapper.formMap;
const region = type === 0 ? 5 : 3; const region = type === 0 ? 6 : 3;
const broker = type === 0 ? 6 : 4; const broker = type === 0 ? 7 : 4;
formMap[region].invisible = status === 'region'; formMap[region].invisible = status === 'region';
formMap[broker].invisible = status !== 'region'; formMap[broker].invisible = status !== 'region';
@@ -37,6 +38,12 @@ const renderModalTilte = (type: number, status: number) => {
export const showApprovalModal = (info: IOrderInfo, status: number, from?: string) => { export const showApprovalModal = (info: IOrderInfo, status: number, from?: string) => {
const { id, type } = info; const { id, type } = info;
const formMap = [{ const formMap = [{
key: 'clusterId',
label: '所属集群',
type: 'input_number',
defaultValue: info.detail.logicalClusterName,
attrs: { disabled: true },
}, {
key: 'partitionNum', key: 'partitionNum',
label: '分区数', label: '分区数',
type: 'input_number', type: 'input_number',
@@ -87,7 +94,7 @@ export const showApprovalModal = (info: IOrderInfo, status: number, from?: strin
label: 'Region', label: 'Region',
value: 'region', value: 'region',
}, { }, {
label: 'Borker', label: 'Broker',
value: 'broker', value: 'broker',
}], }],
rules: [{ required: false, message: '请选择类型' }], rules: [{ required: false, message: '请选择类型' }],

View File

@@ -399,8 +399,8 @@ export const updateAllTopicFormModal = () => {
const formMap = wrapper.xFormWrapper.formMap; const formMap = wrapper.xFormWrapper.formMap;
if (topic.authorities) { if (topic.authorities) {
const { consume, send, checkStatus } = judgeAccessStatus(topic.authorities.access); const { consume, send, checkStatus } = judgeAccessStatus(topic.authorities.access);
formMap[3].defaultValue = checkStatus; formMap[2].defaultValue = checkStatus;
formMap[3].options = [{ formMap[2].options = [{
label: `消费权限${consume ? '(已拥有)' : ''}`, label: `消费权限${consume ? '(已拥有)' : ''}`,
value: '1', value: '1',
disabled: consume, disabled: consume,
@@ -409,7 +409,7 @@ export const updateAllTopicFormModal = () => {
value: '2', value: '2',
disabled: send, disabled: send,
}]; }];
formMap[3].rules = [{ formMap[2].rules = [{
required: true, required: true,
validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus, 'allTopic'), validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus, 'allTopic'),
}]; }];
@@ -476,7 +476,6 @@ export const showAllPermissionModal = (item: ITopic) => {
const showAllPermission = (appId: string, item: ITopic, access: number) => { const showAllPermission = (appId: string, item: ITopic, access: number) => {
const { consume, send, checkStatus } = judgeAccessStatus(access); const { consume, send, checkStatus } = judgeAccessStatus(access);
const xFormModal = { const xFormModal = {
formMap: [ formMap: [
{ {
@@ -489,16 +488,6 @@ const showAllPermission = (appId: string, item: ITopic, access: number) => {
disabled: true, disabled: true,
}, },
}, },
{
key: 'clusterName',
label: '集群名称',
defaultValue: item.clusterName,
rules: [{ required: true, message: '请输入集群名称' }],
attrs: {
placeholder: '请输入集群名称',
disabled: true,
},
},
{ {
key: 'appId', key: 'appId',
label: '绑定应用', label: '绑定应用',
@@ -526,6 +515,26 @@ const showAllPermission = (appId: string, item: ITopic, access: number) => {
validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus, 'allTopic'), validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus, 'allTopic'),
}], }],
}, },
// {
// key: 'clusterName',
// label: '集群名称',
// defaultValue: item.clusterName,
// rules: [{ required: true, message: '请输入集群名称' }],
// attrs: {
// placeholder: '请输入集群名称',
// disabled: true,
// },
// },
// {
// key: 'clusterName',
// label: '集群名称',
// defaultValue: item.clusterName,
// rules: [{ required: true, message: '请输入集群名称' }],
// attrs: {
// placeholder: '请输入集群名称',
// disabled: true,
// },
// },
{ {
key: 'description', key: 'description',
label: '申请原因', label: '申请原因',
@@ -587,16 +596,16 @@ export const showPermissionModal = (item: ITopic) => {
disabled: true, disabled: true,
}, },
}, },
{ // {
key: 'clusterName', // key: 'clusterName',
label: '集群名称', // label: '集群名称',
defaultValue: item.clusterName, // defaultValue: item.clusterName,
rules: [{ required: true, message: '请输入集群名称' }], // rules: [{ required: true, message: '请输入集群名称' }],
attrs: { // attrs: {
placeholder: '请输入集群名称', // placeholder: '请输入集群名称',
disabled: true, // disabled: true,
}, // },
}, // },
{ {
key: 'appName', key: 'appName',
label: '绑定应用', label: '绑定应用',

View File

@@ -126,7 +126,7 @@ export class SearchAndFilterContainer extends React.Component<any, ISearchAndFil
); );
} }
public renderSearch(text?: string, placeholder?: string, keyName: string = 'searchKey',) { public renderSearch(text?: string, placeholder?: string, keyName: string = 'searchKey') {
const value = this.state[keyName] as string; const value = this.state[keyName] as string;
return ( return (
<li className="render-box"> <li className="render-box">
@@ -194,7 +194,7 @@ export class SearchAndFilterContainer extends React.Component<any, ISearchAndFil
); );
} }
public renderColumnsFilter = (type: string) => { public renderColumnsFilter = (type: string, params?: any) => {
return { return {
filterIcon: this.renderFilterIcon.bind(null, type), filterIcon: this.renderFilterIcon.bind(null, type),
filterDropdownVisible: this.state[type] as boolean, filterDropdownVisible: this.state[type] as boolean,

View File

@@ -28,7 +28,8 @@ export class StaffSelect extends React.Component<IStaffSelectProps> {
public getStaffList = () => { public getStaffList = () => {
const { value } = this.props; const { value } = this.props;
const current = users.currentUser.username || getCookie('username'); const current = users.currentUser.username || getCookie('username');
const principals = value || (current ? [current] : []); const principals = [''];
// const principals = value || (current ? [current] : []);
const promises: any[] = []; const promises: any[] = [];
for (const item of principals) { for (const item of principals) {
@@ -64,7 +65,6 @@ export class StaffSelect extends React.Component<IStaffSelectProps> {
const { value, isDisabled } = this.props; const { value, isDisabled } = this.props;
const current = users.currentUser.username || getCookie('username'); const current = users.currentUser.username || getCookie('username');
const principals = value || (current ? [current] : []); const principals = value || (current ? [current] : []);
return ( return (
<Select <Select
mode="multiple" mode="multiple"
@@ -72,6 +72,7 @@ export class StaffSelect extends React.Component<IStaffSelectProps> {
defaultValue={principals} defaultValue={principals}
onChange={(e: string[]) => this.handleChange(e)} onChange={(e: string[]) => this.handleChange(e)}
onSearch={(e: string) => this.handleSearch(e)} onSearch={(e: string) => this.handleSearch(e)}
onFocus={() => this.getFocus()}
disabled={isDisabled} disabled={isDisabled}
{...searchProps} {...searchProps}
> >
@@ -83,6 +84,10 @@ export class StaffSelect extends React.Component<IStaffSelectProps> {
); );
} }
public getFocus() {
this.getStaffList();
}
public handleSearch(params: string) { public handleSearch(params: string) {
debounce(() => { debounce(() => {
getStaff(params).then((data: IStaff[]) => { getStaff(params).then((data: IStaff[]) => {
@@ -98,9 +103,9 @@ export class StaffSelect extends React.Component<IStaffSelectProps> {
}); });
}, 300)(); }, 300)();
} }
public handleChange(params: string[]) { public handleChange(params: string[]) {
const { onChange } = this.props; const { onChange } = this.props;
// tslint:disable-next-line:no-unused-expression // tslint:disable-next-line:no-unused-expression
onChange && onChange(params); onChange && onChange(params);
} }

View File

@@ -129,8 +129,9 @@ export class BaseInformation extends React.Component<IInfoProps> {
} }
public realTimeTraffic() { public realTimeTraffic() {
const realTraffic = topic.realTraffic; // const realTraffic = topic.realTraffic;
if (realTraffic) {
// if (realTraffic) {
return ( return (
<> <>
<Spin spinning={topic.realLoading}> <Spin spinning={topic.realLoading}>
@@ -138,7 +139,7 @@ export class BaseInformation extends React.Component<IInfoProps> {
</Spin> </Spin>
</> </>
); );
} // }
} }
public realTimeConsume() { public realTimeConsume() {

View File

@@ -82,10 +82,12 @@ export const getInfoRenderItem = (orderInfo: IOrderInfo, result: boolean) => {
const clusterInfoList: ILabelValue[] = [{ const clusterInfoList: ILabelValue[] = [{
label: '流入流量', label: '流入流量',
value: `${transBToMB(orderInfo.detail.bytesIn)} MB/s`, value: `${transBToMB(orderInfo.detail.bytesIn)} MB/s`,
}, { },
label: '数据中心', // {
value: orderInfo.detail.idc, // label: '数据中心',
}, { // value: orderInfo.detail.idc,
// },
{
label: '集群类型', label: '集群类型',
value: clusterTypeMap[orderInfo.detail.mode], value: clusterTypeMap[orderInfo.detail.mode],
}, { }, {

View File

@@ -133,6 +133,12 @@ export class OrderDetail extends React.Component {
width: '20%', width: '20%',
render: (t: string) => <span>{t === 'consumer' ? '消费' : '生产'}</span>, render: (t: string) => <span>{t === 'consumer' ? '消费' : '生产'}</span>,
}, },
// {
// title: '客户端语言',
// dataIndex: 'language',
// key: 'language',
// width: '20%',
// },
]; ];
return ( return (
<> <>

View File

@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
import { modal } from 'store/modal'; import { modal } from 'store/modal';
import { ConnectTopicList } from '../modal/connect-topic-list'; import { ConnectTopicList } from '../modal/connect-topic-list';
import { ConnectAppList } from '../modal/offline-app-modal'; import { ConnectAppList } from '../modal/offline-app-modal';
import { ConnectAppNewList } from '../modal/offline-app-modal-new';
import { CancelTopicPermission } from 'container/modal/cancel-topic-permission'; import { CancelTopicPermission } from 'container/modal/cancel-topic-permission';
import { OfflineClusterModal } from 'container/modal/offline-cluster-modal'; import { OfflineClusterModal } from 'container/modal/offline-cluster-modal';
import { RenderOrderOpResult } from 'container/modal/order'; import { RenderOrderOpResult } from 'container/modal/order';
@@ -22,6 +23,7 @@ export default class AllCustomModalInOne extends React.Component {
const modalMap = { const modalMap = {
offlineTopicModal: <ConnectTopicList />, offlineTopicModal: <ConnectTopicList />,
offlineAppNewModal: <ConnectAppNewList />,
offlineAppModal: <ConnectAppList />, offlineAppModal: <ConnectAppList />,
cancelTopicPermission: <CancelTopicPermission />, cancelTopicPermission: <CancelTopicPermission />,
offlineClusterModal: <OfflineClusterModal />, offlineClusterModal: <OfflineClusterModal />,

View File

@@ -418,6 +418,13 @@ export const getMetaData = (needDetail: boolean = true) => {
return fetch(`/rd/clusters/basic-info?need-detail=${needDetail}`); return fetch(`/rd/clusters/basic-info?need-detail=${needDetail}`);
}; };
export const getOperationRecordData = (params: any) => {
return fetch(`/rd/operate-record`,{
method: 'POST',
body: JSON.stringify(params),
});
};
export const getConfigure = () => { export const getConfigure = () => {
return fetch(`/rd/configs`); return fetch(`/rd/configs`);
}; };

View File

@@ -3,7 +3,7 @@ import CommonRoutePage from './common';
import urlParser from 'lib/url-parser'; import urlParser from 'lib/url-parser';
import urlQuery from 'store/url-query'; import urlQuery from 'store/url-query';
import { AppDetail } from 'container/app'; import { AppDetail } from 'container/app';
import { AdminAppList, ClusterList, ClusterDetail, BrokerDetail, UserManagement, VersionManagement, OperationManagement, OperationDetail, BillManagement, ConfigureManagement, IndividualBill, MigrationDetail, BillDetail } from 'container/admin'; import { AdminAppList, ClusterList, ClusterDetail, BrokerDetail, UserManagement, VersionManagement, OperationManagement, OperationDetail, BillManagement, ConfigureManagement, IndividualBill, MigrationDetail, BillDetail, OperationRecord } from 'container/admin';
import { PlatformManagement } from 'container/admin/platform-management'; import { PlatformManagement } from 'container/admin/platform-management';
export default class Home extends React.Component<any> { export default class Home extends React.Component<any> {
@@ -52,7 +52,11 @@ export default class Home extends React.Component<any> {
path: '/admin/migration-detail', path: '/admin/migration-detail',
exact: true, exact: true,
component: MigrationDetail, component: MigrationDetail,
}]; }, {
path: '/admin/operation-record',
exact: true,
component: OperationRecord,
},];
constructor(props: any) { constructor(props: any) {
super(props); super(props);

View File

@@ -9,6 +9,7 @@ import {
getTopicsBasicInfo, getTopicsBasicInfo,
getTasksKafkaFiles, getTasksKafkaFiles,
getMetaData, getMetaData,
getOperationRecordData,
getConfigure, getConfigure,
addNewConfigure, addNewConfigure,
editConfigure, editConfigure,
@@ -103,6 +104,14 @@ class Admin {
@observable @observable
public metaList: IMetaData[] = []; public metaList: IMetaData[] = [];
@observable
public oRList: any[] = [];
@observable
public oRparams:any={
moduleId:0
};
@observable @observable
public configureList: IConfigure[] = []; public configureList: IConfigure[] = [];
@@ -319,6 +328,15 @@ class Admin {
}) : []; }) : [];
} }
@action.bound
public setOperationRecordList(data:any){
this.setLoading(false);
this.oRList = data ? data.map((item:any, index: any) => {
item.key = index;
return item;
}) : [];
}
@action.bound @action.bound
public setConfigure(data: IConfigure[]) { public setConfigure(data: IConfigure[]) {
this.configureList = data ? data.map((item, index) => { this.configureList = data ? data.map((item, index) => {
@@ -657,6 +675,12 @@ class Admin {
getMetaData(needDetail).then(this.setMetaList); getMetaData(needDetail).then(this.setMetaList);
} }
public getOperationRecordData(params: any) {
this.setLoading(true);
this.oRparams = params
getOperationRecordData(params).then(this.setOperationRecordList);
}
public getConfigure() { public getConfigure() {
getConfigure().then(this.setConfigure); getConfigure().then(this.setConfigure);
} }

View File

@@ -31,6 +31,12 @@ class CustomModal {
this.params = value; this.params = value;
} }
@action.bound
public showOfflineAppNewModal(value: any) {
this.modalId = 'offlineAppNewModal';
this.params = value;
}
@action.bound @action.bound
public showOrderOpResult() { public showOrderOpResult() {
this.modalId = 'orderOpResult'; this.modalId = 'orderOpResult';

View File

@@ -6,6 +6,16 @@
url('//at.alicdn.com/t/font_1251424_q66z80q0hio.ttf?t=1577526422376') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('//at.alicdn.com/t/font_1251424_q66z80q0hio.ttf?t=1577526422376') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('//at.alicdn.com/t/font_1251424_q66z80q0hio.svg?t=1577526422376#kafka-manager') format('svg'); /* iOS 4.1- */ url('//at.alicdn.com/t/font_1251424_q66z80q0hio.svg?t=1577526422376#kafka-manager') format('svg'); /* iOS 4.1- */
} }
@font-face {
font-family: 'kafka-manager';
/* project id 2406313 */
src: url('//at.alicdn.com/t/font_2406313_rbsze6uqtta.eot');
src: url('//at.alicdn.com/t/font_2406313_rbsze6uqtta.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_2406313_rbsze6uqtta.woff2') format('woff2'),
url('//at.alicdn.com/t/font_2406313_rbsze6uqtta.woff') format('woff'),
url('//at.alicdn.com/t/font_2406313_rbsze6uqtta.ttf') format('truetype'),
url('//at.alicdn.com/t/font_2406313_rbsze6uqtta.svg#iconfont') format('svg');
}
.kafka-manager { .kafka-manager {
font-family: "kafka-manager" !important; font-family: "kafka-manager" !important;
@@ -15,6 +25,15 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
/* .kafka-manager-record {
font-family: "kafka-manager" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} */
.k-icon-fuwurenwuguanli:before { .k-icon-fuwurenwuguanli:before {
content: "\e660"; content: "\e660";
} }
@@ -47,6 +66,10 @@
content: "\e634"; content: "\e634";
} }
.k-icon-operationrecord:before {
content: "\e772";
}
.k-icon-menu2:before { .k-icon-menu2:before {
content: "\e609"; content: "\e609";
} }

View File

@@ -17,5 +17,5 @@ public interface OperateRecordService {
int insert(String operator, ModuleEnum module, String resourceName, OperateEnum operate, Map<String, String> content); int insert(String operator, ModuleEnum module, String resourceName, OperateEnum operate, Map<String, String> content);
List<OperateRecordDO> queryByCondt(OperateRecordDTO dto); List<OperateRecordDO> queryByCondition(OperateRecordDTO dto);
} }

View File

@@ -10,6 +10,7 @@ import com.xiaojukeji.kafka.manager.common.entity.ao.topic.MineTopicSummary;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicExpiredDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicExpiredDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicStatisticsDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicStatisticsDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -22,6 +23,8 @@ import java.util.Map;
public interface TopicManagerService { public interface TopicManagerService {
List<TopicDO> listAll(); List<TopicDO> listAll();
List<TopicDO> getByClusterIdFromCache(Long clusterId);
List<TopicDO> getByClusterId(Long clusterId); List<TopicDO> getByClusterId(Long clusterId);
TopicDO getByTopicName(Long clusterId, String topicName); TopicDO getByTopicName(Long clusterId, String topicName);
@@ -30,6 +33,15 @@ public interface TopicManagerService {
Map<String, List<Double>> getTopicMaxAvgBytesIn(Long clusterId, Integer latestDay, Double minMaxAvgBytesIn); Map<String, List<Double>> getTopicMaxAvgBytesIn(Long clusterId, Integer latestDay, Double minMaxAvgBytesIn);
/**
* 获取指定时间范围内Topic的峰值均值流量
* @param clusterId 集群ID
* @param topicName Topic名称
* @param startTime 开始时间
* @param endTime 结束时间
* @param maxAvgDay 最大几天的均值
* @return
*/
Double getTopicMaxAvgBytesIn(Long clusterId, String topicName, Date startTime, Date endTime, Integer maxAvgDay); Double getTopicMaxAvgBytesIn(Long clusterId, String topicName, Date startTime, Date endTime, Integer maxAvgDay);
TopicStatisticsDO getByTopicAndDay(Long clusterId, String topicName, String gmtDay); TopicStatisticsDO getByTopicAndDay(Long clusterId, String topicName, String gmtDay);
@@ -111,5 +123,12 @@ public interface TopicManagerService {
List<TopicStatisticsDO> getTopicStatistic(Long clusterId, String topicName, Date startTime, Date endTime); List<TopicStatisticsDO> getTopicStatistic(Long clusterId, String topicName, Date startTime, Date endTime);
TopicBusinessInfo getTopicBusinessInfo(Long physicalClusterId, String topicName); TopicBusinessInfo getTopicBusinessInfo(Long physicalClusterId, String topicName);
/**
* topic权限调整
* @param authorityDO topic权限
* @return
*/
ResultStatus addAuthority(AuthorityDO authorityDO);
} }

View File

@@ -105,4 +105,5 @@ public interface TopicService {
List<TopicBrokerDTO> getTopicBrokerList(Long clusterId, String topicName); List<TopicBrokerDTO> getTopicBrokerList(Long clusterId, String topicName);
Result<TopicOffsetChangedEnum> checkTopicOffsetChanged(Long physicalClusterId, String topicName, Long latestTime); Result<TopicOffsetChangedEnum> checkTopicOffsetChanged(Long physicalClusterId, String topicName, Long latestTime);
} }

View File

@@ -1,5 +1,6 @@
package com.xiaojukeji.kafka.manager.service.service.gateway; package com.xiaojukeji.kafka.manager.service.service.gateway;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota; import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
/** /**
@@ -34,4 +35,11 @@ public interface QuotaService {
TopicQuota getQuotaFromZk(Long clusterId, String topicName, String appId); TopicQuota getQuotaFromZk(Long clusterId, String topicName, String appId);
Boolean modifyProduceQuota(Long clusterId, String topicName, String appId, Long produceQuota); Boolean modifyProduceQuota(Long clusterId, String topicName, String appId, Long produceQuota);
/**
* topic配额调整
* @param topicQuota topic配额
* @return
*/
ResultStatus addTopicQuotaByAuthority(TopicQuota topicQuota);
} }

View File

@@ -1,16 +1,17 @@
package com.xiaojukeji.kafka.manager.service.service.gateway.impl; package com.xiaojukeji.kafka.manager.service.service.gateway.impl;
import com.alibaba.fastjson.JSONObject;
import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum; import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum; import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperationStatusEnum; import com.xiaojukeji.kafka.manager.common.bizenum.OperationStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicAuthorityEnum;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.entity.pojo.OperateRecordDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.OperateRecordDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.KafkaAclDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.KafkaAclDO;
import com.xiaojukeji.kafka.manager.dao.gateway.AuthorityDao; import com.xiaojukeji.kafka.manager.common.utils.JsonUtils;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils; import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.dao.gateway.AuthorityDao;
import com.xiaojukeji.kafka.manager.dao.gateway.KafkaAclDao; import com.xiaojukeji.kafka.manager.dao.gateway.KafkaAclDao;
import com.xiaojukeji.kafka.manager.service.service.OperateRecordService; import com.xiaojukeji.kafka.manager.service.service.OperateRecordService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService; import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
@@ -20,10 +21,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap; import java.util.stream.Collectors;
import java.util.List;
import java.util.Map;
/** /**
* @author zhongyuankai * @author zhongyuankai
@@ -120,7 +119,7 @@ public class AuthorityServiceImpl implements AuthorityService {
operateRecordDO.setModuleId(ModuleEnum.AUTHORITY.getCode()); operateRecordDO.setModuleId(ModuleEnum.AUTHORITY.getCode());
operateRecordDO.setOperateId(OperateEnum.DELETE.getCode()); operateRecordDO.setOperateId(OperateEnum.DELETE.getCode());
operateRecordDO.setResource(topicName); operateRecordDO.setResource(topicName);
operateRecordDO.setContent(JSONObject.toJSONString(content)); operateRecordDO.setContent(JsonUtils.toJSONString(content));
operateRecordDO.setOperator(operator); operateRecordDO.setOperator(operator);
operateRecordService.insert(operateRecordDO); operateRecordService.insert(operateRecordDO);
} catch (Exception e) { } catch (Exception e) {
@@ -150,7 +149,7 @@ public class AuthorityServiceImpl implements AuthorityService {
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("get authority failed, clusterId:{} topicName:{}.", clusterId, topicName, e); LOGGER.error("get authority failed, clusterId:{} topicName:{}.", clusterId, topicName, e);
} }
return null; return Collections.emptyList();
} }
@Override @Override
@@ -164,7 +163,11 @@ public class AuthorityServiceImpl implements AuthorityService {
if (ValidateUtils.isEmptyList(doList)) { if (ValidateUtils.isEmptyList(doList)) {
return new ArrayList<>(); return new ArrayList<>();
} }
return doList;
// 去除掉权限列表中无权限的数据
return doList.stream()
.filter(authorityDO -> !TopicAuthorityEnum.DENY.getCode().equals(authorityDO.getAccess()))
.collect(Collectors.toList());
} }
@Override @Override

View File

@@ -86,8 +86,8 @@ public class GatewayConfigServiceImpl implements GatewayConfigService {
GatewayConfigDO configDO = null; GatewayConfigDO configDO = null;
try { try {
configDO = gatewayConfigDao.getByConfigTypeAndName( configDO = gatewayConfigDao.getByConfigTypeAndName(
GatewayConfigKeyEnum.SD_APP_ID_RATE.getConfigType(), GatewayConfigKeyEnum.SD_APP_RATE.getConfigType(),
GatewayConfigKeyEnum.SD_APP_ID_RATE.getConfigName() GatewayConfigKeyEnum.SD_APP_RATE.getConfigName()
); );
if (ValidateUtils.isNull(configDO) || configDO.getVersion() <= requestVersion) { if (ValidateUtils.isNull(configDO) || configDO.getVersion() <= requestVersion) {
return new AppRateConfig(Long.MIN_VALUE, null); return new AppRateConfig(Long.MIN_VALUE, null);

View File

@@ -1,11 +1,16 @@
package com.xiaojukeji.kafka.manager.service.service.gateway.impl; package com.xiaojukeji.kafka.manager.service.service.gateway.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicAuthorityEnum;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota; import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.utils.NumberUtils; import com.xiaojukeji.kafka.manager.common.utils.NumberUtils;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils; import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.config.TopicQuotaData; import com.xiaojukeji.kafka.manager.common.zookeeper.znode.config.TopicQuotaData;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO; import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.service.cache.LogicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager; import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.gateway.QuotaService; import com.xiaojukeji.kafka.manager.service.service.gateway.QuotaService;
import com.xiaojukeji.kafka.manager.service.service.TopicManagerService; import com.xiaojukeji.kafka.manager.service.service.TopicManagerService;
import com.xiaojukeji.kafka.manager.service.strategy.AbstractAllocateQuotaStrategy; import com.xiaojukeji.kafka.manager.service.strategy.AbstractAllocateQuotaStrategy;
@@ -28,6 +33,12 @@ public class QuotaServiceImpl implements QuotaService {
@Autowired @Autowired
private AbstractAllocateQuotaStrategy allocateQuotaStrategy; private AbstractAllocateQuotaStrategy allocateQuotaStrategy;
@Autowired
private LogicalClusterMetadataManager logicalClusterMetadataManager;
@Autowired
private AuthorityService authorityService;
@Override @Override
public int addTopicQuota(TopicQuota topicQuotaDO) { public int addTopicQuota(TopicQuota topicQuotaDO) {
return KafkaZookeeperUtils.setTopicQuota( return KafkaZookeeperUtils.setTopicQuota(
@@ -78,4 +89,34 @@ public class QuotaServiceImpl implements QuotaService {
} }
return Boolean.TRUE; return Boolean.TRUE;
} }
@Override
public ResultStatus addTopicQuotaByAuthority(TopicQuota topicQuota) {
// 获取物理集群id
Long physicalClusterId = logicalClusterMetadataManager.getPhysicalClusterId(topicQuota.getClusterId());
if (ValidateUtils.isNull(physicalClusterId)) {
return ResultStatus.CLUSTER_NOT_EXIST;
}
// 权限判断(access 0:无权限, 1:读, 2:写, 3:读写4:可管理)
AuthorityDO authority = authorityService.getAuthority(physicalClusterId,
topicQuota.getTopicName(), topicQuota.getAppId());
if (ValidateUtils.isNull(authority) || authority.getAccess() == TopicAuthorityEnum.DENY.getCode()) {
return ResultStatus.USER_WITHOUT_AUTHORITY;
}
if (authority.getAccess() == TopicAuthorityEnum.READ.getCode()) {
// 可以消费
topicQuota.setProduceQuota(null);
}
if (authority.getAccess() == TopicAuthorityEnum.WRITE.getCode()) {
// 可以生产
topicQuota.setConsumeQuota(null);
}
// 设置物理集群id
topicQuota.setClusterId(physicalClusterId);
// 添加配额
if (addTopicQuota(topicQuota) > 0) {
return ResultStatus.SUCCESS;
}
return ResultStatus.ZOOKEEPER_WRITE_FAILED;
}
} }

View File

@@ -26,21 +26,27 @@ public class TopicConnectionServiceImpl implements TopicConnectionService {
@Autowired @Autowired
private TopicConnectionDao topicConnectionDao; private TopicConnectionDao topicConnectionDao;
private int splitInterval = 50;
@Override @Override
public void batchAdd(List<TopicConnectionDO> doList) { public void batchAdd(List<TopicConnectionDO> doList) {
if (ValidateUtils.isEmptyList(doList)) { if (ValidateUtils.isEmptyList(doList)) {
return; return;
} }
int allSize = doList.size();
int successSize = 0;
int count = 0; int part = doList.size() / splitInterval;
for (TopicConnectionDO connectionDO: doList) { for (int i = 0; i < part; ++i) {
try { List<TopicConnectionDO> subList = doList.subList(0, splitInterval);
count += topicConnectionDao.replace(connectionDO); successSize += topicConnectionDao.batchReplace(subList);
} catch (Exception e) { doList.subList(0, splitInterval).clear();
LOGGER.error("class=TopicConnectionServiceImpl||method=batchAdd||connectionDO={}||errMsg={}", connectionDO, e.getMessage());
} }
if (!ValidateUtils.isEmptyList(doList)) {
successSize += topicConnectionDao.batchReplace(doList);
} }
LOGGER.info("class=TopicConnectionServiceImpl||method=batchAdd||allSize={}||successSize={}", doList.size(), count); LOGGER.info("class=TopicConnectionServiceImpl||method=batchAdd||allSize={}||successSize={}", allSize, successSize);
} }
@Override @Override

View File

@@ -66,7 +66,10 @@ public class AdminServiceImpl implements AdminService {
String applicant, String applicant,
String operator) { String operator) {
List<Integer> fullBrokerIdList = regionService.getFullBrokerIdList(clusterDO.getId(), regionId, brokerIdList); List<Integer> fullBrokerIdList = regionService.getFullBrokerIdList(clusterDO.getId(), regionId, brokerIdList);
if (PhysicalClusterMetadataManager.getNotAliveBrokerNum(clusterDO.getId(), fullBrokerIdList) > DEFAULT_DEAD_BROKER_LIMIT_NUM) {
Long notAliveBrokerNum = PhysicalClusterMetadataManager.getNotAliveBrokerNum(clusterDO.getId(), fullBrokerIdList);
if (notAliveBrokerNum >= fullBrokerIdList.size() || notAliveBrokerNum > DEFAULT_DEAD_BROKER_LIMIT_NUM) {
// broker全挂了或者是挂的数量大于了DEFAULT_DEAD_BROKER_LIMIT_NUM时, 则认为broker参数不合法
return ResultStatus.BROKER_NOT_EXIST; return ResultStatus.BROKER_NOT_EXIST;
} }

View File

@@ -91,7 +91,7 @@ public class BrokerServiceImpl implements BrokerService {
for (BrokerDO brokerDO : brokerDOList) { for (BrokerDO brokerDO : brokerDOList) {
PeakFlowStatusEnum peakFlowStatus = getPeakFlowStatus(brokerDO.getMaxAvgBytesIn(), peakFlow); PeakFlowStatusEnum peakFlowStatus = getPeakFlowStatus(brokerDO.getMaxAvgBytesIn(), peakFlow);
peakFlowStatusMap.put( peakFlowStatusMap.put(
peakFlowStatus.code, peakFlowStatus.getCode(),
peakFlowStatusMap.getOrDefault(peakFlowStatus.getCode(), 0) + 1 peakFlowStatusMap.getOrDefault(peakFlowStatus.getCode(), 0) + 1
); );
} }

View File

@@ -82,6 +82,7 @@ public class ClusterServiceImpl implements ClusterService {
content.put("security properties", clusterDO.getSecurityProperties()); content.put("security properties", clusterDO.getSecurityProperties());
content.put("jmx properties", clusterDO.getJmxProperties()); content.put("jmx properties", clusterDO.getJmxProperties());
operateRecordService.insert(operator, ModuleEnum.CLUSTER, clusterDO.getClusterName(), OperateEnum.ADD, content); operateRecordService.insert(operator, ModuleEnum.CLUSTER, clusterDO.getClusterName(), OperateEnum.ADD, content);
if (clusterDao.insert(clusterDO) <= 0) { if (clusterDao.insert(clusterDO) <= 0) {
LOGGER.error("add new cluster failed, clusterDO:{}.", clusterDO); LOGGER.error("add new cluster failed, clusterDO:{}.", clusterDO);
return ResultStatus.MYSQL_ERROR; return ResultStatus.MYSQL_ERROR;

View File

@@ -41,8 +41,8 @@ public class OperateRecordServiceImpl implements OperateRecordService {
} }
@Override @Override
public List<OperateRecordDO> queryByCondt(OperateRecordDTO dto) { public List<OperateRecordDO> queryByCondition(OperateRecordDTO dto) {
return operateRecordDao.queryByCondt( return operateRecordDao.queryByCondition(
dto.getModuleId(), dto.getModuleId(),
dto.getOperateId(), dto.getOperateId(),
dto.getOperator(), dto.getOperator(),

View File

@@ -20,6 +20,7 @@ import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.utils.DateUtils; import com.xiaojukeji.kafka.manager.common.utils.DateUtils;
import com.xiaojukeji.kafka.manager.common.utils.JsonUtils; import com.xiaojukeji.kafka.manager.common.utils.JsonUtils;
import com.xiaojukeji.kafka.manager.common.utils.NumberUtils; import com.xiaojukeji.kafka.manager.common.utils.NumberUtils;
import com.xiaojukeji.kafka.manager.common.utils.SpringTool;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils; import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata; import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.config.TopicQuotaData; import com.xiaojukeji.kafka.manager.common.zookeeper.znode.config.TopicQuotaData;
@@ -95,6 +96,14 @@ public class TopicManagerServiceImpl implements TopicManagerService {
return new ArrayList<>(); return new ArrayList<>();
} }
@Override
public List<TopicDO> getByClusterIdFromCache(Long clusterId) {
if (clusterId == null) {
return new ArrayList<>();
}
return topicDao.getByClusterIdFromCache(clusterId);
}
@Override @Override
public List<TopicDO> getByClusterId(Long clusterId) { public List<TopicDO> getByClusterId(Long clusterId) {
if (clusterId == null) { if (clusterId == null) {
@@ -139,12 +148,14 @@ public class TopicManagerServiceImpl implements TopicManagerService {
} }
@Override @Override
public Double getTopicMaxAvgBytesIn(Long clusterId, public Double getTopicMaxAvgBytesIn(Long clusterId, String topicName, Date startTime, Date endTime, Integer maxAvgDay) {
String topicName, try {
Date startTime,
Date endTime,
Integer maxAvgDay) {
return topicStatisticsDao.getTopicMaxAvgBytesIn(clusterId, topicName, startTime, endTime, maxAvgDay); return topicStatisticsDao.getTopicMaxAvgBytesIn(clusterId, topicName, startTime, endTime, maxAvgDay);
} catch (Exception e) {
LOGGER.error("class=TopicManagerServiceImpl||method=getTopicMaxAvgBytesIn||clusterId={}||topicName={}||startTime={}||endTime={}||maxAvgDay={}||errMsg={}",
clusterId, topicName, startTime, endTime, maxAvgDay, e.getMessage());
}
return null;
} }
@Override @Override
@@ -608,6 +619,38 @@ public class TopicManagerServiceImpl implements TopicManagerService {
return topicBusinessInfo; return topicBusinessInfo;
} }
@Override
public ResultStatus addAuthority(AuthorityDO authorityDO) {
// 查询该用户拥有的应用
List<AppDO> appDOs = appService.getByPrincipal(SpringTool.getUserName());
if (ValidateUtils.isEmptyList(appDOs)) {
// 该用户无应用,需要先申请应用
return ResultStatus.APP_NOT_EXIST;
}
List<Long> appIds = appDOs.stream().map(AppDO::getId).collect(Collectors.toList());
if (!appIds.contains(authorityDO.getAppId())) {
// 入参中的appId该用户未拥有
return ResultStatus.APP_NOT_EXIST;
}
// 获取物理集群id
Long physicalClusterId = logicalClusterMetadataManager.getPhysicalClusterId(authorityDO.getClusterId());
if (ValidateUtils.isNull(physicalClusterId)) {
// 集群不存在
return ResultStatus.CLUSTER_NOT_EXIST;
}
TopicDO topic = getByTopicName(physicalClusterId, authorityDO.getTopicName());
if (ValidateUtils.isNull(topic)) {
// topic不存在
return ResultStatus.TOPIC_NOT_EXIST;
}
// 设置物理集群id
authorityDO.setClusterId(physicalClusterId);
if (authorityService.addAuthority(authorityDO) > 0) {
return ResultStatus.SUCCESS;
}
return ResultStatus.MYSQL_ERROR;
}
private RdTopicBasic convert2RdTopicBasic(ClusterDO clusterDO, private RdTopicBasic convert2RdTopicBasic(ClusterDO clusterDO,
String topicName, String topicName,
TopicDO topicDO, TopicDO topicDO,

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