diff --git a/document/contact/weixin_contact.jpg b/document/contact/weixin_contact.jpg index 7998ce8..58f2319 100644 Binary files a/document/contact/weixin_contact.jpg and b/document/contact/weixin_contact.jpg differ diff --git a/pom.xml b/pom.xml index 3b56fc9..a2b6ae5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xuxd kafka-console-ui - 1.0.5 + 1.0.6 kafka-console-ui Kafka console manage ui diff --git a/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java b/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java index f6ba89b..0c9ee4f 100644 --- a/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java +++ b/src/main/java/com/xuxd/kafka/console/controller/AclAuthController.java @@ -118,4 +118,14 @@ public class AclAuthController { return aclService.deleteConsumerAcl(param.toTopicEntry(), param.toGroupEntry()); } + /** + * clear principal acls. + * + * @param param acl principal. + * @return true or false. + */ + @DeleteMapping("/clear") + public Object clearAcl(@RequestBody DeleteAclDTO param) { + return aclService.clearAcl(param.toUserEntry()); + } } diff --git a/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java b/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java index da6e7d8..7570287 100644 --- a/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java +++ b/src/main/java/com/xuxd/kafka/console/controller/AclUserController.java @@ -1,7 +1,9 @@ package com.xuxd.kafka.console.controller; +import com.xuxd.kafka.console.beans.AclEntry; import com.xuxd.kafka.console.beans.AclUser; import com.xuxd.kafka.console.service.AclService; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -12,7 +14,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** - * kafka-console-ui. + * kafka-console-ui. sasl scram user. * * @author xuxd * @date 2021-08-28 21:13:05 @@ -49,4 +51,11 @@ public class AclUserController { public Object getUserDetail(@RequestParam String username) { return aclService.getUserDetail(username); } + + @GetMapping("/scram") + public Object getSaslScramUserList(@RequestParam(required = false) String username) { + AclEntry entry = new AclEntry(); + entry.setPrincipal(StringUtils.isNotBlank(username) ? username : null); + return aclService.getSaslScramUserList(entry); + } } diff --git a/src/main/java/com/xuxd/kafka/console/interceptor/ContextSetFilter.java b/src/main/java/com/xuxd/kafka/console/interceptor/ContextSetFilter.java index 43421c2..8b2ed58 100644 --- a/src/main/java/com/xuxd/kafka/console/interceptor/ContextSetFilter.java +++ b/src/main/java/com/xuxd/kafka/console/interceptor/ContextSetFilter.java @@ -72,6 +72,7 @@ public class ContextSetFilter implements Filter { config.setProperties(ConvertUtil.toProperties(infoDO.getProperties())); } ContextConfigHolder.CONTEXT_CONFIG.set(config); +// log.info("current kafka config: {}", config); } } chain.doFilter(req, response); diff --git a/src/main/java/com/xuxd/kafka/console/service/AclService.java b/src/main/java/com/xuxd/kafka/console/service/AclService.java index 3949724..2fd66ff 100644 --- a/src/main/java/com/xuxd/kafka/console/service/AclService.java +++ b/src/main/java/com/xuxd/kafka/console/service/AclService.java @@ -42,4 +42,7 @@ public interface AclService { ResponseData getUserDetail(String username); + ResponseData clearAcl(AclEntry entry); + + ResponseData getSaslScramUserList(AclEntry entry); } diff --git a/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java b/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java index 81a5cc3..dd3e6a2 100644 --- a/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java +++ b/src/main/java/com/xuxd/kafka/console/service/impl/AclServiceImpl.java @@ -10,30 +10,23 @@ import com.xuxd.kafka.console.config.ContextConfigHolder; import com.xuxd.kafka.console.dao.KafkaUserMapper; import com.xuxd.kafka.console.service.AclService; import com.xuxd.kafka.console.utils.SaslUtil; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.stream.Collectors; import kafka.console.KafkaAclConsole; import kafka.console.KafkaConfigConsole; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.apache.kafka.clients.CommonClientConfigs; -import org.apache.kafka.clients.admin.ScramMechanism; import org.apache.kafka.clients.admin.UserScramCredentialsDescription; import org.apache.kafka.common.acl.AclBinding; import org.apache.kafka.common.acl.AclOperation; import org.apache.kafka.common.config.SaslConfigs; -import org.apache.kafka.common.security.auth.SecurityProtocol; +import org.apache.kafka.common.errors.SecurityDisabledException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import scala.Tuple2; +import java.util.*; +import java.util.stream.Collectors; + import static com.xuxd.kafka.console.utils.SaslUtil.isEnableSasl; import static com.xuxd.kafka.console.utils.SaslUtil.isEnableScram; @@ -139,39 +132,52 @@ public class AclServiceImpl implements AclService { } @Override public ResponseData getAclList(AclEntry entry) { - List aclBindingList = entry.isNull() ? aclConsole.getAclList(null) : aclConsole.getAclList(entry); + List aclBindingList = Collections.emptyList(); + try { + aclBindingList = entry.isNull() ? aclConsole.getAclList(null) : aclConsole.getAclList(entry); + }catch (Exception ex) { + if (ex.getCause() instanceof SecurityDisabledException) { + Throwable e = ex.getCause(); + log.info("SecurityDisabledException: {}", e.getMessage()); + Map hint = new HashMap<>(2); + hint.put("hint", "Security Disabled: " + e.getMessage()); + return ResponseData.create().data(hint).success(); + } + throw new RuntimeException(ex.getCause()); + } +// List aclBindingList = entry.isNull() ? aclConsole.getAclList(null) : aclConsole.getAclList(entry); List entryList = aclBindingList.stream().map(x -> AclEntry.valueOf(x)).collect(Collectors.toList()); Map> entryMap = entryList.stream().collect(Collectors.groupingBy(AclEntry::getPrincipal)); Map resultMap = new HashMap<>(); entryMap.forEach((k, v) -> { Map> map = v.stream().collect(Collectors.groupingBy(e -> e.getResourceType() + "#" + e.getName())); - String username = SaslUtil.findUsername(ContextConfigHolder.CONTEXT_CONFIG.get().getProperties().getProperty(SaslConfigs.SASL_JAAS_CONFIG)); - if (k.equals(username)) { - Map map2 = new HashMap<>(map); - Map userMap = new HashMap<>(); - userMap.put("role", "admin"); - map2.put("USER", userMap); - } +// String username = SaslUtil.findUsername(ContextConfigHolder.CONTEXT_CONFIG.get().getProperties().getProperty(SaslConfigs.SASL_JAAS_CONFIG)); +// if (k.equals(username)) { +// Map map2 = new HashMap<>(map); +// Map userMap = new HashMap<>(); +// userMap.put("role", "admin"); +// map2.put("USER", userMap); +// } resultMap.put(k, map); }); - if (entry.isNull() || StringUtils.isNotBlank(entry.getPrincipal())) { - Map detailList = configConsole.getUserDetailList(StringUtils.isNotBlank(entry.getPrincipal()) ? Collections.singletonList(entry.getPrincipal()) : null); - - detailList.values().forEach(u -> { - if (!resultMap.containsKey(u.name()) && !u.credentialInfos().isEmpty()) { - String username = SaslUtil.findUsername(ContextConfigHolder.CONTEXT_CONFIG.get().getProperties().getProperty(SaslConfigs.SASL_JAAS_CONFIG)); - if (!u.name().equals(username)) { - resultMap.put(u.name(), Collections.emptyMap()); - } else { - Map map2 = new HashMap<>(); - Map userMap = new HashMap<>(); - userMap.put("role", "admin"); - map2.put("USER", userMap); - resultMap.put(u.name(), map2); - } - } - }); - } +// if (entry.isNull() || StringUtils.isNotBlank(entry.getPrincipal())) { +// Map detailList = configConsole.getUserDetailList(StringUtils.isNotBlank(entry.getPrincipal()) ? Collections.singletonList(entry.getPrincipal()) : null); +// +// detailList.values().forEach(u -> { +// if (!resultMap.containsKey(u.name()) && !u.credentialInfos().isEmpty()) { +// String username = SaslUtil.findUsername(ContextConfigHolder.CONTEXT_CONFIG.get().getProperties().getProperty(SaslConfigs.SASL_JAAS_CONFIG)); +// if (!u.name().equals(username)) { +// resultMap.put(u.name(), Collections.emptyMap()); +// } else { +// Map map2 = new HashMap<>(); +// Map userMap = new HashMap<>(); +// userMap.put("role", "admin"); +// map2.put("USER", userMap); +// resultMap.put(u.name(), map2); +// } +// } +// }); +// } return ResponseData.create().data(new CounterMap<>(resultMap)).success(); } @@ -236,6 +242,37 @@ public class AclServiceImpl implements AclService { return ResponseData.create().data(vo).success(); } + @Override + public ResponseData clearAcl(AclEntry entry) { + log.info("Start clear acl, principal: {}", entry); + return aclConsole.deleteUserAcl(entry) ? ResponseData.create().success() : ResponseData.create().failed("操作失败"); + } + + @Override + public ResponseData getSaslScramUserList(AclEntry entry) { + Map resultMap = new HashMap<>(); + if (entry.isNull() || StringUtils.isNotBlank(entry.getPrincipal())) { + Map detailList = configConsole.getUserDetailList(StringUtils.isNotBlank(entry.getPrincipal()) ? Collections.singletonList(entry.getPrincipal()) : null); + + detailList.values().forEach(u -> { + if (!resultMap.containsKey(u.name()) && !u.credentialInfos().isEmpty()) { + String username = SaslUtil.findUsername(ContextConfigHolder.CONTEXT_CONFIG.get().getProperties().getProperty(SaslConfigs.SASL_JAAS_CONFIG)); + if (!u.name().equals(username)) { + resultMap.put(u.name(), Collections.emptyMap()); + } else { + Map map2 = new HashMap<>(); + Map userMap = new HashMap<>(); + userMap.put("role", "admin"); + map2.put("USER", userMap); + resultMap.put(u.name(), map2); + } + } + }); + } + + return ResponseData.create().data(new CounterMap<>(resultMap)).success(); + } + // @Override public void afterSingletonsInstantiated() { // if (kafkaConfig.isEnableAcl() && kafkaConfig.isAdminCreate()) { // log.info("Start create admin user, username: {}, password: {}", kafkaConfig.getAdminUsername(), kafkaConfig.getAdminPassword()); diff --git a/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java b/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java index e4c8bb2..4d5661e 100644 --- a/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java +++ b/src/main/java/com/xuxd/kafka/console/service/impl/ClusterServiceImpl.java @@ -73,6 +73,10 @@ public class ClusterServiceImpl implements ClusterService { } @Override public ResponseData updateClusterInfo(ClusterInfoDO infoDO) { + if (infoDO.getProperties() == null) { + // null 的话不更新,这个是bug,设置为空字符串解决 + infoDO.setProperties(""); + } clusterInfoMapper.updateById(infoDO); return ResponseData.create().success(); } diff --git a/ui/src/App.vue b/ui/src/App.vue index 540f245..a0c25e6 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -11,10 +11,8 @@ >消费组 |消息 - |Acl + |Acl |运维 集群:{{ clusterName }} diff --git a/ui/src/utils/api.js b/ui/src/utils/api.js index 79d1090..d86e452 100644 --- a/ui/src/utils/api.js +++ b/ui/src/utils/api.js @@ -47,6 +47,18 @@ export const KafkaAclApi = { url: "/acl", method: "delete", }, + clearAcl: { + url: "/acl/clear", + method: "delete", + }, + getSaslScramUserList: { + url: "/user/scram", + method: "get", + }, + deleteSaslScramUser: { + url: "/user", + method: "delete", + }, }; export const KafkaConfigApi = { diff --git a/ui/src/views/acl/Acl.vue b/ui/src/views/acl/Acl.vue index bc50f85..ded2688 100644 --- a/ui/src/views/acl/Acl.vue +++ b/ui/src/views/acl/Acl.vue @@ -1,461 +1,36 @@ - - diff --git a/ui/src/views/acl/AclList.vue b/ui/src/views/acl/AclList.vue new file mode 100644 index 0000000..b3365f8 --- /dev/null +++ b/ui/src/views/acl/AclList.vue @@ -0,0 +1,444 @@ + + + + + diff --git a/ui/src/views/acl/AddPrincipalAuth.vue b/ui/src/views/acl/AddPrincipalAuth.vue new file mode 100644 index 0000000..91f3731 --- /dev/null +++ b/ui/src/views/acl/AddPrincipalAuth.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/ui/src/views/acl/SaslScram.vue b/ui/src/views/acl/SaslScram.vue new file mode 100644 index 0000000..4caa72e --- /dev/null +++ b/ui/src/views/acl/SaslScram.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/ui/src/views/op/AddClusterInfo.vue b/ui/src/views/op/AddClusterInfo.vue index 030d691..cdd0da3 100644 --- a/ui/src/views/op/AddClusterInfo.vue +++ b/ui/src/views/op/AddClusterInfo.vue @@ -132,9 +132,9 @@ export default { if ( clusterInfo && clusterInfo.id && - clusterInfo.id == this.clusterInfo.id && - clusterInfo.clusterName != data.clusterName + clusterInfo.id == this.clusterInfo.id ) { + // &&clusterInfo.clusterName != data.clusterName this.switchCluster(data); } } diff --git a/ui/src/views/topic/Topic.vue b/ui/src/views/topic/Topic.vue index 919c509..0e1e96c 100644 --- a/ui/src/views/topic/Topic.vue +++ b/ui/src/views/topic/Topic.vue @@ -50,25 +50,33 @@ >新增 - + 批量删除 - - + + @@ -282,7 +290,7 @@ export default { request({ url: KafkaTopicApi.deleteTopic.url, method: KafkaTopicApi.deleteTopic.method, - data: topics + data: topics, }).then((res) => { if (res.code == 0) { this.$message.success(res.msg); @@ -297,7 +305,7 @@ export default { }); }, deleteTopic(topic) { - this.deleteTopics([topic]) + this.deleteTopics([topic]); }, onTopicUpdate(input) { this.filterTopic = input.target.value;