You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@dubbo.apache.org by GitBox <gi...@apache.org> on 2018/09/25 08:26:46 UTC

[GitHub] nzomkxia closed pull request #117: [#112] Access Control page

nzomkxia closed pull request #117: [#112] Access Control page
URL: https://github.com/apache/incubator-dubbo-ops/pull/117
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

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 @@
 
     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 @@
 
 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 @@ private URL findRouteUrl(Long id) {
         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 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 URL toUrl() {
         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)
@@ -41,6 +42,11 @@ export default new Router({
       name: 'RoutingRule',
       component: RoutingRule
     },
+    {
+      path: '/governance/access',
+      name: 'AccessControl',
+      component: AccessControl
+    },
     {
       path: '/governance/loadbalance',
       name: 'LoadBalance',


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org