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

Reply via email to