ACL的认证和授权管理功能分离.
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 205 KiB |
2
pom.xml
2
pom.xml
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.xuxd</groupId>
|
||||
<artifactId>kafka-console-ui</artifactId>
|
||||
<version>1.0.5</version>
|
||||
<version>1.0.6</version>
|
||||
<name>kafka-console-ui</name>
|
||||
<description>Kafka console manage ui</description>
|
||||
<properties>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -42,4 +42,7 @@ public interface AclService {
|
||||
|
||||
ResponseData getUserDetail(String username);
|
||||
|
||||
ResponseData clearAcl(AclEntry entry);
|
||||
|
||||
ResponseData getSaslScramUserList(AclEntry entry);
|
||||
}
|
||||
|
||||
@@ -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<AclBinding> aclBindingList = entry.isNull() ? aclConsole.getAclList(null) : aclConsole.getAclList(entry);
|
||||
List<AclBinding> 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<String, String> hint = new HashMap<>(2);
|
||||
hint.put("hint", "Security Disabled: " + e.getMessage());
|
||||
return ResponseData.create().data(hint).success();
|
||||
}
|
||||
throw new RuntimeException(ex.getCause());
|
||||
}
|
||||
// List<AclBinding> aclBindingList = entry.isNull() ? aclConsole.getAclList(null) : aclConsole.getAclList(entry);
|
||||
List<AclEntry> entryList = aclBindingList.stream().map(x -> AclEntry.valueOf(x)).collect(Collectors.toList());
|
||||
Map<String, List<AclEntry>> entryMap = entryList.stream().collect(Collectors.groupingBy(AclEntry::getPrincipal));
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
entryMap.forEach((k, v) -> {
|
||||
Map<String, List<AclEntry>> 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<String, Object> map2 = new HashMap<>(map);
|
||||
Map<String, Object> 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<String, Object> map2 = new HashMap<>(map);
|
||||
// Map<String, Object> userMap = new HashMap<>();
|
||||
// userMap.put("role", "admin");
|
||||
// map2.put("USER", userMap);
|
||||
// }
|
||||
resultMap.put(k, map);
|
||||
});
|
||||
if (entry.isNull() || StringUtils.isNotBlank(entry.getPrincipal())) {
|
||||
Map<String, UserScramCredentialsDescription> 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<String, Object> map2 = new HashMap<>();
|
||||
Map<String, Object> userMap = new HashMap<>();
|
||||
userMap.put("role", "admin");
|
||||
map2.put("USER", userMap);
|
||||
resultMap.put(u.name(), map2);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (entry.isNull() || StringUtils.isNotBlank(entry.getPrincipal())) {
|
||||
// Map<String, UserScramCredentialsDescription> 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<String, Object> map2 = new HashMap<>();
|
||||
// Map<String, Object> 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<String, Object> resultMap = new HashMap<>();
|
||||
if (entry.isNull() || StringUtils.isNotBlank(entry.getPrincipal())) {
|
||||
Map<String, UserScramCredentialsDescription> 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<String, Object> map2 = new HashMap<>();
|
||||
Map<String, Object> 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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -11,10 +11,8 @@
|
||||
><router-link to="/group-page" class="pad-l-r">消费组</router-link>
|
||||
<span>|</span
|
||||
><router-link to="/message-page" class="pad-l-r">消息</router-link>
|
||||
<span v-show="enableSasl">|</span
|
||||
><router-link to="/acl-page" class="pad-l-r" v-show="enableSasl"
|
||||
>Acl</router-link
|
||||
>
|
||||
<span>|</span
|
||||
><router-link to="/acl-page" class="pad-l-r">Acl</router-link>
|
||||
<span>|</span
|
||||
><router-link to="/op-page" class="pad-l-r">运维</router-link>
|
||||
<span class="right">集群:{{ clusterName }}</span>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -1,461 +1,36 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="acl">
|
||||
<div id="components-form-acl-advanced-search">
|
||||
<a-form
|
||||
class="ant-advanced-search-form"
|
||||
:form="form"
|
||||
@submit="handleSearch"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`用户名`">
|
||||
<a-input
|
||||
placeholder="username"
|
||||
class="input-w"
|
||||
v-decorator="['username']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`topic`">
|
||||
<a-input
|
||||
placeholder="topic"
|
||||
class="input-w"
|
||||
v-decorator="['topic']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`消费组`">
|
||||
<a-input
|
||||
placeholder="groupId"
|
||||
class="input-w"
|
||||
v-decorator="['groupId']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24" :style="{ textAlign: 'right' }">
|
||||
<a-button type="primary" html-type="submit"> 搜索</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<a-tabs default-active-key="1" size="large" tabPosition="top">
|
||||
<a-tab-pane key="1" tab="资源授权">
|
||||
<acl-list></acl-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="SaslScram用户管理">
|
||||
<div v-show="enableSasl">
|
||||
<sasl-scram></sasl-scram>
|
||||
</div>
|
||||
<div class="operation-row-button">
|
||||
<a-button type="primary" @click="updateUser">新增/更新用户</a-button>
|
||||
<UpdateUser
|
||||
:visible="showUpdateUser"
|
||||
@updateUserDialogData="closeUpdateUserDialog"
|
||||
></UpdateUser>
|
||||
<div v-show="!enableSasl">
|
||||
<h2>未启用SASL SCRAM认证,不支持该认证的用户管理操作</h2>
|
||||
</div>
|
||||
<a-table :columns="columns" :data-source="data" bordered>
|
||||
<div slot="username" slot-scope="username">
|
||||
<span>{{ username }}</span
|
||||
><a-button
|
||||
size="small"
|
||||
shape="round"
|
||||
type="dashed"
|
||||
style="float: right"
|
||||
@click="onUserDetail(username)"
|
||||
>详情</a-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div slot="topicList" slot-scope="topicList, record">
|
||||
<a
|
||||
href="#"
|
||||
v-for="t in topicList"
|
||||
:key="t"
|
||||
@click="onTopicDetail(t, record.username)"
|
||||
><div style="border-bottom: 1px solid #e5e1e1">{{ t }}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div slot="groupList" slot-scope="groupList, record">
|
||||
<a
|
||||
href="#"
|
||||
v-for="t in groupList"
|
||||
:key="t"
|
||||
@click="onGroupDetail(t, record.username)"
|
||||
><div style="border-bottom: 1px solid #e5e1e1">{{ t }}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
slot="operation"
|
||||
slot-scope="record"
|
||||
v-show="!record.user || record.user.role != 'admin'"
|
||||
>
|
||||
<a-popconfirm
|
||||
:title="'删除用户: ' + record.username + '及相关权限?'"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="onDeleteUser(record)"
|
||||
>
|
||||
<a-button size="small" href="javascript:;" class="operation-btn"
|
||||
>删除</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onManageProducerAuth(record)"
|
||||
>管理生产权限
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onManageConsumerAuth(record)"
|
||||
>管理消费权限
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onAddAuth(record)"
|
||||
>增加权限
|
||||
</a-button>
|
||||
</div>
|
||||
</a-table>
|
||||
<UserDetail
|
||||
:visible="openUserDetailDialog"
|
||||
:username="selectDetail.username"
|
||||
@userDetailDialog="closeUserDetailDialog"
|
||||
></UserDetail>
|
||||
<AclDetail
|
||||
:visible="openAclDetailDialog"
|
||||
:selectDetail="selectDetail"
|
||||
@aclDetailDialog="closeAclDetailDialog"
|
||||
></AclDetail>
|
||||
<ManageProducerAuth
|
||||
:visible="openManageProducerAuthDialog"
|
||||
:record="selectRow"
|
||||
@manageProducerAuthDialog="closeManageProducerAuthDialog"
|
||||
></ManageProducerAuth>
|
||||
<ManageConsumerAuth
|
||||
:visible="openManageConsumerAuthDialog"
|
||||
:record="selectRow"
|
||||
@manageConsumerAuthDialog="closeManageConsumerAuthDialog"
|
||||
></ManageConsumerAuth>
|
||||
<AddAuth
|
||||
:visible="openAddAuthDialog"
|
||||
:record="selectRow"
|
||||
@addAuthDialog="closeAddAuthDialog"
|
||||
></AddAuth>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from "@/utils/request";
|
||||
import notification from "ant-design-vue/es/notification";
|
||||
import UpdateUser from "@/views/acl/UpdateUser";
|
||||
import { KafkaAclApi } from "@/utils/api";
|
||||
import ManageProducerAuth from "@/views/acl/ManageProducerAuth";
|
||||
import ManageConsumerAuth from "@/views/acl/ManageConsumerAuth";
|
||||
import AddAuth from "@/views/acl/AddAuth";
|
||||
import AclDetail from "@/views/acl/AclDetail";
|
||||
import UserDetail from "@/views/acl/UserDetail";
|
||||
import AclList from "@/views/acl/AclList";
|
||||
import SaslScram from "@/views/acl/SaslScram";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "Acl",
|
||||
components: {
|
||||
UpdateUser,
|
||||
ManageProducerAuth,
|
||||
ManageConsumerAuth,
|
||||
AddAuth,
|
||||
AclDetail,
|
||||
UserDetail,
|
||||
AclList,
|
||||
SaslScram,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
queryParam: {},
|
||||
data: [],
|
||||
columns,
|
||||
selectRow: {},
|
||||
form: this.$form.createForm(this, { name: "advanced_search" }),
|
||||
showUpdateUser: false,
|
||||
deleteUserConfirm: false,
|
||||
openManageProducerAuthDialog: false,
|
||||
openManageConsumerAuthDialog: false,
|
||||
openAddAuthDialog: false,
|
||||
openAclDetailDialog: false,
|
||||
openUserDetailDialog: false,
|
||||
selectDetail: {
|
||||
resourceName: "",
|
||||
resourceType: "",
|
||||
username: "",
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSearch(e) {
|
||||
e.preventDefault();
|
||||
this.form.validateFields((error, values) => {
|
||||
let queryParam = {};
|
||||
if (values.username) {
|
||||
queryParam.username = values.username;
|
||||
}
|
||||
if (values.topic) {
|
||||
queryParam.resourceType = "TOPIC";
|
||||
queryParam.resourceName = values.topic;
|
||||
} else if (values.groupId) {
|
||||
queryParam.resourceType = "GROUP";
|
||||
queryParam.resourceName = values.groupId;
|
||||
}
|
||||
Object.assign(this.queryParam, queryParam);
|
||||
this.getAclList();
|
||||
});
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.form.resetFields();
|
||||
},
|
||||
|
||||
updateUser() {
|
||||
this.showUpdateUser = true;
|
||||
},
|
||||
closeUpdateUserDialog(data) {
|
||||
this.showUpdateUser = data.show;
|
||||
if (data.ok) {
|
||||
this.getAclList();
|
||||
}
|
||||
},
|
||||
onDeleteUser(row) {
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.deleteKafkaUser.url,
|
||||
method: KafkaAclApi.deleteKafkaUser.method,
|
||||
data: { username: row.username },
|
||||
}).then((res) => {
|
||||
this.loading = false;
|
||||
this.getAclList();
|
||||
if (res.code == 0) {
|
||||
this.$message.success(res.msg);
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
onManageProducerAuth(row) {
|
||||
this.openManageProducerAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onManageConsumerAuth(row) {
|
||||
this.openManageConsumerAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onAddAuth(row) {
|
||||
this.openAddAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onTopicDetail(topic, username) {
|
||||
this.selectDetail.resourceType = "TOPIC";
|
||||
this.selectDetail.resourceName = topic;
|
||||
this.selectDetail.username = username;
|
||||
this.openAclDetailDialog = true;
|
||||
},
|
||||
onGroupDetail(group, username) {
|
||||
this.selectDetail.resourceType = "GROUP";
|
||||
this.selectDetail.resourceName = group;
|
||||
this.selectDetail.username = username;
|
||||
this.openAclDetailDialog = true;
|
||||
},
|
||||
onUserDetail(username) {
|
||||
this.selectDetail.username = username;
|
||||
this.openUserDetailDialog = true;
|
||||
},
|
||||
closeManageProducerAuthDialog() {
|
||||
this.openManageProducerAuthDialog = false;
|
||||
this.getAclList();
|
||||
},
|
||||
closeManageConsumerAuthDialog() {
|
||||
this.openManageConsumerAuthDialog = false;
|
||||
this.getAclList();
|
||||
},
|
||||
closeAddAuthDialog(p) {
|
||||
this.openAddAuthDialog = false;
|
||||
if (p.refresh) {
|
||||
this.getAclList();
|
||||
}
|
||||
},
|
||||
closeAclDetailDialog(p) {
|
||||
this.openAclDetailDialog = false;
|
||||
if (p.refresh) {
|
||||
this.getAclList();
|
||||
}
|
||||
},
|
||||
closeUserDetailDialog() {
|
||||
this.openUserDetailDialog = false;
|
||||
},
|
||||
getAclList() {
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.getAclList.url,
|
||||
method: KafkaAclApi.getAclList.method,
|
||||
data: this.queryParam,
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
this.data.splice(0, this.data.length);
|
||||
if (response.code != 0) {
|
||||
notification.error({
|
||||
message: response.msg,
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (let k in response.data.map) {
|
||||
let v = response.data.map[k];
|
||||
let topicList = Object.keys(v)
|
||||
.filter((e) => e.startsWith("TOPIC"))
|
||||
.map((e) => e.split("#")[1]);
|
||||
let groupList = Object.keys(v)
|
||||
.filter((e) => e.startsWith("GROUP"))
|
||||
.map((e) => e.split("#")[1]);
|
||||
this.data.push({
|
||||
key: k,
|
||||
username: k,
|
||||
topicList: topicList,
|
||||
groupList: groupList,
|
||||
user: response.data.map[k]["USER"],
|
||||
});
|
||||
this.data.sort((a, b) => a.username.localeCompare(b.username));
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getAclList();
|
||||
computed: {
|
||||
...mapState({
|
||||
enableSasl: (state) => state.clusterInfo.enableSasl,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// function getAclList(data, requestParameters) {
|
||||
// request({
|
||||
// url: KafkaAclApi.getAclList.url,
|
||||
// method: KafkaAclApi.getAclList.method,
|
||||
// data: requestParameters,
|
||||
// }).then((response) => {
|
||||
// data.splice(0, data.length);
|
||||
// if (response.code != 0) {
|
||||
// notification.error({
|
||||
// message: response.msg,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// for (let k in response.data.map) {
|
||||
// let v = response.data.map[k];
|
||||
// let topicList = Object.keys(v)
|
||||
// .filter((e) => e.startsWith("TOPIC"))
|
||||
// .map((e) => e.split("#")[1]);
|
||||
// let groupList = Object.keys(v)
|
||||
// .filter((e) => e.startsWith("GROUP"))
|
||||
// .map((e) => e.split("#")[1]);
|
||||
// data.push({
|
||||
// key: k,
|
||||
// username: k,
|
||||
// topicList: topicList,
|
||||
// groupList: groupList,
|
||||
// user: response.data.map[k]["USER"],
|
||||
// });
|
||||
// data.sort((a, b) => a.username.localeCompare(b.username));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "用户名",
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
width: 300,
|
||||
slots: { title: "username" },
|
||||
scopedSlots: { customRender: "username" },
|
||||
},
|
||||
{
|
||||
title: "topic列表",
|
||||
dataIndex: "topicList",
|
||||
key: "topicList",
|
||||
slots: { title: "topicList" },
|
||||
scopedSlots: { customRender: "topicList" },
|
||||
},
|
||||
{
|
||||
title: "消费组列表",
|
||||
dataIndex: "groupList",
|
||||
key: "groupList",
|
||||
slots: { title: "groupList" },
|
||||
scopedSlots: { customRender: "groupList" },
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
scopedSlots: { customRender: "operation" },
|
||||
width: 500,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.acl {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form {
|
||||
padding: 24px;
|
||||
background: #fbfbfb;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#components-form-acl-advanced-search .ant-form {
|
||||
max-width: none;
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
#components-form-acl-advanced-search .search-result-list {
|
||||
margin-top: 16px;
|
||||
border: 1px dashed #e9e9e9;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
.input-w {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.operation-row-button {
|
||||
height: 4%;
|
||||
text-align: left;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.operation-btn {
|
||||
margin-right: 3%;
|
||||
}
|
||||
</style>
|
||||
|
||||
444
ui/src/views/acl/AclList.vue
Normal file
444
ui/src/views/acl/AclList.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<div class="acl">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-show="!hint" class="acl">
|
||||
<div id="components-form-acl-advanced-search">
|
||||
<a-form
|
||||
class="ant-advanced-search-form"
|
||||
:form="form"
|
||||
@submit="handleSearch"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`主体`">
|
||||
<a-input
|
||||
placeholder="比如, 用户名"
|
||||
class="input-w"
|
||||
v-decorator="['username']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`topic`">
|
||||
<a-input
|
||||
placeholder="topic"
|
||||
class="input-w"
|
||||
v-decorator="['topic']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`消费组`">
|
||||
<a-input
|
||||
placeholder="groupId"
|
||||
class="input-w"
|
||||
v-decorator="['groupId']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24" :style="{ textAlign: 'right' }">
|
||||
<a-button type="primary" html-type="submit"> 搜索</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="operation-row-button">
|
||||
<a-button type="primary" @click="onAddPrincipalAuth"
|
||||
>新增主体权限</a-button
|
||||
>
|
||||
</div>
|
||||
<a-table :columns="columns" :data-source="data" bordered>
|
||||
<div slot="username" slot-scope="username">
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
|
||||
<div slot="topicList" slot-scope="topicList, record">
|
||||
<a
|
||||
href="#"
|
||||
v-for="t in topicList"
|
||||
:key="t"
|
||||
@click="onTopicDetail(t, record.username)"
|
||||
><div style="border-bottom: 1px solid #e5e1e1">{{ t }}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div slot="groupList" slot-scope="groupList, record">
|
||||
<a
|
||||
href="#"
|
||||
v-for="t in groupList"
|
||||
:key="t"
|
||||
@click="onGroupDetail(t, record.username)"
|
||||
><div style="border-bottom: 1px solid #e5e1e1">{{ t }}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div slot="operation" slot-scope="record">
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onManageProducerAuth(record)"
|
||||
>管理生产权限
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onManageConsumerAuth(record)"
|
||||
>管理消费权限
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onAddAuth(record)"
|
||||
>增加权限
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:title="'清除: ' + record.username + '所有资源权限?'"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="onClearUserAcl(record)"
|
||||
>
|
||||
<a-button size="small" href="javascript:;" class="operation-btn"
|
||||
>清除权限</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</a-table>
|
||||
<AclDetail
|
||||
:visible="openAclDetailDialog"
|
||||
:selectDetail="selectDetail"
|
||||
@aclDetailDialog="closeAclDetailDialog"
|
||||
></AclDetail>
|
||||
<ManageProducerAuth
|
||||
:visible="openManageProducerAuthDialog"
|
||||
:record="selectRow"
|
||||
@manageProducerAuthDialog="closeManageProducerAuthDialog"
|
||||
></ManageProducerAuth>
|
||||
<ManageConsumerAuth
|
||||
:visible="openManageConsumerAuthDialog"
|
||||
:record="selectRow"
|
||||
@manageConsumerAuthDialog="closeManageConsumerAuthDialog"
|
||||
></ManageConsumerAuth>
|
||||
<AddAuth
|
||||
:visible="openAddAuthDialog"
|
||||
:record="selectRow"
|
||||
@addAuthDialog="closeAddAuthDialog"
|
||||
></AddAuth>
|
||||
<AddPrincipalAuth
|
||||
:visible="openAddPrincipalAuthDialog"
|
||||
@closeAddPrincipalAuthDialog="closeAddPrincipalAuthDialog"
|
||||
></AddPrincipalAuth>
|
||||
</div>
|
||||
<div v-show="hint">
|
||||
<h2>{{ hint }}</h2>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from "@/utils/request";
|
||||
import notification from "ant-design-vue/es/notification";
|
||||
import { KafkaAclApi } from "@/utils/api";
|
||||
import ManageProducerAuth from "@/views/acl/ManageProducerAuth";
|
||||
import ManageConsumerAuth from "@/views/acl/ManageConsumerAuth";
|
||||
import AddAuth from "@/views/acl/AddAuth";
|
||||
import AclDetail from "@/views/acl/AclDetail";
|
||||
import AddPrincipalAuth from "@/views/acl/AddPrincipalAuth";
|
||||
|
||||
export default {
|
||||
name: "AclList",
|
||||
components: {
|
||||
ManageProducerAuth,
|
||||
ManageConsumerAuth,
|
||||
AddAuth,
|
||||
AclDetail,
|
||||
AddPrincipalAuth,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
queryParam: {},
|
||||
data: [],
|
||||
columns,
|
||||
selectRow: {},
|
||||
form: this.$form.createForm(this, { name: "advanced_search" }),
|
||||
openManageProducerAuthDialog: false,
|
||||
openManageConsumerAuthDialog: false,
|
||||
openAddAuthDialog: false,
|
||||
openAclDetailDialog: false,
|
||||
openAddPrincipalAuthDialog: false,
|
||||
selectDetail: {
|
||||
resourceName: "",
|
||||
resourceType: "",
|
||||
username: "",
|
||||
},
|
||||
loading: false,
|
||||
hint: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSearch(e) {
|
||||
e.preventDefault();
|
||||
this.form.validateFields((error, values) => {
|
||||
let queryParam = {};
|
||||
queryParam.username = values.username ? values.username : null;
|
||||
// if (values.username) {
|
||||
// queryParam.username = values.username;
|
||||
// }
|
||||
if (values.topic) {
|
||||
queryParam.resourceType = "TOPIC";
|
||||
queryParam.resourceName = values.topic;
|
||||
} else if (values.groupId) {
|
||||
queryParam.resourceType = "GROUP";
|
||||
queryParam.resourceName = values.groupId;
|
||||
}
|
||||
Object.assign(this.queryParam, queryParam);
|
||||
this.getAclList();
|
||||
});
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.form.resetFields();
|
||||
},
|
||||
onClearUserAcl(row) {
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.clearAcl.url,
|
||||
method: KafkaAclApi.clearAcl.method,
|
||||
data: { username: row.username },
|
||||
}).then((res) => {
|
||||
this.loading = false;
|
||||
this.getAclList();
|
||||
if (res.code == 0) {
|
||||
this.$message.success(res.msg);
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
onManageProducerAuth(row) {
|
||||
this.openManageProducerAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onManageConsumerAuth(row) {
|
||||
this.openManageConsumerAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onAddAuth(row) {
|
||||
this.openAddAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onTopicDetail(topic, username) {
|
||||
this.selectDetail.resourceType = "TOPIC";
|
||||
this.selectDetail.resourceName = topic;
|
||||
this.selectDetail.username = username;
|
||||
this.openAclDetailDialog = true;
|
||||
},
|
||||
onGroupDetail(group, username) {
|
||||
this.selectDetail.resourceType = "GROUP";
|
||||
this.selectDetail.resourceName = group;
|
||||
this.selectDetail.username = username;
|
||||
this.openAclDetailDialog = true;
|
||||
},
|
||||
onAddPrincipalAuth() {
|
||||
this.openAddPrincipalAuthDialog = true;
|
||||
},
|
||||
closeManageProducerAuthDialog() {
|
||||
this.openManageProducerAuthDialog = false;
|
||||
this.getAclList();
|
||||
},
|
||||
closeManageConsumerAuthDialog() {
|
||||
this.openManageConsumerAuthDialog = false;
|
||||
this.getAclList();
|
||||
},
|
||||
closeAddAuthDialog(p) {
|
||||
this.openAddAuthDialog = false;
|
||||
if (p.refresh) {
|
||||
this.getAclList();
|
||||
}
|
||||
},
|
||||
closeAddPrincipalAuthDialog(p) {
|
||||
this.openAddPrincipalAuthDialog = false;
|
||||
if (p.refresh) {
|
||||
this.getAclList();
|
||||
}
|
||||
},
|
||||
closeAclDetailDialog(p) {
|
||||
this.openAclDetailDialog = false;
|
||||
if (p.refresh) {
|
||||
this.getAclList();
|
||||
}
|
||||
},
|
||||
getAclList() {
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.getAclList.url,
|
||||
method: KafkaAclApi.getAclList.method,
|
||||
data: this.queryParam,
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
this.data.splice(0, this.data.length);
|
||||
if (response.code != 0) {
|
||||
notification.error({
|
||||
message: response.msg,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!response.data.total) {
|
||||
this.hint = response.data.hint;
|
||||
return;
|
||||
}
|
||||
this.hint = "";
|
||||
for (let k in response.data.map) {
|
||||
let v = response.data.map[k];
|
||||
let topicList = Object.keys(v)
|
||||
.filter((e) => e.startsWith("TOPIC"))
|
||||
.map((e) => e.split("#")[1]);
|
||||
let groupList = Object.keys(v)
|
||||
.filter((e) => e.startsWith("GROUP"))
|
||||
.map((e) => e.split("#")[1]);
|
||||
this.data.push({
|
||||
key: k,
|
||||
username: k,
|
||||
topicList: topicList,
|
||||
groupList: groupList,
|
||||
user: response.data.map[k]["USER"],
|
||||
});
|
||||
this.data.sort((a, b) => a.username.localeCompare(b.username));
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getAclList();
|
||||
},
|
||||
};
|
||||
|
||||
// function getAclList(data, requestParameters) {
|
||||
// request({
|
||||
// url: KafkaAclApi.getAclList.url,
|
||||
// method: KafkaAclApi.getAclList.method,
|
||||
// data: requestParameters,
|
||||
// }).then((response) => {
|
||||
// data.splice(0, data.length);
|
||||
// if (response.code != 0) {
|
||||
// notification.error({
|
||||
// message: response.msg,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// for (let k in response.data.map) {
|
||||
// let v = response.data.map[k];
|
||||
// let topicList = Object.keys(v)
|
||||
// .filter((e) => e.startsWith("TOPIC"))
|
||||
// .map((e) => e.split("#")[1]);
|
||||
// let groupList = Object.keys(v)
|
||||
// .filter((e) => e.startsWith("GROUP"))
|
||||
// .map((e) => e.split("#")[1]);
|
||||
// data.push({
|
||||
// key: k,
|
||||
// username: k,
|
||||
// topicList: topicList,
|
||||
// groupList: groupList,
|
||||
// user: response.data.map[k]["USER"],
|
||||
// });
|
||||
// data.sort((a, b) => a.username.localeCompare(b.username));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "主体标识",
|
||||
dataIndex: "username", //历史原因使用变量username
|
||||
key: "username",
|
||||
width: 300,
|
||||
slots: { title: "username" },
|
||||
scopedSlots: { customRender: "username" },
|
||||
},
|
||||
{
|
||||
title: "topic列表",
|
||||
dataIndex: "topicList",
|
||||
key: "topicList",
|
||||
slots: { title: "topicList" },
|
||||
scopedSlots: { customRender: "topicList" },
|
||||
},
|
||||
{
|
||||
title: "消费组列表",
|
||||
dataIndex: "groupList",
|
||||
key: "groupList",
|
||||
slots: { title: "groupList" },
|
||||
scopedSlots: { customRender: "groupList" },
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
scopedSlots: { customRender: "operation" },
|
||||
width: 500,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.acl {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form {
|
||||
padding: 24px;
|
||||
background: #fbfbfb;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#components-form-acl-advanced-search .ant-form {
|
||||
max-width: none;
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
#components-form-acl-advanced-search .search-result-list {
|
||||
margin-top: 16px;
|
||||
border: 1px dashed #e9e9e9;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
.input-w {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.operation-row-button {
|
||||
height: 4%;
|
||||
text-align: left;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.operation-btn {
|
||||
margin-right: 3%;
|
||||
}
|
||||
</style>
|
||||
154
ui/src/views/acl/AddPrincipalAuth.vue
Normal file
154
ui/src/views/acl/AddPrincipalAuth.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<a-modal
|
||||
title="增加权限"
|
||||
:visible="show"
|
||||
:confirm-loading="confirmLoading"
|
||||
:width="800"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
okText="提交"
|
||||
cancelText="取消"
|
||||
:mask="false"
|
||||
:destroyOnClose="true"
|
||||
>
|
||||
<a-form :form="form" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
|
||||
<a-form-item label="主体标识">
|
||||
<a-input
|
||||
v-decorator="[
|
||||
'username',
|
||||
{ rules: [{ required: true, message: '请输入!' }] },
|
||||
]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源类型">
|
||||
<a-radio-group
|
||||
v-decorator="['resourceType', { initialValue: 'TOPIC' }]"
|
||||
>
|
||||
<a-radio value="TOPIC"> topic</a-radio>
|
||||
<a-radio value="GROUP"> 消费组</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源名称">
|
||||
<a-input
|
||||
v-decorator="[
|
||||
'resourceName',
|
||||
{ rules: [{ required: true, message: '请输入!' }] },
|
||||
]"
|
||||
placeholder="请输入topic或消费组名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="主机">
|
||||
<a-input
|
||||
v-decorator="[
|
||||
'host',
|
||||
{
|
||||
rules: [{ required: true, message: '请输入!' }],
|
||||
initialValue: '*',
|
||||
},
|
||||
]"
|
||||
placeholder="请输入主机地址,比如:*,全部匹配"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="操作类型" has-feedback>
|
||||
<a-select
|
||||
v-decorator="[
|
||||
'operation',
|
||||
{ rules: [{ required: true, message: '请选择!' }] },
|
||||
]"
|
||||
placeholder="请选择!"
|
||||
>
|
||||
<a-select-option v-for="i in operations" :key="i">
|
||||
{{ i }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="权限类型">
|
||||
<a-radio-group
|
||||
v-decorator="['permissionType', { initialValue: 'ALLOW' }]"
|
||||
>
|
||||
<a-radio value="ALLOW"> 允许</a-radio>
|
||||
<a-radio value="DENY"> 拒绝</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { KafkaAclApi } from "@/utils/api";
|
||||
import request from "@/utils/request";
|
||||
|
||||
export default {
|
||||
name: "AddPrincipalAuth",
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formLayout: "horizontal",
|
||||
form: this.$form.createForm(this, { name: "AddPrincipalAuthForm" }),
|
||||
confirmLoading: false,
|
||||
show: this.visible,
|
||||
operations: operationList,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible(v) {
|
||||
if (this.show != v) {
|
||||
this.show = v;
|
||||
if (this.show) {
|
||||
this.getOperationList();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleOk() {
|
||||
const form = this.form;
|
||||
form.validateFields((e, v) => {
|
||||
if (e) {
|
||||
return;
|
||||
}
|
||||
const param = Object.assign({}, v);
|
||||
const api = KafkaAclApi.addAclAuth;
|
||||
this.confirmLoading = true;
|
||||
request({
|
||||
url: api.url,
|
||||
method: api.method,
|
||||
data: param,
|
||||
}).then((res) => {
|
||||
this.confirmLoading = false;
|
||||
if (res.code == 0) {
|
||||
this.$message.success(res.msg);
|
||||
this.$emit("closeAddPrincipalAuthDialog", { refresh: true });
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
handleCancel() {
|
||||
this.$emit("closeAddPrincipalAuthDialog", { refresh: false });
|
||||
},
|
||||
getOperationList() {
|
||||
request({
|
||||
url: KafkaAclApi.getOperationList.url,
|
||||
method: KafkaAclApi.getOperationList.method,
|
||||
}).then((res) => {
|
||||
if (res.code != 0) {
|
||||
this.$message.error(res.msg);
|
||||
} else {
|
||||
operationList.splice(0, operationList.length);
|
||||
operationList.push(...res.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const operationList = [];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
403
ui/src/views/acl/SaslScram.vue
Normal file
403
ui/src/views/acl/SaslScram.vue
Normal file
@@ -0,0 +1,403 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="acl">
|
||||
<div id="components-form-acl-advanced-search">
|
||||
<a-form
|
||||
class="ant-advanced-search-form"
|
||||
:form="form"
|
||||
@submit="handleSearch"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item :label="`用户名`">
|
||||
<a-input
|
||||
placeholder="username"
|
||||
class="input-w"
|
||||
v-decorator="['username']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12" :style="{ textAlign: 'right' }">
|
||||
<a-button type="primary" html-type="submit"> 搜索</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="operation-row-button">
|
||||
<a-button type="primary" @click="updateUser">新增/更新用户</a-button>
|
||||
<UpdateUser
|
||||
:visible="showUpdateUser"
|
||||
@updateUserDialogData="closeUpdateUserDialog"
|
||||
></UpdateUser>
|
||||
</div>
|
||||
<a-table :columns="columns" :data-source="data" bordered>
|
||||
<div slot="username" slot-scope="username">
|
||||
<span>{{ username }}</span
|
||||
><a-button
|
||||
size="small"
|
||||
shape="round"
|
||||
type="dashed"
|
||||
style="float: right"
|
||||
@click="onUserDetail(username)"
|
||||
>详情</a-button
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
slot="operation"
|
||||
slot-scope="record"
|
||||
v-show="!record.user || record.user.role != 'admin'"
|
||||
>
|
||||
<a-popconfirm
|
||||
:title="'删除用户: ' + record.username + '?'"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="onDeleteUser(record)"
|
||||
>
|
||||
<a-button size="small" href="javascript:;" class="operation-btn"
|
||||
>删除</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onManageProducerAuth(record)"
|
||||
>管理生产权限
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onManageConsumerAuth(record)"
|
||||
>管理消费权限
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
href="javascript:;"
|
||||
class="operation-btn"
|
||||
@click="onAddAuth(record)"
|
||||
>增加权限
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:title="'删除用户: ' + record.username + '及相关权限?'"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="onDeleteUserAndAuth(record)"
|
||||
>
|
||||
<a-button size="small" href="javascript:;" class="operation-btn"
|
||||
>彻底删除</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</a-table>
|
||||
<UserDetail
|
||||
:visible="openUserDetailDialog"
|
||||
:username="selectDetail.username"
|
||||
@userDetailDialog="closeUserDetailDialog"
|
||||
></UserDetail>
|
||||
<AclDetail
|
||||
:visible="openAclDetailDialog"
|
||||
:selectDetail="selectDetail"
|
||||
@aclDetailDialog="closeAclDetailDialog"
|
||||
></AclDetail>
|
||||
<ManageProducerAuth
|
||||
:visible="openManageProducerAuthDialog"
|
||||
:record="selectRow"
|
||||
@manageProducerAuthDialog="closeManageProducerAuthDialog"
|
||||
></ManageProducerAuth>
|
||||
<ManageConsumerAuth
|
||||
:visible="openManageConsumerAuthDialog"
|
||||
:record="selectRow"
|
||||
@manageConsumerAuthDialog="closeManageConsumerAuthDialog"
|
||||
></ManageConsumerAuth>
|
||||
<AddAuth
|
||||
:visible="openAddAuthDialog"
|
||||
:record="selectRow"
|
||||
@addAuthDialog="closeAddAuthDialog"
|
||||
></AddAuth>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from "@/utils/request";
|
||||
import notification from "ant-design-vue/es/notification";
|
||||
import UpdateUser from "@/views/acl/UpdateUser";
|
||||
import { KafkaAclApi } from "@/utils/api";
|
||||
import ManageProducerAuth from "@/views/acl/ManageProducerAuth";
|
||||
import ManageConsumerAuth from "@/views/acl/ManageConsumerAuth";
|
||||
import AddAuth from "@/views/acl/AddAuth";
|
||||
import AclDetail from "@/views/acl/AclDetail";
|
||||
import UserDetail from "@/views/acl/UserDetail";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "SaslScram",
|
||||
components: {
|
||||
UpdateUser,
|
||||
ManageProducerAuth,
|
||||
ManageConsumerAuth,
|
||||
AddAuth,
|
||||
AclDetail,
|
||||
UserDetail,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
queryParam: {},
|
||||
data: [],
|
||||
columns,
|
||||
selectRow: {},
|
||||
form: this.$form.createForm(this, { name: "advanced_search" }),
|
||||
showUpdateUser: false,
|
||||
deleteUserConfirm: false,
|
||||
openManageProducerAuthDialog: false,
|
||||
openManageConsumerAuthDialog: false,
|
||||
openAddAuthDialog: false,
|
||||
openAclDetailDialog: false,
|
||||
openUserDetailDialog: false,
|
||||
selectDetail: {
|
||||
resourceName: "",
|
||||
resourceType: "",
|
||||
username: "",
|
||||
},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSearch(e) {
|
||||
e.preventDefault();
|
||||
this.form.validateFields((error, values) => {
|
||||
let queryParam = {};
|
||||
if (values.username) {
|
||||
queryParam.username = values.username;
|
||||
}
|
||||
if (values.topic) {
|
||||
queryParam.resourceType = "TOPIC";
|
||||
queryParam.resourceName = values.topic;
|
||||
} else if (values.groupId) {
|
||||
queryParam.resourceType = "GROUP";
|
||||
queryParam.resourceName = values.groupId;
|
||||
}
|
||||
this.queryParam = {};
|
||||
Object.assign(this.queryParam, queryParam);
|
||||
this.getSaslScramUserList();
|
||||
});
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.form.resetFields();
|
||||
},
|
||||
|
||||
updateUser() {
|
||||
this.showUpdateUser = true;
|
||||
},
|
||||
closeUpdateUserDialog(data) {
|
||||
this.showUpdateUser = data.show;
|
||||
if (data.ok) {
|
||||
this.getSaslScramUserList();
|
||||
}
|
||||
},
|
||||
onDeleteUser(row) {
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.deleteSaslScramUser.url,
|
||||
method: KafkaAclApi.deleteSaslScramUser.method,
|
||||
data: { username: row.username },
|
||||
}).then((res) => {
|
||||
this.loading = false;
|
||||
this.getSaslScramUserList();
|
||||
if (res.code == 0) {
|
||||
this.$message.success(res.msg);
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
onDeleteUserAndAuth(row) {
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.deleteKafkaUser.url,
|
||||
method: KafkaAclApi.deleteKafkaUser.method,
|
||||
data: { username: row.username },
|
||||
}).then((res) => {
|
||||
this.loading = false;
|
||||
this.getSaslScramUserList();
|
||||
if (res.code == 0) {
|
||||
this.$message.success(res.msg);
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
onManageProducerAuth(row) {
|
||||
this.openManageProducerAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onManageConsumerAuth(row) {
|
||||
this.openManageConsumerAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onAddAuth(row) {
|
||||
this.openAddAuthDialog = true;
|
||||
const rowData = {};
|
||||
Object.assign(rowData, row);
|
||||
this.selectRow = rowData;
|
||||
},
|
||||
onTopicDetail(topic, username) {
|
||||
this.selectDetail.resourceType = "TOPIC";
|
||||
this.selectDetail.resourceName = topic;
|
||||
this.selectDetail.username = username;
|
||||
this.openAclDetailDialog = true;
|
||||
},
|
||||
onGroupDetail(group, username) {
|
||||
this.selectDetail.resourceType = "GROUP";
|
||||
this.selectDetail.resourceName = group;
|
||||
this.selectDetail.username = username;
|
||||
this.openAclDetailDialog = true;
|
||||
},
|
||||
onUserDetail(username) {
|
||||
this.selectDetail.username = username;
|
||||
this.openUserDetailDialog = true;
|
||||
},
|
||||
closeManageProducerAuthDialog() {
|
||||
this.openManageProducerAuthDialog = false;
|
||||
},
|
||||
closeManageConsumerAuthDialog() {
|
||||
this.openManageConsumerAuthDialog = false;
|
||||
},
|
||||
closeAddAuthDialog() {
|
||||
this.openAddAuthDialog = false;
|
||||
},
|
||||
closeAclDetailDialog(p) {
|
||||
this.openAclDetailDialog = false;
|
||||
if (p.refresh) {
|
||||
this.getSaslScramUserList();
|
||||
}
|
||||
},
|
||||
closeUserDetailDialog() {
|
||||
this.openUserDetailDialog = false;
|
||||
},
|
||||
getSaslScramUserList() {
|
||||
if (!this.enableSasl) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
request({
|
||||
url: KafkaAclApi.getSaslScramUserList.url,
|
||||
method: KafkaAclApi.getSaslScramUserList.method,
|
||||
params: this.queryParam,
|
||||
}).then((response) => {
|
||||
this.loading = false;
|
||||
this.data.splice(0, this.data.length);
|
||||
if (response.code != 0) {
|
||||
notification.error({
|
||||
message: response.msg,
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (let k in response.data.map) {
|
||||
let v = response.data.map[k];
|
||||
let topicList = Object.keys(v)
|
||||
.filter((e) => e.startsWith("TOPIC"))
|
||||
.map((e) => e.split("#")[1]);
|
||||
let groupList = Object.keys(v)
|
||||
.filter((e) => e.startsWith("GROUP"))
|
||||
.map((e) => e.split("#")[1]);
|
||||
this.data.push({
|
||||
key: k,
|
||||
username: k,
|
||||
topicList: topicList,
|
||||
groupList: groupList,
|
||||
user: response.data.map[k]["USER"],
|
||||
});
|
||||
this.data.sort((a, b) => a.username.localeCompare(b.username));
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getSaslScramUserList();
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
enableSasl: (state) => state.clusterInfo.enableSasl,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "用户名",
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
width: 300,
|
||||
slots: { title: "username" },
|
||||
scopedSlots: { customRender: "username" },
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
scopedSlots: { customRender: "operation" },
|
||||
width: 500,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.acl {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form {
|
||||
padding: 24px;
|
||||
background: #fbfbfb;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#components-form-acl-advanced-search .ant-form {
|
||||
max-width: none;
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
#components-form-acl-advanced-search .search-result-list {
|
||||
margin-top: 16px;
|
||||
border: 1px dashed #e9e9e9;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
.input-w {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.operation-row-button {
|
||||
height: 4%;
|
||||
text-align: left;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.operation-btn {
|
||||
margin-right: 3%;
|
||||
}
|
||||
</style>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,25 +50,33 @@
|
||||
>新增</a-button
|
||||
>
|
||||
<a-popconfirm
|
||||
title="删除这些Topic?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="deleteTopics(selectedRowKeys)"
|
||||
title="删除这些Topic?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="deleteTopics(selectedRowKeys)"
|
||||
>
|
||||
<a-button type="danger" class="btn-left" :disabled="!hasSelected" :loading="loading">
|
||||
<a-button
|
||||
type="danger"
|
||||
class="btn-left"
|
||||
:disabled="!hasSelected"
|
||||
:loading="loading"
|
||||
>
|
||||
批量删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<span style="margin-left: 8px">
|
||||
<template v-if="hasSelected">
|
||||
{{ `已选择 ${selectedRowKeys.length} 个Topic` }}
|
||||
</template>
|
||||
</span>
|
||||
<template v-if="hasSelected">
|
||||
{{ `已选择 ${selectedRowKeys.length} 个Topic` }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="filteredData"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
:row-selection="{
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
bordered
|
||||
row-key="name"
|
||||
>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user