This is an automated email from the ASF dual-hosted git repository.
min pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-dubbo-ops.git
The following commit(s) were added to refs/heads/develop by this push:
new ffba7f8 [#112] Access Control page (#117)
ffba7f8 is described below
commit ffba7f8f95fe171e16b52d7984e02d4bbe2943e9
Author: 马金凯 <[email protected]>
AuthorDate: Tue Sep 25 16:26:43 2018 +0800
[#112] Access Control page (#117)
* add Access Control search/delete/batch delete.
* add Access Control create.
* Change to use one DTO.
* add edit Access Control
---
.../dubbo/admin/controller/AccessesController.java | 139 ++++++++++
.../java/org/apache/dubbo/admin/dto/AccessDTO.java | 61 ++++
.../admin/governance/service/RouteService.java | 2 +
.../governance/service/impl/RouteServiceImpl.java | 11 +
.../dubbo/admin/registry/common/domain/Route.java | 4 +-
.../web/mvc/governance/AccessesController.java | 284 -------------------
dubbo-admin-frontend/package.json | 1 +
dubbo-admin-frontend/src/api/menu.js | 16 +-
.../src/components/AccessControl.vue | 306 +++++++++++++++++++++
dubbo-admin-frontend/src/components/AceEditor.vue | 84 ++++++
dubbo-admin-frontend/src/router/index.js | 6 +
11 files changed, 620 insertions(+), 294 deletions(-)
diff --git
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java
new file mode 100644
index 0000000..184944e
--- /dev/null
+++
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.admin.controller;
+
+import com.alibaba.dubbo.common.logger.Logger;
+import com.alibaba.dubbo.common.logger.LoggerFactory;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.dubbo.admin.dto.AccessDTO;
+import org.apache.dubbo.admin.governance.service.ProviderService;
+import org.apache.dubbo.admin.governance.service.RouteService;
+import org.apache.dubbo.admin.registry.common.domain.Route;
+import org.apache.dubbo.admin.registry.common.route.RouteRule;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.text.ParseException;
+import java.util.*;
+
+@RestController
+@RequestMapping("/api/access")
+public class AccessesController {
+ private static final Logger logger =
LoggerFactory.getLogger(AccessesController.class);
+
+ @Resource
+ private RouteService routeService;
+ @Resource
+ private ProviderService providerService;
+
+ @RequestMapping("/search")
+ public List<AccessDTO> searchAccess(@RequestBody(required = false)
Map<String, String> params) throws ParseException {
+ List<AccessDTO> result = new ArrayList<>();
+ List<Route> routes = new ArrayList<>();
+ if (StringUtils.isNotBlank(params.get("service"))) {
+ Route route =
routeService.getBlackwhitelistRouteByService(params.get("service").trim());
+ if (route != null) {
+ routes.add(route);
+ }
+ } else {
+ routes = routeService.findAllForceRoute();
+ }
+
+ for (Route route : routes) {
+ // Match WhiteBlackList Route
+ if (route.getName().endsWith(AccessDTO.KEY_BLACK_WHITE_LIST)) {
+ AccessDTO accessDTO = new AccessDTO();
+ accessDTO.setId(route.getId());
+ accessDTO.setService(route.getService());
+ Map<String, RouteRule.MatchPair> when =
RouteRule.parseRule(route.getMatchRule());
+ for (String key : when.keySet()) {
+ accessDTO.setWhitelist(when.get(key).getUnmatches());
+ accessDTO.setBlacklist(when.get(key).getMatches());
+ }
+ result.add(accessDTO);
+ }
+ }
+ return result;
+ }
+
+ @RequestMapping(value = "/delete", method = RequestMethod.POST)
+ public void deleteAccess(@RequestBody Map<String, Long> params) {
+ if (params.get("id") == null) {
+ throw new IllegalArgumentException("Argument of id is null!");
+ }
+ routeService.deleteRoute(params.get("id"));
+ }
+
+ @RequestMapping(value = "/create", method = RequestMethod.POST)
+ public void createAccess(@RequestBody AccessDTO accessDTO) {
+ if (StringUtils.isBlank(accessDTO.getService())) {
+ throw new IllegalArgumentException("Service is required.");
+ }
+ if (accessDTO.getBlacklist() == null && accessDTO.getWhitelist() ==
null) {
+ throw new IllegalArgumentException("One of Blacklist/Whitelist is
required.");
+ }
+
+ Route route =
routeService.getBlackwhitelistRouteByService(accessDTO.getService());
+
+ if (route != null) {
+ throw new IllegalArgumentException(accessDTO.getService() + " is
existed.");
+ }
+
+ route = new Route();
+ route.setService(accessDTO.getService());
+ route.setForce(true);
+ route.setName(accessDTO.getService() + " " +
AccessDTO.KEY_BLACK_WHITE_LIST);
+ route.setEnabled(true);
+
+ Map<String, RouteRule.MatchPair> when = new HashMap<>();
+ RouteRule.MatchPair matchPair = new RouteRule.MatchPair(new
HashSet<>(), new HashSet<>());
+ when.put(Route.KEY_CONSUMER_HOST, matchPair);
+
+ if (accessDTO.getWhitelist() != null) {
+ matchPair.getUnmatches().addAll(accessDTO.getWhitelist());
+ }
+ if (accessDTO.getBlacklist() != null) {
+ matchPair.getMatches().addAll(accessDTO.getBlacklist());
+ }
+
+ StringBuilder sb = new StringBuilder();
+ RouteRule.contidionToString(sb, when);
+ route.setMatchRule(sb.toString());
+ routeService.createRoute(route);
+ }
+
+ @RequestMapping(value = "/update", method = RequestMethod.POST)
+ public void updateAccess(@RequestBody AccessDTO accessDTO) {
+ Route route = routeService.findRoute(accessDTO.getId());
+ Map<String, RouteRule.MatchPair> when = new HashMap<>();
+ RouteRule.MatchPair matchPair = new RouteRule.MatchPair(new
HashSet<>(), new HashSet<>());
+ when.put(Route.KEY_CONSUMER_HOST, matchPair);
+
+ if (accessDTO.getWhitelist() != null) {
+ matchPair.getUnmatches().addAll(accessDTO.getWhitelist());
+ }
+ if (accessDTO.getBlacklist() != null) {
+ matchPair.getMatches().addAll(accessDTO.getBlacklist());
+ }
+
+ StringBuilder sb = new StringBuilder();
+ RouteRule.contidionToString(sb, when);
+ route.setMatchRule(sb.toString());
+
+ routeService.updateRoute(route);
+ }
+}
diff --git
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/dto/AccessDTO.java
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/dto/AccessDTO.java
new file mode 100644
index 0000000..94c06ff
--- /dev/null
+++
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/dto/AccessDTO.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.admin.dto;
+
+import java.util.Set;
+
+public class AccessDTO {
+ // BlackWhiteList key
+ public static final String KEY_BLACK_WHITE_LIST = "blackwhitelist";
+
+ private Long id;
+ private String service;
+ private Set<String> whitelist;
+ private Set<String> blacklist;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public Set<String> getWhitelist() {
+ return whitelist;
+ }
+
+ public void setWhitelist(Set<String> whitelist) {
+ this.whitelist = whitelist;
+ }
+
+ public Set<String> getBlacklist() {
+ return blacklist;
+ }
+
+ public void setBlacklist(Set<String> blacklist) {
+ this.blacklist = blacklist;
+ }
+}
diff --git
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/RouteService.java
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/RouteService.java
index c667814..9662400 100644
---
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/RouteService.java
+++
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/RouteService.java
@@ -54,4 +54,6 @@ public interface RouteService {
List<Route> findAllForceRoute();
+ Route getBlackwhitelistRouteByService(String service);
+
}
diff --git
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/impl/RouteServiceImpl.java
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/impl/RouteServiceImpl.java
index 17911a7..65bbe7d 100644
---
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/impl/RouteServiceImpl.java
+++
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/governance/service/impl/RouteServiceImpl.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.admin.governance.service.impl;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
+import org.apache.dubbo.admin.dto.AccessDTO;
import org.apache.dubbo.admin.governance.service.RouteService;
import org.apache.dubbo.admin.governance.sync.util.Pair;
import org.apache.dubbo.admin.governance.sync.util.SyncUtils;
@@ -165,4 +166,14 @@ public class RouteServiceImpl extends AbstractService
implements RouteService {
return SyncUtils.url2RouteList(findRouteUrl(null, null, true));
}
+ public Route getBlackwhitelistRouteByService(String service) {
+ List<Route> routes = SyncUtils.url2RouteList(findRouteUrl(service,
null, true));
+ for (Route route : routes) {
+ if (route.getName().endsWith(AccessDTO.KEY_BLACK_WHITE_LIST)) {
+ return route;
+ }
+ }
+ return null;
+ }
+
}
diff --git
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/common/domain/Route.java
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/common/domain/Route.java
index f98ca4b..91859b2 100644
---
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/common/domain/Route.java
+++
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/common/domain/Route.java
@@ -34,7 +34,7 @@ public class Route extends Entity {
public static final String KEY_CONSUMER_APPLICATION =
"consumer.application";
public static final String KEY_CONSUMER_GROUP = "consumer.cluster";
public static final String KEY_CONSUMER_VERSION = "consumer.version";
- public static final String KEY_CONSUMER_HOST = "consumer.host";
+ public static final String KEY_CONSUMER_HOST = "host";
public static final String KEY_CONSUMER_METHODS = "consumer.methods";
public static final String KEY_PROVIDER_APPLICATION =
"provider.application";
@@ -220,7 +220,7 @@ public class Route extends Entity {
return URL.valueOf(Constants.ROUTE_PROTOCOL + "://" +
Constants.ANYHOST_VALUE + "/" + getService()
+ "?" + Constants.CATEGORY_KEY + "=" +
Constants.ROUTERS_CATEGORY
+ "&router=condition&runtime=" + isRuntime() + "&enabled=" +
isEnabled() + "&priority=" + getPriority() + "&force=" + isForce() +
"&dynamic=" + isDynamic()
- + "&name=" + getName() + "&" + Constants.RULE_KEY + "=" +
URL.encode(getRule())
+ + "&name=" + getName() + "&" + Constants.RULE_KEY + "=" +
URL.encode(getMatchRule() + " => " + getFilterRule())
+ (getGroup() == null ? "" : "&" + Constants.GROUP_KEY + "=" +
getGroup())
+ (getVersion() == null ? "" : "&" + Constants.VERSION_KEY +
"=" + getVersion()));
}
diff --git
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/web/mvc/governance/AccessesController.java
b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/web/mvc/governance/AccessesController.java
deleted file mode 100644
index 90cd59f..0000000
---
a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/web/mvc/governance/AccessesController.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.admin.web.mvc.governance;
-
-import org.apache.dubbo.admin.governance.service.ProviderService;
-import org.apache.dubbo.admin.governance.service.RouteService;
-import org.apache.dubbo.admin.registry.common.domain.Access;
-import org.apache.dubbo.admin.registry.common.domain.Route;
-import org.apache.dubbo.admin.registry.common.route.RouteRule;
-import org.apache.dubbo.admin.web.mvc.BaseController;
-import org.apache.dubbo.admin.web.pulltool.Tool;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.validation.support.BindingAwareModelMap;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.text.ParseException;
-import java.util.*;
-import java.util.Map.Entry;
-import java.util.regex.Pattern;
-
-/**
- * ProvidersController. URI: /services/$service/accesses
- *
- */
-
-@Controller
-@RequestMapping("/governance/accesses")
-public class AccessesController extends BaseController {
-
- private static final Pattern IP_PATTERN =
Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3}$");
- private static final Pattern LOCAL_IP_PATTERN =
Pattern.compile("127(\\.\\d{1,3}){3}$");
- private static final Pattern ALL_IP_PATTERN =
Pattern.compile("0{1,3}(\\.0{1,3}){3}$");
-
- @Autowired
- private RouteService routeService;
- @Autowired
- private ProviderService providerService;
-
- @RequestMapping("")
- public String index(HttpServletRequest request, HttpServletResponse
response, Model model) {
- prepare(request, response, model, "index", "accesses");
- BindingAwareModelMap newModel = (BindingAwareModelMap)model;
- String address = (String)newModel.get("address");
- String service = (String)newModel.get("service");
-
- address = Tool.getIP(address);
- List<Route> routes;
- if (service != null && service.length() > 0) {
- routes = routeService.findForceRouteByService(service);
- } else if (address != null && address.length() > 0) {
- routes = routeService.findForceRouteByAddress(address);
- } else {
- routes = routeService.findAllForceRoute();
- }
- List<Access> accesses = new ArrayList<Access>();
- if (routes == null) {
- model.addAttribute("accesses", accesses);
- return "governance/screen/accesses/index";
- }
- for (Route route : routes) {
- Map<String, RouteRule.MatchPair> rule = null;
- try {
- rule = RouteRule.parseRule(route.getMatchRule());
- } catch (ParseException e) {
- logger.error("parse rule error", e);
- }
- RouteRule.MatchPair pair = rule.get("consumer.host");
- if (pair != null) {
- for (String host : pair.getMatches()) {
- Access access = new Access();
- access.setAddress(host);
- access.setService(route.getService());
- access.setAllow(false);
- accesses.add(access);
- }
- for (String host : pair.getUnmatches()) {
- Access access = new Access();
- access.setAddress(host);
- access.setService(route.getService());
- access.setAllow(true);
- accesses.add(access);
- }
- }
- }
- model.addAttribute("accesses", accesses);
- return "governance/screen/accesses/index";
- }
-
- @RequestMapping("/add")
- public String add(HttpServletRequest request, HttpServletResponse
response, Model model) {
- prepare(request, response, model, "add", "accesses");
- List<String> serviceList =
Tool.sortSimpleName(providerService.findServices());
- model.addAttribute("serviceList", serviceList);
- return "governance/screen/accesses/add";
- }
-
- @RequestMapping("/create")
- public String create(HttpServletRequest request, HttpServletResponse
response, Model model) throws Exception {
- prepare(request, response, model, "create", "accesses");
- String addr = request.getParameter("consumerAddress");
- String services = request.getParameter("service");
- Set<String> consumerAddresses = toAddr(addr);
- Set<String> aimServices = toService(services);
- for (String aimService : aimServices) {
- boolean isFirst = false;
- List<Route> routes =
routeService.findForceRouteByService(aimService);
- Route route = null;
- if (routes == null || routes.size() == 0) {
- isFirst = true;
- route = new Route();
- route.setService(aimService);
- route.setForce(true);
- route.setName(aimService + " blackwhitelist");
- route.setFilterRule("false");
- route.setEnabled(true);
- } else {
- route = routes.get(0);
- }
- Map<String, RouteRule.MatchPair> when = null;
- RouteRule.MatchPair matchPair = null;
- if (isFirst) {
- when = new HashMap<String, RouteRule.MatchPair>();
- matchPair = new RouteRule.MatchPair(new HashSet<String>(), new
HashSet<String>());
- when.put("consumer.host", matchPair);
- } else {
- when = RouteRule.parseRule(route.getMatchRule());
- matchPair = when.get("consumer.host");
- }
- for (String consumerAddress : consumerAddresses) {
- if (Boolean.valueOf((String) request.getParameter("allow"))) {
- matchPair.getUnmatches().add(Tool.getIP(consumerAddress));
-
- } else {
- matchPair.getMatches().add(Tool.getIP(consumerAddress));
- }
- }
- StringBuilder sb = new StringBuilder();
- RouteRule.contidionToString(sb, when);
- route.setMatchRule(sb.toString());
- route.setUsername(operator);
- if (isFirst) {
- routeService.createRoute(route);
- } else {
- routeService.updateRoute(route);
- }
-
- }
- model.addAttribute("success", true);
- model.addAttribute("redirect", "../accesses");
- return "governance/screen/redirect";
-
- }
-
- private Set<String> toAddr(String addr) throws IOException {
- Set<String> consumerAddresses = new HashSet<String>();
- BufferedReader reader = new BufferedReader(new StringReader(addr));
- while (true) {
- String line = reader.readLine();
- if (null == line)
- break;
-
- String[] split = line.split("[\\s,;]+");
- for (String s : split) {
- if (s.length() == 0)
- continue;
- if (!IP_PATTERN.matcher(s).matches()) {
- throw new IllegalStateException("illegal IP: " + s);
- }
- if (LOCAL_IP_PATTERN.matcher(s).matches() ||
ALL_IP_PATTERN.matcher(s).matches()) {
- throw new IllegalStateException("local IP or any host ip
is illegal: " + s);
- }
-
- consumerAddresses.add(s);
- }
- }
- return consumerAddresses;
- }
-
- private Set<String> toService(String services) throws IOException {
- Set<String> aimServices = new HashSet<String>();
- BufferedReader reader = new BufferedReader(new StringReader(services));
- while (true) {
- String line = reader.readLine();
- if (null == line)
- break;
-
- String[] split = line.split("[\\s,;]+");
- for (String s : split) {
- if (s.length() == 0)
- continue;
- aimServices.add(s);
- }
- }
- return aimServices;
- }
-
- /**
- *
- * @throws ParseException
- */
- @RequestMapping("/delete")
- public String delete(@RequestParam String accesses, HttpServletRequest
request, HttpServletResponse response, Model model) throws ParseException {
- prepare(request, response, model, "delete", "accesses");
- String[] temp = accesses.split(" ");
- Map<String, Set<String>> prepareToDeleate = new HashMap<String,
Set<String>>();
- for (String s : temp) {
- String service = s.split("=")[0];
- String address = s.split("=")[1];
- Set<String> addresses = prepareToDeleate.get(service);
- if (addresses == null) {
- prepareToDeleate.put(service, new HashSet<String>());
- addresses = prepareToDeleate.get(service);
- }
- addresses.add(address);
- }
- for (Entry<String, Set<String>> entry : prepareToDeleate.entrySet()) {
-
- String service = entry.getKey();
- List<Route> routes = routeService.findForceRouteByService(service);
- if (routes == null || routes.size() == 0) {
- continue;
- }
- for (Route blackwhitelist : routes) {
- RouteRule.MatchPair pairs =
RouteRule.parseRule(blackwhitelist.getMatchRule()).get("consumer.host");
- Set<String> matches = new HashSet<String>();
- matches.addAll(pairs.getMatches());
- Set<String> unmatches = new HashSet<String>();
- unmatches.addAll(pairs.getUnmatches());
- for (String pair : pairs.getMatches()) {
- for (String address : entry.getValue()) {
- if (pair.equals(address)) {
- matches.remove(pair);
- break;
- }
- }
- }
- for (String pair : pairs.getUnmatches()) {
- for (String address : entry.getValue()) {
- if (pair.equals(address)) {
- unmatches.remove(pair);
- break;
- }
- }
- }
- if (matches.size() == 0 && unmatches.size() == 0) {
- routeService.deleteRoute(blackwhitelist.getId());
- } else {
- Map<String, RouteRule.MatchPair> condition = new
HashMap<String, RouteRule.MatchPair>();
- condition.put("consumer.host", new
RouteRule.MatchPair(matches, unmatches));
- StringBuilder sb = new StringBuilder();
- RouteRule.contidionToString(sb, condition);
- blackwhitelist.setMatchRule(sb.toString());
- routeService.updateRoute(blackwhitelist);
- }
- }
-
- }
- model.addAttribute("success", true);
- model.addAttribute("redirect", "../accesses");
- return "governance/screen/redirect";
- }
-}
diff --git a/dubbo-admin-frontend/package.json
b/dubbo-admin-frontend/package.json
index e5ff4ae..a9cf14e 100644
--- a/dubbo-admin-frontend/package.json
+++ b/dubbo-admin-frontend/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"axios": "^0.18.0",
+ "brace": "^0.11.1",
"js-yaml": "^3.12.0",
"vue": "^2.5.2",
"vue-codemirror": "^4.0.5",
diff --git a/dubbo-admin-frontend/src/api/menu.js
b/dubbo-admin-frontend/src/api/menu.js
index 2377c55..c94bb42 100644
--- a/dubbo-admin-frontend/src/api/menu.js
+++ b/dubbo-admin-frontend/src/api/menu.js
@@ -1,24 +1,24 @@
const Menu = [
- {title: 'Service Search', path: '/service', icon: 'search'},
+ { title: 'Service Search', path: '/service', icon: 'search' },
{
title: 'Service Governance',
icon: 'edit',
group: 'governance',
items: [
- {title: 'Routing Rule', path: '/governance/routingRule'},
- {title: 'Dynamic Config', path: '/governance/config'},
- {title: 'Access Control', path: '/governance/access'},
- {title: 'Weight Adjust', path: '/governance/weight'},
- {title: 'Load Balance', path: '/governance/loadbalance'}
+ { title: 'Routing Rule', path: '/governance/routingRule' },
+ { title: 'Dynamic Config', path: '/governance/config' },
+ { title: 'Access Control', path: '/governance/access' },
+ { title: 'Weight Adjust', path: '/governance/weight' },
+ { title: 'Load Balance', path: '/governance/loadbalance' }
]
},
- {title: 'QoS', path: '/qos', icon: 'computer'},
+ { title: 'QoS', path: '/qos', icon: 'computer' },
{
title: 'Service Info',
icon: 'info',
group: 'info',
items: [
- {title: 'Version', path: '/info/version'}
+ { title: 'Version', path: '/info/version' }
]
}
]
diff --git a/dubbo-admin-frontend/src/components/AccessControl.vue
b/dubbo-admin-frontend/src/components/AccessControl.vue
new file mode 100644
index 0000000..961393c
--- /dev/null
+++ b/dubbo-admin-frontend/src/components/AccessControl.vue
@@ -0,0 +1,306 @@
+<!--
+ - Licensed to the Apache Software Foundation (ASF) under one or more
+ - contributor license agreements. See the NOTICE file distributed with
+ - this work for additional information regarding copyright ownership.
+ - The ASF licenses this file to You under the Apache License, Version 2.0
+ - (the "License"); you may not use this file except in compliance with
+ - he License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+ -->
+
+<template>
+ <v-container grid-list-xl
+ fluid>
+ <v-layout row
+ wrap>
+ <v-flex xs12>
+ <v-card flat
+ color="transparent">
+ <v-card-text>
+ <v-layout row
+ wrap>
+ <v-text-field label="Search Access Controls by service name"
+ v-model="filter"
+ clearable></v-text-field>
+ <v-btn @click="submit"
+ color="primary"
+ large>Search</v-btn>
+ </v-layout>
+ </v-card-text>
+ </v-card>
+ </v-flex>
+ </v-layout>
+
+ <v-flex lg12>
+ <v-card>
+ <v-toolbar flat
+ color="transparent"
+ class="elevation-0">
+ <v-toolbar-title>
+ <span class="headline">Search Result</span>
+ </v-toolbar-title>
+ <v-spacer></v-spacer>
+ <v-btn outline
+ color="primary"
+ @click.stop="toCreate"
+ class="mb-2">CREATE</v-btn>
+ </v-toolbar>
+
+ <v-card-text class="pa-0">
+ <v-data-table v-model="selected"
+ :headers="headers"
+ :items="accesses"
+ :loading="loading"
+ hide-actions
+ class="elevation-0">
+ <template slot="items"
+ slot-scope="props">
+ <td class="text-xs-left">{{ props.item.service }}</td>
+ <td class="text-xs-center px-0">
+ <v-tooltip bottom>
+ <v-icon small
+ class="mr-2"
+ color="blue"
+ slot="activator"
+ @click="toEdit(props.item)">edit</v-icon>
+ <span>Edit</span>
+ </v-tooltip>
+ <v-tooltip bottom>
+ <v-icon small
+ class="mr-2"
+ slot="activator"
+ color="red"
+ @click="toDelete(props.item)">delete</v-icon>
+ <span>Delete</span>
+ </v-tooltip>
+ </td>
+ </template>
+ </v-data-table>
+ </v-card-text>
+ </v-card>
+ </v-flex>
+
+ <v-dialog v-model="modal.enable"
+ width="800px"
+ persistent>
+ <v-card>
+ <v-card-title class="justify-center">
+ <span class="headline">{{ modal.title }} Access Control</span>
+ </v-card-title>
+ <v-card-text>
+ <v-form ref="modalForm">
+ <v-text-field label="Service Unique ID"
+ hint="A service ID in form of service"
+ required
+ :readonly="modal.id != null"
+ v-model="modal.service" />
+ <v-subheader class="pa-0 mt-3">BLACK/WHITE LIST
CONTENT</v-subheader>
+ <ace-editor v-model="modal.content"
+ :config="modal.aceConfig" />
+ </v-form>
+ </v-card-text>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn color="darken-1"
+ flat
+ @click="closeModal()">Close</v-btn>
+ <v-btn color="green darken-1"
+ flat
+ @click="modal.click">{{ modal.saveBtn }}</v-btn>
+ </v-card-actions>
+ </v-card>
+ </v-dialog>
+
+ <v-dialog v-model="confirm.enable"
+ persistent
+ max-width="500px">
+ <v-card>
+ <v-card-title class="headline">{{this.confirm.title}}</v-card-title>
+ <v-card-text>{{this.confirm.text}}</v-card-text>
+ <v-card-actions>
+ <v-spacer></v-spacer>
+ <v-btn color="red darken-1"
+ flat
+ @click="confirm.enable = false">Disagree</v-btn>
+ <v-btn color="green darken-1"
+ flat
+ @click="deleteItem(confirm.id)">Agree</v-btn>
+ </v-card-actions>
+ </v-card>
+ </v-dialog>
+
+ <v-snackbar v-model="snackbar.enable"
+ :color="snackbar.color">
+ {{ snackbar.text }}
+ <v-btn dark
+ flat
+ @click="snackbar.enable = false">
+ Close
+ </v-btn>
+ </v-snackbar>
+ </v-container>
+</template>
+
+<script>
+import yaml from 'js-yaml'
+import { AXIOS } from './http-common'
+import AceEditor from '@/components/AceEditor'
+
+export default {
+ name: 'AccessControl',
+ data: () => ({
+ selected: [],
+ filter: '',
+ loading: false,
+ headers: [
+ {
+ text: 'Service Name',
+ value: 'service',
+ align: 'left'
+ },
+ {
+ text: 'Operation',
+ value: 'operation',
+ sortable: false,
+ width: '115px'
+ }
+ ],
+ accesses: [],
+ modal: {
+ enable: false,
+ title: 'Create New',
+ saveBtn: 'Create',
+ click: null,
+ id: null,
+ service: null,
+ content: '',
+ template:
+ 'blacklist:\n' +
+ ' - 1.1.1.1\n' +
+ ' - 2.2.2.2\n' +
+ 'whitelist:\n' +
+ ' - 3.3.3.3\n' +
+ ' - 4.4.*\n',
+ aceConfig: {}
+ },
+ services: [],
+ confirm: {
+ enable: false,
+ title: '',
+ text: '',
+ id: null
+ },
+ snackbar: {
+ enable: false,
+ text: ''
+ }
+ }),
+ methods: {
+ submit () {
+ if (this.filter == null) {
+ this.filter = ''
+ }
+ this.search(this.filter)
+ },
+ search (filter) {
+ this.loading = true
+ AXIOS.post('/access/search', {
+ service: this.filter
+ }).then(response => {
+ this.accesses = response.data
+ this.loading = false
+ }).catch(error => {
+ this.showSnackbar('error', error.response.data.message)
+ this.loading = false
+ })
+ },
+ closeModal () {
+ this.modal.enable = false
+ this.modal.id = null
+ this.$refs.modalForm.reset()
+ },
+ toCreate () {
+ Object.assign(this.modal, {
+ enable: true,
+ title: 'Create New',
+ saveBtn: 'Create',
+ content: this.modal.template,
+ click: this.createItem
+ })
+ },
+ createItem () {
+ let doc = yaml.load(this.modal.content)
+ AXIOS.post('/access/create', {
+ service: this.modal.service,
+ whitelist: doc.whitelist,
+ blacklist: doc.blacklist
+ }).then(response => {
+ this.closeModal()
+ this.search(this.filter)
+ this.showSnackbar('success', 'Create success')
+ }).catch(error => this.showSnackbar('error',
error.response.data.message))
+ },
+ toEdit (item) {
+ Object.assign(this.modal, {
+ enable: true,
+ title: 'Edit',
+ saveBtn: 'Update',
+ click: this.editItem,
+ id: item.id,
+ service: item.service,
+ content: yaml.safeDump({blacklist: item.blacklist, whitelist:
item.whitelist})
+ })
+ },
+ editItem () {
+ let doc = yaml.load(this.modal.content)
+ AXIOS.post('/access/update', {
+ id: this.modal.id,
+ whitelist: doc.whitelist,
+ blacklist: doc.blacklist
+ }).then(response => {
+ this.closeModal()
+ this.search(this.filter)
+ this.showSnackbar('success', 'Update success')
+ }).catch(error => this.showSnackbar('error',
error.response.data.message))
+ },
+ toDelete (item) {
+ Object.assign(this.confirm, {
+ enable: true,
+ title: 'Are you sure to Delete Access Control',
+ text: `Service: ${item.service}`,
+ id: item.id
+ })
+ },
+ deleteItem (id) {
+ AXIOS.post('/access/delete', {
+ id: id
+ }).then(response => {
+ this.showSnackbar('success', 'Delete success')
+ this.search(this.filter)
+ }).catch(error => this.showSnackbar('error',
error.response.data.message))
+ },
+ showSnackbar (color, message) {
+ Object.assign(this.snackbar, {
+ enable: true,
+ color: color,
+ text: message
+ })
+ this.confirm.enable = false
+ this.selected = []
+ }
+ },
+ mounted () {
+ this.search(this.filter)
+ },
+ components: {
+ AceEditor
+ }
+}
+</script>
diff --git a/dubbo-admin-frontend/src/components/AceEditor.vue
b/dubbo-admin-frontend/src/components/AceEditor.vue
new file mode 100644
index 0000000..8683034
--- /dev/null
+++ b/dubbo-admin-frontend/src/components/AceEditor.vue
@@ -0,0 +1,84 @@
+<template>
+ <div :style="{height: myConfig.height + 'px', width: myConfig.width +
'px'}"></div>
+</template>
+
+<script>
+import brace from 'brace'
+
+let defaultConfig = {
+ width: '100%',
+ height: 300,
+ lang: 'yaml',
+ theme: 'monokai',
+ readonly: false,
+ fontSize: 14,
+ tabSize: 2
+}
+
+export default {
+ name: 'ace-editor',
+ props: {
+ value: String,
+ config: {
+ type: Object,
+ default () {
+ return {}
+ }
+ }
+ },
+ data () {
+ return {
+ myConfig: Object.assign({}, defaultConfig, this.config),
+ $ace: null
+ }
+ },
+ watch: {
+ config (newVal, oldVal) {
+ if (newVal !== oldVal) {
+ this.myConfig = Object.assign({}, defaultConfig, newVal)
+ this.initAce(this.myConfig)
+ }
+ },
+ value (newVal, oldVal) {
+ if (newVal !== oldVal) {
+ this.$ace.setValue(newVal, 1)
+ }
+ }
+ },
+ methods: {
+ initAce (conf) {
+ this.$ace = brace.edit(this.$el)
+ this.$ace.$blockScrolling = Infinity // 消除警告
+ let session = this.$ace.getSession()
+ this.$emit('init', this.$ace)
+
+ require(`brace/mode/${conf.lang}`)
+ require(`brace/theme/${conf.theme}`)
+
+ session.setMode(`ace/mode/${conf.lang}`) // 配置语言
+ this.$ace.setTheme(`ace/theme/${conf.theme}`) // 配置主题
+ this.$ace.setValue(this.value, 1) // 设置默认内容
+ this.$ace.setReadOnly(conf.readonly) // 设置是否为只读模式
+ this.$ace.setFontSize(conf.fontSize)
+ session.setTabSize(conf.tabSize) // Tab大小
+ session.setUseSoftTabs(true)
+
+ this.$ace.setShowPrintMargin(false) // 不显示打印边距
+ session.setUseWrapMode(true) // 自动换行
+
+ // 绑定输入事件回调
+ this.$ace.on('change', () => {
+ var content = this.$ace.getValue()
+ this.$emit('input', content)
+ })
+ }
+ },
+ mounted () {
+ if (this.myConfig) {
+ this.initAce(this.myConfig)
+ } else {
+ this.initAce(defaultConfig)
+ }
+ }
+}
+</script>
diff --git a/dubbo-admin-frontend/src/router/index.js
b/dubbo-admin-frontend/src/router/index.js
index 11b64f2..f1d3055 100644
--- a/dubbo-admin-frontend/src/router/index.js
+++ b/dubbo-admin-frontend/src/router/index.js
@@ -20,6 +20,7 @@ import Router from 'vue-router'
import ServiceSearch from '@/components/ServiceSearch'
import ServiceDetail from '@/components/ServiceDetail'
import RoutingRule from '@/components/RoutingRule'
+import AccessControl from '@/components/AccessControl'
import LoadBalance from '@/components/LoadBalance'
Vue.use(Router)
@@ -42,6 +43,11 @@ export default new Router({
component: RoutingRule
},
{
+ path: '/governance/access',
+ name: 'AccessControl',
+ component: AccessControl
+ },
+ {
path: '/governance/loadbalance',
name: 'LoadBalance',
component: LoadBalance