Merge branch 'develop_1.2.0' into develop
# Conflicts: # console/src/main/resources/static/css/main.css # console/src/main/resources/static/js/main.js
This commit is contained in:
commit
cc0a4915c7
@ -16,7 +16,7 @@
|
||||
<parent>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -96,6 +96,17 @@ public @interface NacosProperties {
|
||||
*/
|
||||
String ENABLE_REMOTE_SYNC_CONFIG = "enableRemoteSyncConfig";
|
||||
|
||||
/**
|
||||
* The property name of "username"
|
||||
*/
|
||||
String USERNAME = "username";
|
||||
|
||||
/**
|
||||
* The property name of "password"
|
||||
*/
|
||||
String PASSWORD = "password";
|
||||
|
||||
|
||||
/**
|
||||
* The placeholder of endpoint, the value is <code>"${nacos.endpoint:}"</code>
|
||||
*/
|
||||
@ -156,6 +167,16 @@ public @interface NacosProperties {
|
||||
*/
|
||||
String ENABLE_REMOTE_SYNC_CONFIG_PLACEHOLDER = "${" + PREFIX + ENABLE_REMOTE_SYNC_CONFIG + ":}";
|
||||
|
||||
/**
|
||||
* The placeholder of endpoint, the value is <code>"${nacos.username:}"</code>
|
||||
*/
|
||||
String USERNAME_PLACEHOLDER = "${" + PREFIX + USERNAME + ":}";
|
||||
|
||||
/**
|
||||
* The placeholder of endpoint, the value is <code>"${nacos.password:}"</code>
|
||||
*/
|
||||
String PASSWORD_PLACEHOLDER = "${" + PREFIX + PASSWORD + ":}";
|
||||
|
||||
/**
|
||||
* The property of "endpoint"
|
||||
*
|
||||
@ -252,4 +273,20 @@ public @interface NacosProperties {
|
||||
*/
|
||||
String enableRemoteSyncConfig() default ENABLE_REMOTE_SYNC_CONFIG_PLACEHOLDER;
|
||||
|
||||
/**
|
||||
* The property of "username"
|
||||
*
|
||||
* @return empty as default value
|
||||
* @see #USERNAME_PLACEHOLDER
|
||||
*/
|
||||
String username() default USERNAME_PLACEHOLDER;
|
||||
|
||||
/**
|
||||
* The property of "password"
|
||||
*
|
||||
* @return empty as default value
|
||||
* @see #PASSWORD_PLACEHOLDER
|
||||
*/
|
||||
String password() default PASSWORD_PLACEHOLDER;
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -274,14 +274,13 @@ public class ServerHttpAgent implements HttpAgent {
|
||||
}
|
||||
|
||||
private void injectSecurityInfo(List<String> params) {
|
||||
ArrayList<String> list = (ArrayList) params;
|
||||
if (StringUtils.isNotBlank(securityProxy.getAccessToken())) {
|
||||
list.add(Constants.ACCESS_TOKEN);
|
||||
list.add(securityProxy.getAccessToken());
|
||||
params.add(Constants.ACCESS_TOKEN);
|
||||
params.add(securityProxy.getAccessToken());
|
||||
}
|
||||
if (StringUtils.isNotBlank(namespaceId)) {
|
||||
list.add("tenant");
|
||||
list.add(namespaceId);
|
||||
if (StringUtils.isNotBlank(namespaceId) && !params.contains(SpasAdapter.TENANT_KEY)) {
|
||||
params.add(SpasAdapter.TENANT_KEY);
|
||||
params.add(namespaceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,7 +359,10 @@ public class ClientWorker {
|
||||
*/
|
||||
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws IOException {
|
||||
|
||||
List<String> params = Arrays.asList(Constants.PROBE_MODIFY_REQUEST, probeUpdateString);
|
||||
|
||||
List<String> params = new ArrayList<String>(2);
|
||||
params.add(Constants.PROBE_MODIFY_REQUEST);
|
||||
params.add(probeUpdateString);
|
||||
|
||||
List<String> headers = new ArrayList<String>(2);
|
||||
headers.add("Long-Pulling-Timeout");
|
||||
@ -516,6 +519,7 @@ public class ClientWorker {
|
||||
|
||||
// check server config
|
||||
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
|
||||
LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
|
||||
|
||||
for (String groupKey : changedGroupKeys) {
|
||||
String[] key = GroupKey.parseKey(groupKey);
|
||||
|
@ -101,5 +101,5 @@ public class SpasAdapter {
|
||||
}
|
||||
|
||||
private static final String GROUP_KEY = "group";
|
||||
private static final String TENANT_KEY = "tenant";
|
||||
public static final String TENANT_KEY = "tenant";
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ public class NamingProxy {
|
||||
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
|
||||
|
||||
refreshSrvIfNeed();
|
||||
securityProxy.login(getServerList());
|
||||
}
|
||||
|
||||
public List<String> getServerListFromEndpoint() {
|
||||
|
@ -28,10 +28,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -111,7 +108,9 @@ public class SecurityProxy {
|
||||
public boolean login(String server) {
|
||||
|
||||
if (StringUtils.isNotBlank(username)) {
|
||||
String body = "username=" + username + "&password=" + password;
|
||||
Map<String, String> params = new HashMap<String, String>(2);
|
||||
params.put("username", username);
|
||||
String body = "password=" + password;
|
||||
String url = "http://" + server + contextPath + LOGIN_URL;
|
||||
|
||||
if (server.contains(Constants.HTTP_PREFIX)) {
|
||||
@ -119,7 +118,7 @@ public class SecurityProxy {
|
||||
}
|
||||
|
||||
HttpClient.HttpResult result = HttpClient.request(url, new ArrayList<String>(2),
|
||||
new HashMap<String, String>(2), body, Charsets.UTF_8.name(), HttpMethod.POST);
|
||||
params, body, Charsets.UTF_8.name(), HttpMethod.POST);
|
||||
|
||||
if (result.code != HttpURLConnection.HTTP_OK) {
|
||||
SECURITY_LOGGER.error("login failed: {}", JSON.toJSONString(result));
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.alibaba.nacos.config.server.auth;
|
||||
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import com.alibaba.nacos.core.auth.Resource;
|
||||
import com.alibaba.nacos.core.auth.ResourceParser;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -39,13 +38,13 @@ public class ConfigResourceParser implements ResourceParser {
|
||||
String groupName = req.getParameter("group");
|
||||
String dataId = req.getParameter("dataId");
|
||||
|
||||
if (StringUtils.isBlank(namespaceId)) {
|
||||
namespaceId = Constants.DEFAULT_NAMESPACE_ID;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(namespaceId).append(Resource.SPLITTER);
|
||||
if (StringUtils.isNotBlank(namespaceId)) {
|
||||
sb.append(namespaceId);
|
||||
}
|
||||
|
||||
sb.append(Resource.SPLITTER);
|
||||
|
||||
if (StringUtils.isBlank(dataId)) {
|
||||
sb.append("*")
|
||||
|
@ -282,6 +282,8 @@ public class ConfigController {
|
||||
throw new IllegalArgumentException("invalid probeModify");
|
||||
}
|
||||
|
||||
log.info("listen config id:" + probeModify);
|
||||
|
||||
probeModify = URLDecoder.decode(probeModify, Constants.ENCODE);
|
||||
|
||||
Map<String, String> clientMd5Map;
|
||||
@ -291,6 +293,8 @@ public class ConfigController {
|
||||
throw new IllegalArgumentException("invalid probeModify");
|
||||
}
|
||||
|
||||
log.info("listen config id 2:" + probeModify);
|
||||
|
||||
// do long-polling
|
||||
inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
|
||||
}
|
||||
@ -376,7 +380,7 @@ public class ConfigController {
|
||||
}
|
||||
|
||||
@DeleteMapping(params = "beta=true")
|
||||
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
|
||||
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
|
||||
public RestResult<Boolean> stopBeta(@RequestParam(value = "dataId") String dataId,
|
||||
@RequestParam(value = "group") String group,
|
||||
@RequestParam(value = "tenant", required = false,
|
||||
@ -545,6 +549,7 @@ public class ConfigController {
|
||||
}
|
||||
|
||||
@PostMapping(params = "clone=true")
|
||||
@Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
|
||||
public RestResult<Map<String, Object>> cloneConfig(HttpServletRequest request,
|
||||
@RequestParam(value = "src_user", required = false) String srcUser,
|
||||
@RequestParam(value = "tenant", required = true) String namespace,
|
||||
|
@ -24,6 +24,7 @@ import com.alibaba.nacos.config.server.service.LongPollingService;
|
||||
import com.alibaba.nacos.config.server.service.PersistService;
|
||||
import com.alibaba.nacos.config.server.service.trace.ConfigTraceService;
|
||||
import com.alibaba.nacos.config.server.utils.*;
|
||||
import com.alibaba.nacos.core.utils.Loggers;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -98,6 +99,8 @@ public class ConfigServletInner {
|
||||
request.setAttribute("content", newResult);
|
||||
}
|
||||
|
||||
Loggers.AUTH.info("new content:" + newResult);
|
||||
|
||||
// 禁用缓存
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setDateHeader("Expires", 0);
|
||||
|
@ -186,7 +186,8 @@ CREATE TABLE users (
|
||||
|
||||
CREATE TABLE roles (
|
||||
username varchar(50) NOT NULL,
|
||||
role varchar(50) NOT NULL
|
||||
role varchar(50) NOT NULL,
|
||||
constraint uk_username_role UNIQUE (username,role)
|
||||
);
|
||||
|
||||
CREATE TABLE permissions (
|
||||
|
@ -181,7 +181,8 @@ CREATE TABLE users (
|
||||
|
||||
CREATE TABLE roles (
|
||||
username varchar(50) NOT NULL,
|
||||
role varchar(50) NOT NULL
|
||||
role varchar(50) NOT NULL,
|
||||
constraint uk_username_role UNIQUE (username,role)
|
||||
);
|
||||
|
||||
CREATE TABLE permissions (
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
</parent>
|
||||
<artifactId>nacos-console</artifactId>
|
||||
<!--<packaging>war</packaging>-->
|
||||
|
@ -51,7 +51,7 @@ public class HealthController {
|
||||
* @return HTTP code equal to 200 indicates that Nacos is in right states. HTTP code equal to 500 indicates that
|
||||
* Nacos is in broken states.
|
||||
*/
|
||||
@GetMapping("liveness")
|
||||
@GetMapping("/liveness")
|
||||
public ResponseEntity liveness() {
|
||||
return ResponseEntity.ok().body("OK");
|
||||
}
|
||||
@ -62,7 +62,7 @@ public class HealthController {
|
||||
* @return HTTP code equal to 200 indicates that Nacos is ready. HTTP code equal to 500 indicates that Nacos is not
|
||||
* ready.
|
||||
*/
|
||||
@GetMapping("readiness")
|
||||
@GetMapping("/readiness")
|
||||
public ResponseEntity readiness(HttpServletRequest request) {
|
||||
boolean isConfigReadiness = isConfigReadiness();
|
||||
boolean isNamingReadiness = isNamingReadiness(request);
|
||||
|
@ -20,6 +20,9 @@ import com.alibaba.nacos.config.server.model.TenantInfo;
|
||||
import com.alibaba.nacos.config.server.service.PersistService;
|
||||
import com.alibaba.nacos.console.model.Namespace;
|
||||
import com.alibaba.nacos.console.model.NamespaceAllInfo;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.core.auth.ActionTypes;
|
||||
import com.alibaba.nacos.core.auth.Secured;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -55,6 +58,7 @@ public class NamespaceController {
|
||||
* @return namespace list
|
||||
*/
|
||||
@GetMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.READ)
|
||||
public RestResult<List<Namespace>> getNamespaces(HttpServletRequest request, HttpServletResponse response) {
|
||||
RestResult<List<Namespace>> rr = new RestResult<List<Namespace>>();
|
||||
rr.setCode(200);
|
||||
@ -82,6 +86,7 @@ public class NamespaceController {
|
||||
* @return namespace all info
|
||||
*/
|
||||
@GetMapping(params = "show=all")
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.READ)
|
||||
public NamespaceAllInfo getNamespace(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("namespaceId") String namespaceId) {
|
||||
// TODO 获取用kp
|
||||
@ -106,6 +111,7 @@ public class NamespaceController {
|
||||
* @return whether create ok
|
||||
*/
|
||||
@PostMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE)
|
||||
public Boolean createNamespace(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("customNamespaceId") String namespaceId,
|
||||
@RequestParam("namespaceName") String namespaceName,
|
||||
@ -138,6 +144,7 @@ public class NamespaceController {
|
||||
* @return java.lang.Boolean
|
||||
*/
|
||||
@GetMapping(params = "checkNamespaceIdExist=true")
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.READ)
|
||||
public Boolean checkNamespaceIdExist(@RequestParam("customNamespaceId") String namespaceId){
|
||||
if(StringUtils.isBlank(namespaceId)){
|
||||
return false;
|
||||
@ -154,6 +161,7 @@ public class NamespaceController {
|
||||
* @return whether edit ok
|
||||
*/
|
||||
@PutMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE)
|
||||
public Boolean editNamespace(@RequestParam("namespace") String namespace,
|
||||
@RequestParam("namespaceShowName") String namespaceShowName,
|
||||
@RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) {
|
||||
@ -171,6 +179,7 @@ public class NamespaceController {
|
||||
* @return whether del ok
|
||||
*/
|
||||
@DeleteMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE)
|
||||
public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("namespaceId") String namespaceId) {
|
||||
persistService.removeTenantInfoAtomic("1", namespaceId);
|
||||
|
@ -34,7 +34,7 @@ import java.util.Map;
|
||||
@RequestMapping("/v1/console/server")
|
||||
public class ServerStateController {
|
||||
|
||||
@GetMapping("state")
|
||||
@GetMapping("/state")
|
||||
public ResponseEntity serverState() {
|
||||
Map<String,String> serverState = new HashMap<>(3);
|
||||
serverState.put("standalone_mode",SystemUtils.STANDALONE_MODE ?
|
||||
|
@ -17,10 +17,12 @@ package com.alibaba.nacos.console.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import com.alibaba.nacos.config.server.auth.RoleInfo;
|
||||
import com.alibaba.nacos.config.server.model.RestResult;
|
||||
import com.alibaba.nacos.config.server.model.User;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthManager;
|
||||
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUser;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
|
||||
import com.alibaba.nacos.console.utils.JwtTokenUtils;
|
||||
@ -37,6 +39,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User related methods entry
|
||||
@ -57,6 +60,9 @@ public class UserController {
|
||||
@Autowired
|
||||
private NacosUserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Autowired
|
||||
private NacosRoleServiceImpl roleService;
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
|
||||
@ -94,7 +100,14 @@ public class UserController {
|
||||
@DeleteMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
|
||||
public Object deleteUser(@RequestParam String username) {
|
||||
|
||||
List<RoleInfo> roleInfoList = roleService.getRoles(username);
|
||||
if (roleInfoList != null) {
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
|
||||
throw new IllegalArgumentException("cannot delete admin: " + username);
|
||||
}
|
||||
}
|
||||
}
|
||||
userDetailsService.deleteUser(username);
|
||||
return new RestResult<>(200, "delete user ok!");
|
||||
}
|
||||
|
@ -81,12 +81,15 @@ public class NacosAuthManager implements AuthManager {
|
||||
user.setUserName(username);
|
||||
user.setToken(token);
|
||||
List<RoleInfo> roleInfoList = roleService.getRoles(username);
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
|
||||
user.setGlobalAdmin(true);
|
||||
break;
|
||||
if (roleInfoList != null) {
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
|
||||
user.setGlobalAdmin(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import com.alibaba.nacos.core.auth.Permission;
|
||||
import com.alibaba.nacos.core.utils.Loggers;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.mina.util.ConcurrentHashSet;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -45,7 +46,7 @@ import java.util.regex.Pattern;
|
||||
@Service
|
||||
public class NacosRoleServiceImpl {
|
||||
|
||||
public static final String GLOBAL_ADMIN_ROLE = "GLOBAL_ADMIN";
|
||||
public static final String GLOBAL_ADMIN_ROLE = "ROLE_ADMIN";
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
@ -59,6 +60,8 @@ public class NacosRoleServiceImpl {
|
||||
@Autowired
|
||||
private PermissionPersistService permissionPersistService;
|
||||
|
||||
private Set<String> roleSet = new ConcurrentHashSet<>();
|
||||
|
||||
private Map<String, List<RoleInfo>> roleInfoMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, List<PermissionInfo>> permissionInfoMap = new ConcurrentHashMap<>();
|
||||
@ -70,22 +73,23 @@ public class NacosRoleServiceImpl {
|
||||
if (roleInfoPage == null) {
|
||||
return;
|
||||
}
|
||||
Set<String> roleSet = new HashSet<>(16);
|
||||
Set<String> tmpRoleSet = new HashSet<>(16);
|
||||
Map<String, List<RoleInfo>> tmpRoleInfoMap = new ConcurrentHashMap<>(16);
|
||||
for (RoleInfo roleInfo : roleInfoPage.getPageItems()) {
|
||||
if (!tmpRoleInfoMap.containsKey(roleInfo.getUsername())) {
|
||||
tmpRoleInfoMap.put(roleInfo.getUsername(), new ArrayList<>());
|
||||
}
|
||||
tmpRoleInfoMap.get(roleInfo.getUsername()).add(roleInfo);
|
||||
roleSet.add(roleInfo.getRole());
|
||||
tmpRoleSet.add(roleInfo.getRole());
|
||||
}
|
||||
|
||||
Map<String, List<PermissionInfo>> tmpPermissionInfoMap = new ConcurrentHashMap<>(16);
|
||||
for (String role : roleSet) {
|
||||
for (String role : tmpRoleSet) {
|
||||
Page<PermissionInfo> permissionInfoPage = permissionPersistService.getPermissions(role, 1, Integer.MAX_VALUE);
|
||||
tmpPermissionInfoMap.put(role, permissionInfoPage.getPageItems());
|
||||
}
|
||||
|
||||
roleSet = tmpRoleSet;
|
||||
roleInfoMap = tmpRoleInfoMap;
|
||||
permissionInfoMap = tmpPermissionInfoMap;
|
||||
} catch (Exception e) {
|
||||
@ -178,7 +182,11 @@ public class NacosRoleServiceImpl {
|
||||
if (userDetailsService.getUser(username) == null) {
|
||||
throw new IllegalArgumentException("user '" + username + "' not found!");
|
||||
}
|
||||
if (GLOBAL_ADMIN_ROLE.equals(role)) {
|
||||
throw new IllegalArgumentException("role '" + GLOBAL_ADMIN_ROLE + "' is not permitted to create!");
|
||||
}
|
||||
rolePersistService.addRole(role, username);
|
||||
roleSet.add(role);
|
||||
}
|
||||
|
||||
public void deleteRole(String role, String userName) {
|
||||
@ -186,8 +194,8 @@ public class NacosRoleServiceImpl {
|
||||
}
|
||||
|
||||
public void deleteRole(String role) {
|
||||
|
||||
rolePersistService.deleteRole(role);
|
||||
roleSet.remove(role);
|
||||
}
|
||||
|
||||
public Page<PermissionInfo> getPermissionsFromDatabase(String role, int pageNo, int pageSize) {
|
||||
@ -199,6 +207,9 @@ public class NacosRoleServiceImpl {
|
||||
}
|
||||
|
||||
public void addPermission(String role, String resource, String action) {
|
||||
if (!roleSet.contains(role)) {
|
||||
throw new IllegalArgumentException("role " + role + " not found!");
|
||||
}
|
||||
permissionPersistService.addPermission(role, resource, action);
|
||||
}
|
||||
|
||||
|
@ -35,3 +35,6 @@ nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/
|
||||
nacos.core.auth.system.type=nacos
|
||||
nacos.core.auth.enabled=false
|
||||
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
|
||||
|
||||
|
||||
tldSkipPatterns=derbyLocale_*.jar,jaxb-api.jar,jsr173_1.0_api.jar,jaxb1-impl.jar,activation.jar
|
||||
|
@ -30,6 +30,7 @@
|
||||
"generator-star-spacing": "off",
|
||||
"wrap-iife": "off",
|
||||
"arrow-parens": "off",
|
||||
"indent": "off"
|
||||
"indent": "off",
|
||||
"comma-dangle": "off"
|
||||
}
|
||||
}
|
||||
|
@ -16,14 +16,14 @@ const base = require('./webpack.base.conf');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
const [cssLoader] = base.module.rules;
|
||||
cssLoader.use.push({
|
||||
loader: '@alifd/next-theme-loader',
|
||||
options: {
|
||||
modifyVars: {
|
||||
'$icon-font-path':'"/nacos/console-fe/public/icons/icon-font"',
|
||||
'$icon-font-path': '"/nacos/console-fe/public/icons/icon-font"',
|
||||
'$font-custom-path': '"/nacos/console-fe/public/fonts/"'
|
||||
}
|
||||
}
|
||||
@ -40,8 +40,10 @@ module.exports = Object.assign({}, base, {
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(path.resolve(__dirname, '../dist'), {
|
||||
root: path.resolve(__dirname, '../'),
|
||||
new CleanWebpackPlugin({
|
||||
cleanOnceBeforeBuildPatterns:[
|
||||
path.resolve(__dirname, '../dist/**'),
|
||||
]
|
||||
}),
|
||||
...base.plugins,
|
||||
new MiniCssExtractPlugin({
|
||||
|
@ -27,55 +27,56 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alifd/next-theme-loader": "^1.3.1",
|
||||
"@babel/cli": "^7.2.3",
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/plugin-proposal-decorators": "^7.2.3",
|
||||
"@babel/preset-env": "^7.2.3",
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"@babel/cli": "^7.7.7",
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/preset-env": "^7.7.7",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-import": "^1.12.0",
|
||||
"babel-preset-react-app": "^6.1.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^2.0.2",
|
||||
"eslint": "^5.11.0",
|
||||
"eslint-config-ali": "^4.1.0",
|
||||
"eslint-config-prettier": "^3.3.0",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"babel-plugin-import": "^1.13.0",
|
||||
"babel-preset-react-app": "^9.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1 ",
|
||||
"cross-env": "^6.0.3",
|
||||
"css-loader": "^3.4.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-ali": "^9.0.2",
|
||||
"eslint-config-prettier": "^6.8.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-prettier": "^3.0.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"file-loader": "^2.0.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^1.1.4",
|
||||
"lint-staged": "^8.0.4",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"prettier": "1.15.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"uglifyjs-webpack-plugin": "^2.1.0",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.28.2",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.1.13"
|
||||
"husky": "^3.1.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"prettier": "1.19.1",
|
||||
"sass-loader": "^8.0.0",
|
||||
"style-loader": "^1.1.2",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"url-loader": "^3.0.0",
|
||||
"webpack": "^4.41.4",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alifd/next": "^1.15.12",
|
||||
"@alifd/next": "^1.17.4",
|
||||
"axios": "^0.18.0",
|
||||
"jquery": "^3.3.1",
|
||||
"moment": "^2.23.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^4.0.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"yamljs": "^0.3.0"
|
||||
}
|
||||
|
@ -125,9 +125,7 @@ class RegionGroup extends React.Component {
|
||||
? false
|
||||
: window.location.search.indexOf('hideTopbar=') === -1,
|
||||
},
|
||||
() => {
|
||||
this.setRegionWidth();
|
||||
}
|
||||
() => this.setRegionWidth()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -23,3 +23,13 @@ export const GET_STATE = 'GET_STATE';
|
||||
|
||||
export const GET_SUBSCRIBERS = 'GET_SUBSCRIBERS';
|
||||
export const REMOVE_SUBSCRIBERS = 'REMOVE_SUBSCRIBERS';
|
||||
|
||||
export const UPDATE_USER = 'UPDATE_USER';
|
||||
export const SIGN_IN = 'SIGN_IN';
|
||||
export const USER_LIST = 'USER_LIST';
|
||||
|
||||
export const ROLE_LIST = 'ROLE_LIST';
|
||||
|
||||
export const PERMISSIONS_LIST = 'PERMISSIONS_LIST';
|
||||
|
||||
export const GET_NAMESPACES = 'GET_NAMESPACES';
|
||||
|
@ -12,9 +12,8 @@
|
||||
*/
|
||||
|
||||
import projectConfig from './config';
|
||||
import moment from 'moment';
|
||||
import $ from 'jquery';
|
||||
import i18DocObj from './i18ndoc';
|
||||
import { Message } from '@alifd/next';
|
||||
|
||||
const global = window;
|
||||
|
||||
@ -205,120 +204,6 @@ const nacosUtils = (function(_global) {
|
||||
};
|
||||
})(global);
|
||||
|
||||
const aliwareIntl = (function(_global) {
|
||||
/**
|
||||
* 国际化构造方法
|
||||
* @param {Object} options 配置信息
|
||||
*/
|
||||
function AliwareI18n(options) {
|
||||
// let currentLocal = options.currentLocal || navigator.language || navigator.userLanguage;
|
||||
|
||||
const nowData = options.locals;
|
||||
this.nowData = nowData;
|
||||
this.setMomentLocale(this.currentLanguageCode);
|
||||
}
|
||||
|
||||
let aliwareLocal = aliwareGetCookieByKeyName('aliyun_lang') || 'zh';
|
||||
let aliwareLocalSite = aliwareGetCookieByKeyName('aliyun_country') || 'cn';
|
||||
aliwareLocal = aliwareLocal.toLowerCase();
|
||||
aliwareLocalSite = aliwareLocalSite.toLowerCase();
|
||||
// 当前语言
|
||||
AliwareI18n.prototype.currentLocal = aliwareLocal;
|
||||
// 当前地区
|
||||
AliwareI18n.prototype.currentSite = aliwareLocalSite;
|
||||
// 当前语言-地区
|
||||
AliwareI18n.prototype.currentLanguageCode =
|
||||
aliwareGetCookieByKeyName('docsite_language') || `${aliwareLocal}-${aliwareLocalSite}`;
|
||||
/**
|
||||
* 通过key获取对应国际化文案
|
||||
* @param {String} key 国际化key
|
||||
*/
|
||||
AliwareI18n.prototype.get = function(key) {
|
||||
return this.nowData[key];
|
||||
};
|
||||
/**
|
||||
* 修改国际化文案数据
|
||||
* @param {String} local 语言信息
|
||||
*/
|
||||
AliwareI18n.prototype.changeLanguage = function(local) {
|
||||
this.nowData = i18DocObj[local] || {};
|
||||
};
|
||||
/**
|
||||
* 数字国际化
|
||||
* @param {Number} num 数字
|
||||
*/
|
||||
AliwareI18n.prototype.intlNumberFormat = function(num) {
|
||||
if (typeof Intl !== 'object' || typeof Intl.NumberFormat !== 'function') {
|
||||
return num;
|
||||
}
|
||||
try {
|
||||
return new Intl.NumberFormat(this.currentLanguageCode).format(num || 0);
|
||||
} catch (error) {
|
||||
return num;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 时间戳格式化
|
||||
* @param {Number} num 时间戳
|
||||
* @param {Object} initOption 配置信息
|
||||
*/
|
||||
AliwareI18n.prototype.intlTimeFormat = function(num = Date.now(), initOption = {}) {
|
||||
try {
|
||||
const date = Object.prototype.toString.call(num) === '[object Date]' ? num : new Date(num);
|
||||
const options = Object.assign(
|
||||
{},
|
||||
{
|
||||
// weekday: "short",
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
},
|
||||
initOption
|
||||
);
|
||||
return date.toLocaleDateString(this.currentLanguageCode, options);
|
||||
} catch (error) {
|
||||
return typeof moment === 'function' ? moment(num).format() : '--';
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 获取当前时间格式
|
||||
* @param {String} language 语言信息: zh/en
|
||||
*/
|
||||
AliwareI18n.prototype.getIntlTimeFormat = function(_language) {
|
||||
const language = _language || aliwareLocal;
|
||||
const langObj = {
|
||||
zh: 'YYYY年M月D日 HH:mm:ss',
|
||||
en: 'MMM D, YYYY, h:mm:ss A',
|
||||
default: 'YYYY-MM-DD HH:mm:ss',
|
||||
};
|
||||
return langObj[language] ? langObj[language] : langObj.default;
|
||||
};
|
||||
/**
|
||||
* 设置moment的locale
|
||||
* @param {String} languageCode 语言信息: zh-ch/en-us
|
||||
*/
|
||||
AliwareI18n.prototype.setMomentLocale = function(languageCode) {
|
||||
if (Object.prototype.toString.call(moment) === '[object Function]') {
|
||||
moment.locale(languageCode || this.currentLanguageCode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return new AliwareI18n({
|
||||
currentLocal: `${aliwareLocal}`,
|
||||
locals:
|
||||
i18DocObj[AliwareI18n.prototype.currentLanguageCode] ||
|
||||
i18DocObj['en-us'] ||
|
||||
i18DocObj['zh-cn'] ||
|
||||
{},
|
||||
});
|
||||
})(global);
|
||||
|
||||
/**
|
||||
* 获取url中的参数
|
||||
*/
|
||||
@ -561,7 +446,7 @@ const request = (function(_global) {
|
||||
} catch (e) {}
|
||||
// 设置自动loading效果
|
||||
if (serviceObj.autoLoading) {
|
||||
nacosUtils.openLoading();
|
||||
// nacosUtils.openLoading();
|
||||
const prevComplete = config.complete;
|
||||
config.complete = function() {
|
||||
nacosUtils.closeLoading();
|
||||
@ -598,10 +483,15 @@ const request = (function(_global) {
|
||||
// 处理后置中间件
|
||||
config = handleMiddleWare.apply(this, [config, ...args, middlewareBackList]);
|
||||
|
||||
const { accessToken = '' } = JSON.parse(localStorage.token || '{}');
|
||||
const [url, paramsStr = ''] = config.url.split('?');
|
||||
const params = paramsStr.split('&');
|
||||
params.push(`accessToken=${accessToken}`);
|
||||
|
||||
return $.ajax(
|
||||
Object.assign({}, config, {
|
||||
type: config.type,
|
||||
url: config.url,
|
||||
url: [url, params.join('&')].join('?'),
|
||||
data: config.data || '',
|
||||
dataType: config.dataType || 'json',
|
||||
beforeSend(xhr) {
|
||||
@ -615,17 +505,26 @@ const request = (function(_global) {
|
||||
success => {},
|
||||
error => {
|
||||
// 处理403 forbidden
|
||||
if (error && (error.status === 403 || error.status === 401)) {
|
||||
const { status, responseJSON = {} } = error || {};
|
||||
if (responseJSON.message) {
|
||||
Message.error(responseJSON.message);
|
||||
}
|
||||
if (
|
||||
[401, 403].includes(status) &&
|
||||
['unknown user!', 'token invalid', 'token expired!'].includes(responseJSON.message)
|
||||
) {
|
||||
// 跳转至login页
|
||||
// TODO: 用 react-router 重写,改造成本比较高,这里先hack
|
||||
const url = window.location.href;
|
||||
// TODO: 后端返回细致的错误码,如果原始密码不对 不应该直接跳到登陆页
|
||||
if (url.includes('password')) {
|
||||
return;
|
||||
return error;
|
||||
}
|
||||
localStorage.removeItem('token');
|
||||
const base_url = url.split('#')[0];
|
||||
window.location = `${base_url}#/login`;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -645,10 +544,9 @@ export {
|
||||
nacosEvent,
|
||||
nacosUtils,
|
||||
aliwareGetCookieByKeyName,
|
||||
aliwareIntl,
|
||||
removeParams,
|
||||
getParams,
|
||||
setParam,
|
||||
setParams,
|
||||
removeParams,
|
||||
request,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -44,6 +44,9 @@ import ServiceList from './pages/ServiceManagement/ServiceList';
|
||||
import ServiceDetail from './pages/ServiceManagement/ServiceDetail';
|
||||
import SubscriberList from './pages/ServiceManagement/SubscriberList';
|
||||
import ClusterNodeList from './pages/ClusterManagement/ClusterNodeList';
|
||||
import UserManagement from './pages/AuthorityControl/UserManagement';
|
||||
import PermissionsManagement from './pages/AuthorityControl/PermissionsManagement';
|
||||
import RolesManagement from './pages/AuthorityControl/RolesManagement';
|
||||
import Welcome from './pages/Welcome/Welcome';
|
||||
|
||||
import reducers from './reducers';
|
||||
@ -65,10 +68,7 @@ const reducer = combineReducers({
|
||||
|
||||
const store = createStore(
|
||||
reducer,
|
||||
compose(
|
||||
applyMiddleware(thunk),
|
||||
window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f
|
||||
)
|
||||
compose(applyMiddleware(thunk), window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f)
|
||||
);
|
||||
|
||||
const MENU = [
|
||||
@ -89,12 +89,12 @@ const MENU = [
|
||||
{ path: '/serviceDetail', component: ServiceDetail },
|
||||
{ path: '/subscriberList', component: SubscriberList },
|
||||
{ path: '/clusterManagement', component: ClusterNodeList },
|
||||
{ path: '/userManagement', component: UserManagement },
|
||||
{ path: '/rolesManagement', component: RolesManagement },
|
||||
{ path: '/permissionsManagement', component: PermissionsManagement },
|
||||
];
|
||||
|
||||
@connect(
|
||||
state => ({ ...state.locale }),
|
||||
{ changeLanguage }
|
||||
)
|
||||
@connect(state => ({ ...state.locale }), { changeLanguage })
|
||||
class App extends React.Component {
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
|
@ -1217,10 +1217,6 @@ form.vertical-margin-lg .form-group {
|
||||
border-color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row-bg-green {
|
||||
background-color: #e4fdda;
|
||||
}
|
||||
|
@ -22,10 +22,7 @@ import { changeLanguage } from '@/reducers/locale';
|
||||
import './index.scss';
|
||||
|
||||
@withRouter
|
||||
@connect(
|
||||
state => ({ ...state.locale }),
|
||||
{ changeLanguage }
|
||||
)
|
||||
@connect(state => ({ ...state.locale }), { changeLanguage })
|
||||
@ConfigProvider.config
|
||||
class Header extends React.Component {
|
||||
static displayName = 'Header';
|
||||
@ -56,10 +53,15 @@ class Header extends React.Component {
|
||||
getUsername = () => {
|
||||
const token = window.localStorage.getItem('token');
|
||||
if (token) {
|
||||
const base64Url = token.split('.')[1];
|
||||
const [, base64Url = ''] = token.split('.');
|
||||
const base64 = base64Url.replace('-', '+').replace('_', '/');
|
||||
const parsedToken = JSON.parse(window.atob(base64));
|
||||
return parsedToken.sub;
|
||||
try {
|
||||
const parsedToken = JSON.parse(window.atob(base64));
|
||||
return parsedToken.sub;
|
||||
} catch (e) {
|
||||
delete localStorage.token;
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
@ -13,506 +13,127 @@
|
||||
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ConfigProvider, Icon } from '@alifd/next';
|
||||
import Header from './Header';
|
||||
import $ from 'jquery';
|
||||
import { connect } from 'react-redux';
|
||||
import { setParams } from '../globalLib';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ConfigProvider, Icon, Menu } from '@alifd/next';
|
||||
import Header from './Header';
|
||||
import { getState } from '../reducers/base';
|
||||
import _menu from '../menu';
|
||||
import getMenuData from './menu';
|
||||
|
||||
import './index.scss';
|
||||
const { SubMenu, Item } = Menu;
|
||||
|
||||
@withRouter
|
||||
@connect(
|
||||
state => ({ ...state.locale, ...state.base }),
|
||||
{ getState }
|
||||
)
|
||||
@connect(state => ({ ...state.locale, ...state.base }), { getState })
|
||||
@ConfigProvider.config
|
||||
class MainLayout extends React.Component {
|
||||
static displayName = 'MainLayout';
|
||||
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
locale: PropTypes.object,
|
||||
children: PropTypes.any,
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
version: PropTypes.any,
|
||||
functionMode: PropTypes.any,
|
||||
getState: PropTypes.func,
|
||||
functionMode: PropTypes.string,
|
||||
children: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.deepNav = [];
|
||||
this.oneLevelNavArr = {}; // 平行导航map
|
||||
this.state = {
|
||||
navList: [..._menu.data],
|
||||
leftBarClose: false,
|
||||
showLink: null,
|
||||
navRow: [],
|
||||
noChild: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getState();
|
||||
this.refreshNav();
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.props.history.goBack();
|
||||
}
|
||||
|
||||
nacosToggleNav(id, event) {
|
||||
event.preventDefault();
|
||||
const nowNav = document.getElementById(id);
|
||||
const iconClass = nowNav.querySelector('.iconshow');
|
||||
const subNav = nowNav.querySelector('.subnavlist');
|
||||
const { classList } = iconClass;
|
||||
let tmpClassName = 'iconshow ';
|
||||
for (let i = 0; i < classList.length; i++) {
|
||||
if (classList[i] === 'icon-arrow-down') {
|
||||
subNav.style.display = 'none';
|
||||
subNav.className += ' hidden';
|
||||
tmpClassName += 'icon-arrow-right';
|
||||
}
|
||||
if (classList[i] === 'icon-arrow-right') {
|
||||
tmpClassName += 'icon-arrow-down';
|
||||
subNav.className = subNav.className.replace(/hidden/g, '');
|
||||
subNav.style.display = 'block';
|
||||
}
|
||||
}
|
||||
iconClass.className = tmpClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the back button
|
||||
* TODO: this.props.history.goBack(); ???
|
||||
* @param url
|
||||
*/
|
||||
nacosGoBack(url) {
|
||||
const params = window.location.hash.split('?')[1];
|
||||
const urlArr = params.split('&') || [];
|
||||
const queryParams = [];
|
||||
for (let i = 0; i < urlArr.length; i++) {
|
||||
if (
|
||||
urlArr[i].split('=')[0] !== '_k' &&
|
||||
urlArr[i].split('=')[0] !== 'dataId' &&
|
||||
urlArr[i].split('=')[0] !== 'group'
|
||||
) {
|
||||
if (urlArr[i].split('=')[0] === 'searchDataId') {
|
||||
queryParams.push(`dataId=${urlArr[i].split('=')[1]}`);
|
||||
} else if (urlArr[i].split('=')[0] === 'searchGroup') {
|
||||
queryParams.push(`group=${urlArr[i].split('=')[1]}`);
|
||||
} else {
|
||||
queryParams.push(urlArr[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (localStorage.getItem('namespace')) {
|
||||
queryParams.push(`namespace=${localStorage.getItem('namespace')}`);
|
||||
}
|
||||
this.props.history.push(`/${url}?${queryParams.join('&')}`);
|
||||
}
|
||||
|
||||
nacosEnterBack() {
|
||||
document.getElementById('backarrow').style.color = '#09c';
|
||||
}
|
||||
|
||||
nacosOutBack() {
|
||||
document.getElementById('backarrow').style.color = '#546478';
|
||||
}
|
||||
|
||||
nacosToggleLeftBar() {
|
||||
if (!this.nacosOutDom) return;
|
||||
if (!this.state.leftBarClose) {
|
||||
// 关闭
|
||||
this.nacosOutDom.className = 'viewFramework-product';
|
||||
this.nacosLeftBarDom.style.width = 0;
|
||||
this.nacosBodyDom.style.left = 0;
|
||||
this.nacosToggleIconDom.style.left = 0;
|
||||
} else {
|
||||
this.nacosOutDom.className = 'viewFramework-product viewFramework-product-col-1';
|
||||
this.nacosLeftBarDom.style.width = '180px';
|
||||
this.nacosBodyDom.style.left = '180px';
|
||||
this.nacosToggleIconDom.style.left = '160px';
|
||||
}
|
||||
|
||||
this.setState({
|
||||
leftBarClose: !this.state.leftBarClose,
|
||||
});
|
||||
}
|
||||
|
||||
navTo(url) {
|
||||
if (url !== '/configdetail' && url !== '/configeditor') {
|
||||
// 二级菜单不清空
|
||||
setParams({
|
||||
dataId: '',
|
||||
group: '',
|
||||
});
|
||||
}
|
||||
|
||||
const params = window.location.hash.split('?')[1];
|
||||
const urlArr = params.split('&') || [];
|
||||
const queryParams = [];
|
||||
for (let i = 0; i < urlArr.length; i++) {
|
||||
if (urlArr[i].split('=')[0] !== '_k') {
|
||||
queryParams.push(urlArr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.props.history.push(`${url}?${queryParams.join('&')}`);
|
||||
const { search } = this.props.location;
|
||||
this.props.history.push([url, search].join(''));
|
||||
}
|
||||
|
||||
nacosSetSpecialNav(item) {
|
||||
item.children.forEach(_item => {
|
||||
const obj = _item;
|
||||
isCurrentPath(url) {
|
||||
const { location } = this.props;
|
||||
return url === location.pathname ? 'current-path' : undefined;
|
||||
}
|
||||
|
||||
if (obj.dontUseChild === true) {
|
||||
obj.parentName = item.title;
|
||||
obj.parentId = item.id;
|
||||
obj.parentPath = `/${item.id}`;
|
||||
this.deepNav.push(obj);
|
||||
}
|
||||
if (_item.children) {
|
||||
this.nacosSetSpecialNav(_item);
|
||||
defaultOpenKeys() {
|
||||
const MenuData = getMenuData(this.props.functionMode);
|
||||
for (let i = 0, len = MenuData.length; i < len; i++) {
|
||||
const { children } = MenuData[i];
|
||||
if (children && children.filter(({ url }) => url === this.props.location.pathname).length) {
|
||||
return String(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isShowGoBack() {
|
||||
const urls = [];
|
||||
const MenuData = getMenuData(this.props.functionMode);
|
||||
MenuData.forEach(item => {
|
||||
if (item.url) urls.push(item.url);
|
||||
if (item.children) item.children.forEach(({ url }) => urls.push(url));
|
||||
});
|
||||
}
|
||||
|
||||
nacosNavAct(serviceName, match, location) {
|
||||
if (!match) {
|
||||
const formatpath = location.pathname.substr(1); // 得到当前路径
|
||||
const nowpathobj = this.oneLevelNavArr[formatpath]; // 根据平行导航匹配父类
|
||||
if (nowpathobj) {
|
||||
if (nowpathobj.parent === serviceName) {
|
||||
// 如果父类等于当前的导航则高亮
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
nacosLoopNavDeeply(data, parentServiceName) {
|
||||
// 深度遍历获取所有的导航数据
|
||||
data.forEach(item => {
|
||||
if (item) {
|
||||
const navObj = item;
|
||||
|
||||
const _parentServiceName = item.serviceName;
|
||||
navObj.parentServiceName = parentServiceName;
|
||||
this.oneLevelNavArr[item.serviceName] = navObj; // 得到每一个层级的导航映射
|
||||
if (item.children && item.children.length > 0) {
|
||||
this.nacosLoopNavDeeply(item.children, _parentServiceName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
activeNav(id) {
|
||||
if (this.preActNav) {
|
||||
this.preActNav.removeClass('active');
|
||||
}
|
||||
const nowNav = $(`#${id}`);
|
||||
nowNav.addClass('active');
|
||||
this.preActNav = nowNav;
|
||||
}
|
||||
|
||||
nacosLoopNav(data, _index = 0, parent) {
|
||||
const { locale = {}, location = {} } = this.props;
|
||||
const { pathname } = location;
|
||||
let index = _index;
|
||||
// 遍历导航,只显示2级
|
||||
const self = this;
|
||||
return data.map(item => {
|
||||
if (!item) return '';
|
||||
index++;
|
||||
if (item.dontUseChild === true) return '';
|
||||
if (item.children && item.children.length > 0) {
|
||||
if (item.isVirtual) {
|
||||
// 如果是虚拟菜单需要增加展开箭头
|
||||
const icon = item.isExtend ? (
|
||||
<span className="icon-arrow-down iconshow" />
|
||||
) : (
|
||||
<span className="icon-arrow-right iconshow" />
|
||||
);
|
||||
const hiddenClass = item.isExtend ? '' : 'hidden';
|
||||
return (
|
||||
<li
|
||||
style={{ display: item.enable ? 'block' : 'none' }}
|
||||
key={`${item.serviceName}`}
|
||||
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
|
||||
id={`${item.serviceName}`}
|
||||
>
|
||||
<div>
|
||||
<a href="" onClick={this.nacosToggleNav.bind(this, item.serviceName)}>
|
||||
<div className="nav-icon">{icon}</div>
|
||||
<div className="nav-title">{locale[item.serviceName]}</div>
|
||||
</a>
|
||||
</div>
|
||||
<ul className={`subnavlist ${hiddenClass}`}>
|
||||
{self.nacosLoopNav(item.children, index)}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<li
|
||||
className={pathname === `/${item.serviceName}` ? 'selected' : ''}
|
||||
key={`${item.serviceName}`}
|
||||
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
|
||||
onClick={this.navTo.bind(this, `/${item.serviceName}`)}
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
id={`${item.serviceName}`}
|
||||
onClick={this.activeNav.bind(this, `nav${index}`)}
|
||||
>
|
||||
<div className="nav-icon" />
|
||||
<div className="nav-title">{locale[item.serviceName]}</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<li
|
||||
className={pathname === `/${item.serviceName}` ? 'selected' : ''}
|
||||
key={`${item.serviceName}`}
|
||||
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
|
||||
onClick={this.navTo.bind(this, `/${item.serviceName}`)}
|
||||
>
|
||||
<a
|
||||
href={'javascript:;'}
|
||||
id={`${item.serviceName}`}
|
||||
onClick={this.activeNav.bind(this, `nav${index}`)}
|
||||
>
|
||||
<div className="nav-icon" />
|
||||
<div className="nav-title">{locale[item.serviceName]}</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
nacosGetNav(navList) {
|
||||
let navRow = ''; // 导航
|
||||
if (navList.length > 0) {
|
||||
navRow = <ul>{this.nacosLoopNav(navList)}</ul>;
|
||||
this.nacosLoopNavDeeply(navList); // 深度遍历导航树获得平行map
|
||||
}
|
||||
return navRow;
|
||||
}
|
||||
|
||||
renderNav() {
|
||||
const { navList } = this.state;
|
||||
this.nacosLeftBarDom = document.getElementById('viewFramework-product-navbar');
|
||||
this.nacosBodyDom = document.getElementById('viewFramework-product-body');
|
||||
this.nacosToggleIconDom = document.getElementById('viewFramework-product-navbar-collapse');
|
||||
this.nacosOutDom = document.getElementById('page-header-mask');
|
||||
const defaultNav = '/configurationManagement';
|
||||
this.props.history.listen(location => {
|
||||
if (this.preSimplePath && this.preSimplePath !== '/') {
|
||||
if (location.pathname.indexOf(this.preSimplePath) !== -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const simplePath = window.location.hash.split('?')[0];
|
||||
const navName = simplePath.substr('2');
|
||||
this.preSimplePath = simplePath;
|
||||
|
||||
if (navName === '') {
|
||||
this.props.history.push(defaultNav);
|
||||
setTimeout(() => {
|
||||
this.activeNav('configurationManagement');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const nowNavObj = this.oneLevelNavArr[navName];
|
||||
if (!nowNavObj) {
|
||||
this.setState({
|
||||
noChild: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { parentServiceName } = nowNavObj;
|
||||
|
||||
const parentNav = this.oneLevelNavArr[parentServiceName];
|
||||
if (simplePath !== '/' && nowNavObj && parentNav && !parentNav.isVirtual) {
|
||||
this.setState({
|
||||
showLink: (
|
||||
<div>
|
||||
<Icon
|
||||
type="arrow-left"
|
||||
onClick={this.nacosGoBack.bind(this, parentServiceName)}
|
||||
id={'backarrow'}
|
||||
onMouseOver={this.nacosEnterBack.bind(this)}
|
||||
onMouseLeave={this.nacosOutBack.bind(this)}
|
||||
style={{
|
||||
marginLeft: 77,
|
||||
marginTop: 0,
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
color: '#546478',
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
navRow: <ul>{this.nacosLoopNav([nowNavObj])}</ul>,
|
||||
});
|
||||
setTimeout(() => {
|
||||
const navid = navName;
|
||||
this.activeNav(navid);
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showLink: null,
|
||||
navRow: <ul>{this.nacosLoopNav(navList)}</ul>,
|
||||
});
|
||||
setTimeout(() => {
|
||||
const navid = navName;
|
||||
this.activeNav(navid);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refreshNav() {
|
||||
const { navList } = this.state;
|
||||
const { location, history, functionMode } = this.props;
|
||||
const [configUrl, serviceUrl, clusterUrl] = [
|
||||
'/configurationManagement',
|
||||
'/serviceManagement',
|
||||
'/clusterManagement',
|
||||
];
|
||||
this.setState(
|
||||
{
|
||||
navList: navList.map(item => {
|
||||
if (
|
||||
item.serviceName === 'configurationManagementVirtual' &&
|
||||
(functionMode === null || functionMode === 'config')
|
||||
) {
|
||||
item.enable = true;
|
||||
}
|
||||
if (
|
||||
item.serviceName === 'serviceManagementVirtual' &&
|
||||
(functionMode === null || functionMode === 'naming')
|
||||
) {
|
||||
item.enable = true;
|
||||
}
|
||||
if (
|
||||
item.serviceName === 'clusterManagementVirtual' &&
|
||||
(functionMode === null || functionMode === 'cluster')
|
||||
) {
|
||||
item.enable = true;
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
},
|
||||
() => this.setState({ navRow: this.nacosGetNav(navList) }, () => this.renderNav())
|
||||
);
|
||||
if (functionMode === 'config' && location.pathname === serviceUrl) {
|
||||
history.push(configUrl);
|
||||
}
|
||||
if (functionMode === 'naming' && location.pathname === configUrl) {
|
||||
history.push(serviceUrl);
|
||||
}
|
||||
if (functionMode === 'cluster' && location.pathname === clusterUrl) {
|
||||
history.push(clusterUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
setTimeout(() => this.refreshNav());
|
||||
return !urls.includes(this.props.location.pathname);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {}, version } = this.props;
|
||||
const { nacosName, doesNotExist } = locale;
|
||||
const { showLink, navRow, leftBarClose, noChild } = this.state;
|
||||
const { locale = {}, version, functionMode } = this.props;
|
||||
const MenuData = getMenuData(functionMode);
|
||||
return (
|
||||
<div className="viewFramework-product" style={{ top: 66 }}>
|
||||
<>
|
||||
<Header />
|
||||
<div
|
||||
className="viewFramework-product-navbar"
|
||||
style={{ width: 180, marginLeft: 0 }}
|
||||
id="viewFramework-product-navbar"
|
||||
data-spm="acm_nav"
|
||||
>
|
||||
<div className="viewFramework-product-navbar-removed">
|
||||
<div>
|
||||
<div className="product-nav-scene product-nav-main-scene">
|
||||
{showLink ? (
|
||||
<div className="product-nav-icon env" style={{ height: 80, paddingTop: 25 }}>
|
||||
{showLink}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{ textIndent: 0, display: !version ? 'none' : 'block' }}
|
||||
className="product-nav-title"
|
||||
title={nacosName}
|
||||
>
|
||||
<span>{nacosName}</span>
|
||||
<span style={{ marginLeft: 5 }}>{version}</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="product-nav-list"
|
||||
style={{ position: 'relative', top: 0, height: '100%' }}
|
||||
>
|
||||
{navRow}
|
||||
</div>
|
||||
<div className="main-container">
|
||||
<div className="left-panel">
|
||||
{this.isShowGoBack() ? (
|
||||
<div className="go-back" onClick={() => this.goBack()}>
|
||||
<Icon type="arrow-left" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="viewFramework-product-navbar-collapse"
|
||||
id="viewFramework-product-navbar-collapse"
|
||||
onClick={this.nacosToggleLeftBar.bind(this)}
|
||||
>
|
||||
<div className="product-navbar-collapse-inner">
|
||||
<div className="product-navbar-collapse-bg" />
|
||||
<div className="product-navbar-collapse">
|
||||
{leftBarClose ? (
|
||||
<span className="icon-collapse-right" style={{ display: 'block' }} />
|
||||
) : (
|
||||
<span className="icon-collapse-left" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="viewFramework-product-body"
|
||||
style={{ marginLeft: 180 }}
|
||||
id="viewFramework-product-body"
|
||||
>
|
||||
<div>
|
||||
{!noChild ? (
|
||||
<div>{this.props.children}</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: 300,
|
||||
lineHeight: 300,
|
||||
textAlign: 'center',
|
||||
fontSize: 18,
|
||||
}}
|
||||
>
|
||||
{doesNotExist}
|
||||
</div>
|
||||
<>
|
||||
<h1 className="nav-title">
|
||||
{locale.nacosName}
|
||||
<span>{version}</span>
|
||||
</h1>
|
||||
<Menu
|
||||
defaultOpenKeys={this.defaultOpenKeys()}
|
||||
className="nav-menu"
|
||||
openMode="single"
|
||||
>
|
||||
{MenuData.map((subMenu, idx) => {
|
||||
if (subMenu.children) {
|
||||
return (
|
||||
<SubMenu key={String(idx)} label={locale[subMenu.key]}>
|
||||
{subMenu.children.map((item, i) => (
|
||||
<Item
|
||||
key={[idx, i].join('-')}
|
||||
onClick={() => this.navTo(item.url)}
|
||||
className={this.isCurrentPath(item.url)}
|
||||
>
|
||||
{locale[item.key]}
|
||||
</Item>
|
||||
))}
|
||||
</SubMenu>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Item
|
||||
key={idx}
|
||||
className={['first-menu', this.isCurrentPath(subMenu.url)]
|
||||
.filter(c => c)
|
||||
.join(' ')}
|
||||
onClick={() => this.navTo(subMenu.url)}
|
||||
>
|
||||
{locale[subMenu.key]}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="right-panel">{this.props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.header-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
background-color: #fff;
|
||||
}
|
||||
.header-container-primary {
|
||||
background: #252a2f;
|
||||
}
|
||||
@ -1396,5 +1388,63 @@ h6 {
|
||||
}
|
||||
|
||||
.product-nav-list li.selected a {
|
||||
background-color: #F4F6F8;
|
||||
background-color: #f4f6f8;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
height: calc(100vh - 66px);
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
.left-panel {
|
||||
width: 180px;
|
||||
background-color: #eaedf1;
|
||||
}
|
||||
.right-panel {
|
||||
width: calc(100% - 180px);
|
||||
padding: 10px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.nav-title {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 70px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
background-color: #d9dee4;
|
||||
span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.nav-menu {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
line-height: 40px;
|
||||
div.next-menu-item,
|
||||
.first-menu > .next-menu-item-inner {
|
||||
color: #333;
|
||||
}
|
||||
.next-menu-item-inner {
|
||||
height: 40px;
|
||||
color: #666;
|
||||
}
|
||||
.current-path {
|
||||
background-color: #f2f3f7;
|
||||
}
|
||||
}
|
||||
.go-back {
|
||||
text-align: center;
|
||||
color: rgb(84, 100, 120);
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
padding: 10px 0;
|
||||
margin-top: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
import { isJsonString } from '../utils/nacosutil';
|
||||
|
||||
const configurationMenu = {
|
||||
key: 'configurationManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
key: 'configurationManagement',
|
||||
url: '/configurationManagement',
|
||||
},
|
||||
{
|
||||
key: 'historyRollback',
|
||||
url: '/historyRollback',
|
||||
},
|
||||
{
|
||||
key: 'listeningToQuery',
|
||||
url: '/listeningToQuery',
|
||||
},
|
||||
],
|
||||
};
|
||||
/**
|
||||
* 权限控制相关
|
||||
*/
|
||||
const authorityControlMenu = {
|
||||
key: 'authorityControl',
|
||||
children: [
|
||||
{
|
||||
key: 'userList',
|
||||
url: '/userManagement',
|
||||
},
|
||||
{
|
||||
key: 'roleManagement',
|
||||
url: '/rolesManagement',
|
||||
},
|
||||
{
|
||||
key: 'privilegeManagement',
|
||||
url: '/permissionsManagement',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function(model) {
|
||||
const { token = '{}' } = localStorage;
|
||||
const { globalAdmin } = isJsonString(token) ? JSON.parse(token) || {} : {};
|
||||
|
||||
return [
|
||||
model === 'naming' ? undefined : configurationMenu,
|
||||
{
|
||||
key: 'serviceManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
key: 'serviceManagement',
|
||||
url: '/serviceManagement',
|
||||
},
|
||||
{
|
||||
key: 'subscriberList',
|
||||
url: '/subscriberList',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'clusterManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
key: 'clusterManagement',
|
||||
url: '/clusterManagement',
|
||||
},
|
||||
],
|
||||
},
|
||||
globalAdmin ? authorityControlMenu : undefined,
|
||||
{
|
||||
key: 'namespace',
|
||||
url: '/namespace',
|
||||
},
|
||||
].filter(item => item);
|
||||
}
|
@ -50,6 +50,10 @@ const I18N_CONF = {
|
||||
namespace: 'Namespace',
|
||||
clusterManagementVirtual: 'ClusterManagement',
|
||||
clusterManagement: 'Cluster Node List',
|
||||
authorityControl: 'Authority Control',
|
||||
userList: 'User List',
|
||||
roleManagement: 'Role Management',
|
||||
privilegeManagement: 'Privilege Management',
|
||||
},
|
||||
Password: {
|
||||
passwordNotConsistent: 'The passwords are not consistent',
|
||||
@ -505,6 +509,86 @@ const I18N_CONF = {
|
||||
update: 'Update',
|
||||
insert: 'Insert',
|
||||
},
|
||||
UserManagement: {
|
||||
userManagement: 'User Management',
|
||||
createUser: 'Create user',
|
||||
resetPassword: 'Edit',
|
||||
deleteUser: 'Delete',
|
||||
deleteUserTip: 'Do you want to delete this user?',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
operation: 'Operation',
|
||||
},
|
||||
NewUser: {
|
||||
createUser: 'Create user',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
rePassword: 'Repeat',
|
||||
usernamePlaceholder: 'Please Enter Username',
|
||||
passwordPlaceholder: 'Please Enter Password',
|
||||
rePasswordPlaceholder: 'Please Enter Repeat Password',
|
||||
usernameError: 'User name cannot be empty!',
|
||||
passwordError: 'Password cannot be empty!',
|
||||
rePasswordError: 'Repeat Password cannot be empty!',
|
||||
rePasswordError2: 'Passwords are inconsistent!',
|
||||
},
|
||||
PasswordReset: {
|
||||
resetPassword: 'Password Reset',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
rePassword: 'Repeat',
|
||||
passwordPlaceholder: 'Please Enter Password',
|
||||
rePasswordPlaceholder: 'Please Enter Repeat Password',
|
||||
passwordError: 'Password cannot be empty!',
|
||||
rePasswordError: 'Repeat Password cannot be empty!',
|
||||
rePasswordError2: 'Passwords are inconsistent!',
|
||||
},
|
||||
RolesManagement: {
|
||||
roleManagement: 'Role management',
|
||||
bindingRoles: 'Binding roles',
|
||||
role: 'Role',
|
||||
username: 'Username',
|
||||
operation: 'Operation',
|
||||
deleteRole: 'Delete',
|
||||
deleteRoleTip: 'Do you want to delete this role?',
|
||||
},
|
||||
NewRole: {
|
||||
bindingRoles: 'Binding roles',
|
||||
username: 'Username',
|
||||
role: 'Role',
|
||||
usernamePlaceholder: 'Please Enter Username',
|
||||
rolePlaceholder: 'Please Enter Role',
|
||||
usernameError: 'User name cannot be empty!',
|
||||
roleError: 'Role cannot be empty!',
|
||||
},
|
||||
PermissionsManagement: {
|
||||
privilegeManagement: 'Permissions Management',
|
||||
addPermission: 'Add Permission',
|
||||
role: 'Role',
|
||||
resource: 'Resource',
|
||||
action: 'Action',
|
||||
operation: 'Operation',
|
||||
deletePermission: 'Delete',
|
||||
deletePermissionTip: 'Do you want to delete this permission?',
|
||||
readOnly: 'read only',
|
||||
writeOnly: 'write only',
|
||||
readWrite: 'Read and write',
|
||||
},
|
||||
NewPermissions: {
|
||||
addPermission: 'Add Permission',
|
||||
role: 'Role',
|
||||
resource: 'Resource',
|
||||
action: 'Action',
|
||||
resourcePlaceholder: 'Please select resources',
|
||||
rolePlaceholder: 'Please enter Role',
|
||||
actionPlaceholder: 'Please select Action',
|
||||
resourceError: 'Resource cannot be empty!',
|
||||
roleError: 'Role cannot be empty!',
|
||||
actionError: 'Action cannot be empty!',
|
||||
readOnly: 'read only',
|
||||
writeOnly: 'write only',
|
||||
readWrite: 'Read and write',
|
||||
},
|
||||
};
|
||||
|
||||
export default I18N_CONF;
|
||||
|
@ -50,6 +50,10 @@ const I18N_CONF = {
|
||||
namespace: '命名空间',
|
||||
clusterManagementVirtual: '集群管理',
|
||||
clusterManagement: '节点列表',
|
||||
authorityControl: '权限控制',
|
||||
userList: '用户列表',
|
||||
roleManagement: '角色管理',
|
||||
privilegeManagement: '权限管理',
|
||||
},
|
||||
Password: {
|
||||
passwordNotConsistent: '两次输入密码不一致',
|
||||
@ -502,6 +506,86 @@ const I18N_CONF = {
|
||||
update: '更新',
|
||||
insert: '插入',
|
||||
},
|
||||
UserManagement: {
|
||||
userManagement: '用户管理',
|
||||
createUser: '创建用户',
|
||||
resetPassword: '修改',
|
||||
deleteUser: '删除',
|
||||
deleteUserTip: '是否要删除该用户?',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
operation: '操作',
|
||||
},
|
||||
NewUser: {
|
||||
createUser: '创建用户',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
rePassword: '确认密码',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
rePasswordPlaceholder: '请输入确认密码',
|
||||
usernameError: '用户名不能为空!',
|
||||
passwordError: '密码不能为空!',
|
||||
rePasswordError: '确认密码不能为空!',
|
||||
rePasswordError2: '两次输入密码不一致!',
|
||||
},
|
||||
PasswordReset: {
|
||||
resetPassword: '密码重置',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
rePassword: '确认密码',
|
||||
passwordError: '密码不能为空!',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
rePasswordPlaceholder: '请输入确认密码',
|
||||
rePasswordError: '确认密码不能为空!',
|
||||
rePasswordError2: '两次输入密码不一致!',
|
||||
},
|
||||
RolesManagement: {
|
||||
roleManagement: '角色管理',
|
||||
bindingRoles: '绑定角色',
|
||||
role: '角色名',
|
||||
username: '用户名',
|
||||
operation: '操作',
|
||||
deleteRole: '删除',
|
||||
deleteRoleTip: '是否要删除该角色?',
|
||||
},
|
||||
NewRole: {
|
||||
bindingRoles: '绑定角色',
|
||||
username: '用户名',
|
||||
role: '角色名',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
rolePlaceholder: '请输入角色名',
|
||||
usernameError: '用户名不能为空!',
|
||||
roleError: '角色名不能为空!',
|
||||
},
|
||||
PermissionsManagement: {
|
||||
privilegeManagement: '权限管理',
|
||||
addPermission: '添加权限',
|
||||
role: '角色名',
|
||||
resource: '资源',
|
||||
action: '动作',
|
||||
operation: '操作',
|
||||
deletePermission: '删除',
|
||||
deletePermissionTip: '是否要删除该权限?',
|
||||
readOnly: '只读',
|
||||
writeOnly: '只写',
|
||||
readWrite: '读写',
|
||||
},
|
||||
NewPermissions: {
|
||||
addPermission: '添加权限',
|
||||
role: '角色名',
|
||||
resource: '资源',
|
||||
action: '动作',
|
||||
resourcePlaceholder: '请选择资源',
|
||||
rolePlaceholder: '请输入角色名',
|
||||
actionPlaceholder: '请选择动作',
|
||||
resourceError: '资源不能为空!',
|
||||
roleError: '角色名不能为空!',
|
||||
actionError: '动作不能为空!',
|
||||
readOnly: '只读',
|
||||
writeOnly: '只写',
|
||||
readWrite: '读写',
|
||||
},
|
||||
};
|
||||
|
||||
export default I18N_CONF;
|
||||
|
@ -1,295 +0,0 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
data: [
|
||||
{
|
||||
enable: false,
|
||||
isExtend: true,
|
||||
name: '配置管理',
|
||||
title: '配置管理',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configurationManagementVirtual',
|
||||
link: 'configurationManagementVirtual',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.configurationManagementVirtual',
|
||||
useRouter: false,
|
||||
id: 'com.alibaba.nacos.page.configurationManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置列表',
|
||||
title: '配置列表',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configurationManagement',
|
||||
link: 'configurationManagement',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.configurationManagement',
|
||||
useRouter: false,
|
||||
id: 'configurationManagement',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置详情',
|
||||
title: '配置详情',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configdetail',
|
||||
link: 'Configdetail',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.configdetail',
|
||||
useRouter: false,
|
||||
id: 'configdetail',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '同步配置',
|
||||
title: '同步配置',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configsync',
|
||||
link: 'configsync',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: true,
|
||||
registerName: 'com.alibaba.nacos.page.configsync',
|
||||
useRouter: false,
|
||||
id: 'configsync',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置编辑',
|
||||
title: '配置编辑',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configeditor',
|
||||
link: 'configeditor',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.configeditor',
|
||||
useRouter: false,
|
||||
id: 'configeditor',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '新建配置',
|
||||
title: '新建配置',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'newconfig',
|
||||
link: 'newconfig',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.newconfig',
|
||||
useRouter: false,
|
||||
id: 'newconfig',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '历史版本',
|
||||
title: '历史版本',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置回滚',
|
||||
title: '配置回滚',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configRollback',
|
||||
link: 'configRollback',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.configRollback',
|
||||
useRouter: false,
|
||||
id: 'configRollback',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '历史详情',
|
||||
title: '历史详情',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'historyDetail',
|
||||
link: 'historyDetail',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.historyDetail',
|
||||
useRouter: false,
|
||||
id: 'historyDetail',
|
||||
},
|
||||
],
|
||||
serviceName: 'historyRollback',
|
||||
link: 'historyRollback',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.historyRollback',
|
||||
useRouter: false,
|
||||
id: 'historyRollback',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '监听查询',
|
||||
title: '监听查询',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'listeningToQuery',
|
||||
link: 'listeningToQuery',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.listeningToQuery',
|
||||
useRouter: false,
|
||||
id: 'listeningToQuery',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enable: false,
|
||||
isExtend: true,
|
||||
name: '服务管理',
|
||||
title: '服务管理',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'serviceManagementVirtual',
|
||||
link: 'serviceManagementVirtual',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.serviceManagementVirtual',
|
||||
useRouter: false,
|
||||
id: 'com.alibaba.nacos.page.serviceManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '服务列表',
|
||||
title: '服务列表',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'serviceManagement',
|
||||
link: 'serviceManagement',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.serviceManagement',
|
||||
useRouter: false,
|
||||
id: 'serviceManagement',
|
||||
children: [
|
||||
{
|
||||
isExtend: true,
|
||||
name: '服务详情',
|
||||
title: '服务详情',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'serviceDetail',
|
||||
link: 'serviceDetail',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.ServiceDetail',
|
||||
useRouter: false,
|
||||
id: 'serviceDetail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '订阅者列表',
|
||||
title: '订阅者列表',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'subscriberList',
|
||||
link: 'subscriberList',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.subscriberList',
|
||||
useRouter: false,
|
||||
id: 'subscriberList',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enable: true,
|
||||
isExtend: false,
|
||||
name: '命名空间',
|
||||
title: '命名空间',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'namespace',
|
||||
link: 'namespace',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.namespace',
|
||||
useRouter: false,
|
||||
id: 'namespace',
|
||||
},
|
||||
{
|
||||
enable: true,
|
||||
isExtend: false,
|
||||
name: '修改密码',
|
||||
title: '修改密码',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'password',
|
||||
link: 'password',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: true,
|
||||
registerName: 'com.alibaba.nacos.page.password',
|
||||
useRouter: false,
|
||||
id: 'password',
|
||||
},
|
||||
{
|
||||
enable: false,
|
||||
isExtend: true,
|
||||
name: '集群管理',
|
||||
title: '集群管理',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'clusterManagementVirtual',
|
||||
link: 'clusterManagementVirtual',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.clusterManagementVirtual',
|
||||
useRouter: false,
|
||||
id: 'com.alibaba.nacos.page.clusterManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '节点状态',
|
||||
title: '节点状态',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'clusterManagement',
|
||||
link: 'clusterManagement',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.clusterManagement',
|
||||
useRouter: false,
|
||||
id: 'clusterManagement',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
defaultKey: 'configurationManagement',
|
||||
projectName: 'nacos',
|
||||
};
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Select, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getNamespaces } from '../../../reducers/namespace';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@connect(state => ({ namespaces: state.namespace.namespaces }), { getNamespaces })
|
||||
@ConfigProvider.config
|
||||
class NewPermissions extends React.Component {
|
||||
static displayName = 'NewPermissions';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
getNamespaces: PropTypes.func,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
namespaces: PropTypes.array,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getNamespaces();
|
||||
}
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
role: locale.roleError,
|
||||
resource: locale.resourceError,
|
||||
action: locale.actionError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length === 3) {
|
||||
return vals;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { getError } = this.field;
|
||||
const { visible, onOk, onCancel, locale, namespaces } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.addPermission}
|
||||
visible={visible}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.role} required help={getError('role')}>
|
||||
<Input name="role" trim placeholder={locale.rolePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.resource} required help={getError('resource')}>
|
||||
<Select
|
||||
name="resource"
|
||||
placeholder={locale.resourcePlaceholder}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{namespaces.map(({ namespace, namespaceShowName }) => (
|
||||
<Option value={`${namespace}:*:*`}>
|
||||
{namespaceShowName} {namespace ? `(${namespace})` : ''}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label={locale.action} required help={getError('action')}>
|
||||
<Select
|
||||
name="action"
|
||||
placeholder={locale.actionPlaceholder}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Option value="r">{locale.readOnly}(r)</Option>
|
||||
<Option value="w">{locale.writeOnly}(w)</Option>
|
||||
<Option value="rw">{locale.readWrite}(rw)</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewPermissions;
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getPermissions, createPermission, deletePermission } from '../../../reducers/authority';
|
||||
import { getNamespaces } from '../../../reducers/namespace';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import NewPermissions from './NewPermissions';
|
||||
|
||||
import './PermissionsManagement.scss';
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
permissions: state.authority.permissions,
|
||||
namespaces: state.namespace.namespaces,
|
||||
}),
|
||||
{ getPermissions, getNamespaces }
|
||||
)
|
||||
@ConfigProvider.config
|
||||
class PermissionsManagement extends React.Component {
|
||||
static displayName = 'PermissionsManagement';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
namespaces: PropTypes.object,
|
||||
getPermissions: PropTypes.func,
|
||||
getNamespaces: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
pageNo: 1,
|
||||
pageSize: 9,
|
||||
createPermission: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getPermissions();
|
||||
this.props.getNamespaces();
|
||||
}
|
||||
|
||||
getPermissions() {
|
||||
const { pageNo, pageSize } = this.state;
|
||||
this.props
|
||||
.getPermissions({ pageNo, pageSize })
|
||||
.then(() => {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
colseCreatePermission() {
|
||||
this.setState({ createPermissionVisible: false });
|
||||
}
|
||||
|
||||
getActionText(action) {
|
||||
const { locale } = this.props;
|
||||
return {
|
||||
r: `${locale.readOnly} (r)`,
|
||||
w: `${locale.writeOnly} (w)`,
|
||||
rw: `${locale.readWrite} (rw)`,
|
||||
}[action];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { permissions, namespaces = [], locale } = this.props;
|
||||
const { loading, pageSize, pageNo, createPermissionVisible } = this.state;
|
||||
return (
|
||||
<>
|
||||
<RegionGroup left={locale.privilegeManagement} />
|
||||
<div className="filter-panel">
|
||||
<Button type="primary" onClick={() => this.setState({ createPermissionVisible: true })}>
|
||||
{locale.addPermission}
|
||||
</Button>
|
||||
</div>
|
||||
<Table dataSource={permissions.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
|
||||
<Table.Column title={locale.role} dataIndex="role" />
|
||||
<Table.Column
|
||||
title={locale.resource}
|
||||
dataIndex="resource"
|
||||
cell={value => {
|
||||
const [item = {}] = namespaces.filter(({ namespace }) => {
|
||||
const [itemNamespace] = value.split(':');
|
||||
return itemNamespace === namespace;
|
||||
});
|
||||
const { namespaceShowName = '', namespace = '' } = item;
|
||||
return namespaceShowName + (namespace ? ` (${namespace})` : '');
|
||||
}}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.action}
|
||||
dataIndex="action"
|
||||
cell={action => this.getActionText(action)}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
warning
|
||||
onClick={() =>
|
||||
Dialog.confirm({
|
||||
title: locale.deletePermission,
|
||||
content: locale.deletePermissionTip,
|
||||
onOk: () =>
|
||||
deletePermission(record).then(() => {
|
||||
this.setState({ pageNo: 1 }, () => this.getPermissions());
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{locale.deletePermission}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
{permissions.totalCount > pageSize && (
|
||||
<Pagination
|
||||
className="users-pagination"
|
||||
current={pageNo}
|
||||
total={permissions.totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={pageNo => this.setState({ pageNo }, () => this.getPermissions())}
|
||||
/>
|
||||
)}
|
||||
<NewPermissions
|
||||
visible={createPermissionVisible}
|
||||
onOk={permission =>
|
||||
createPermission(permission).then(res => {
|
||||
this.setState({ pageNo: 1 }, () => this.getPermissions());
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.colseCreatePermission()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PermissionsManagement;
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
@ -11,14 +11,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
set(key, value) {
|
||||
window.localStorage.setItem(key, value);
|
||||
},
|
||||
get(key) {
|
||||
return window.localStorage.getItem(key);
|
||||
},
|
||||
remove(key) {
|
||||
window.localStorage.removeItem(key);
|
||||
},
|
||||
};
|
||||
import PermissionsManagement from './PermissionsManagement';
|
||||
|
||||
export default PermissionsManagement;
|
@ -0,0 +1,7 @@
|
||||
# 权限控制
|
||||
|
||||
> AuthorityControl
|
||||
|
||||
1. UserManagement => 用户管理
|
||||
2. RolesManagement => 角色管理
|
||||
3. PermissionsManagement => 权限管理
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@ConfigProvider.config
|
||||
class NewRole extends React.Component {
|
||||
static displayName = 'NewRole';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
};
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
role: locale.roleError,
|
||||
username: locale.usernameError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length === 2) {
|
||||
return vals;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
const { getError } = this.field;
|
||||
const { visible, onOk, onCancel } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.bindingRoles}
|
||||
visible={visible}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.role} required help={getError('role')}>
|
||||
<Input name="role" trim placeholder={locale.rolePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.username} required help={getError('username')}>
|
||||
<Input name="username" placeholder={locale.usernamePlaceholder} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewRole;
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getRoles, createRole, deleteRole } from '../../../reducers/authority';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import NewRole from './NewRole';
|
||||
|
||||
import './RolesManagement.scss';
|
||||
|
||||
@connect(state => ({ roles: state.authority.roles }), { getRoles })
|
||||
@ConfigProvider.config
|
||||
class RolesManagement extends React.Component {
|
||||
static displayName = 'RolesManagement';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
roles: PropTypes.object,
|
||||
getRoles: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
pageNo: 1,
|
||||
pageSize: 9,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getRoles();
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
const { pageNo, pageSize } = this.state;
|
||||
this.props
|
||||
.getRoles({ pageNo, pageSize })
|
||||
.then(() => {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
colseCreateRole() {
|
||||
this.setState({ createRoleVisible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { roles, locale } = this.props;
|
||||
const { loading, pageSize, pageNo, createRoleVisible, passwordResetUser } = this.state;
|
||||
return (
|
||||
<>
|
||||
<RegionGroup left={locale.roleManagement} />
|
||||
<div className="filter-panel">
|
||||
<Button type="primary" onClick={() => this.setState({ createRoleVisible: true })}>
|
||||
{locale.bindingRoles}
|
||||
</Button>
|
||||
</div>
|
||||
<Table dataSource={roles.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
|
||||
<Table.Column title={locale.role} dataIndex="role" />
|
||||
<Table.Column title={locale.username} dataIndex="username" />
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
dataIndex="role"
|
||||
cell={(value, index, record) => {
|
||||
if (value === 'ROLE_ADMIN') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
warning
|
||||
onClick={() =>
|
||||
Dialog.confirm({
|
||||
title: locale.deleteRole,
|
||||
content: locale.deleteRoleTip,
|
||||
onOk: () =>
|
||||
deleteRole(record).then(() => {
|
||||
this.setState({ pageNo: 1 }, () => this.getRoles());
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{locale.deleteRole}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
{roles.totalCount > pageSize && (
|
||||
<Pagination
|
||||
className="users-pagination"
|
||||
current={pageNo}
|
||||
total={roles.totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={pageNo => this.setState({ pageNo }, () => this.getRoles())}
|
||||
/>
|
||||
)}
|
||||
<NewRole
|
||||
visible={createRoleVisible}
|
||||
onOk={role =>
|
||||
createRole(role).then(res => {
|
||||
this.getRoles();
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.colseCreateRole()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RolesManagement;
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import RolesManagement from './RolesManagement';
|
||||
|
||||
export default RolesManagement;
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import './UserManagement.scss';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@ConfigProvider.config
|
||||
class NewUser extends React.Component {
|
||||
static displayName = 'NewUser';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
};
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
username: locale.usernameError,
|
||||
password: locale.passwordError,
|
||||
rePassword: locale.rePasswordError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length !== 3) {
|
||||
return null;
|
||||
}
|
||||
const [password, rePassword] = ['password', 'rePassword'].map(k => this.field.getValue(k));
|
||||
if (password !== rePassword) {
|
||||
this.field.setError('rePassword', locale.rePasswordError2);
|
||||
return null;
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
const { getError } = this.field;
|
||||
const { visible, onOk, onCancel } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.createUser}
|
||||
visible={visible}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.username} required help={getError('username')}>
|
||||
<Input name="username" trim placeholder={locale.usernamePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.password} required help={getError('password')}>
|
||||
<Input name="password" htmlType="password" placeholder={locale.passwordPlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.rePassword} required help={getError('rePassword')}>
|
||||
<Input
|
||||
name="rePassword"
|
||||
htmlType="password"
|
||||
placeholder={locale.rePasswordPlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewUser;
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import './UserManagement.scss';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@ConfigProvider.config
|
||||
class PasswordReset extends React.Component {
|
||||
static displayName = 'PasswordReset';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
onCancel: PropTypes.func,
|
||||
onOk: PropTypes.func,
|
||||
};
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
password: locale.passwordError,
|
||||
rePassword: locale.rePasswordError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length !== 2) {
|
||||
return null;
|
||||
}
|
||||
const [password, rePassword] = ['password', 'rePassword'].map(k => this.field.getValue(k));
|
||||
if (password !== rePassword) {
|
||||
this.field.setError('rePassword', locale.rePasswordError2);
|
||||
return null;
|
||||
}
|
||||
return [this.props.username, ...vals];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
const { getError } = this.field;
|
||||
const { username, onOk, onCancel } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.resetPassword}
|
||||
visible={username}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.username} required>
|
||||
<p>{username}</p>
|
||||
</FormItem>
|
||||
<FormItem label={locale.password} required help={getError('password')}>
|
||||
<Input name="password" htmlType="password" placeholder={locale.passwordPlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.rePassword} required help={getError('rePassword')}>
|
||||
<Input
|
||||
name="rePassword"
|
||||
htmlType="password"
|
||||
placeholder={locale.rePasswordPlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PasswordReset;
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Field, Form, Input, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getUsers, createUser, deleteUser, passwordReset } from '../../../reducers/authority';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import NewUser from './NewUser';
|
||||
import PasswordReset from './PasswordReset';
|
||||
|
||||
import './UserManagement.scss';
|
||||
|
||||
@connect(state => ({ users: state.authority.users }), { getUsers })
|
||||
@ConfigProvider.config
|
||||
class UserManagement extends React.Component {
|
||||
static displayName = 'UserManagement';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
users: PropTypes.object,
|
||||
getUsers: PropTypes.func,
|
||||
createUser: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
pageNo: 1,
|
||||
pageSize: 9,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
getUsers() {
|
||||
const { pageNo, pageSize } = this.state;
|
||||
this.props
|
||||
.getUsers({ pageNo, pageSize })
|
||||
.then(() => {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
colseCreateUser() {
|
||||
this.setState({ createUserVisible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { users, locale } = this.props;
|
||||
const { loading, pageSize, pageNo, createUserVisible, passwordResetUser } = this.state;
|
||||
return (
|
||||
<>
|
||||
<RegionGroup left={locale.userManagement} />
|
||||
<div className="filter-panel">
|
||||
<Button type="primary" onClick={() => this.setState({ createUserVisible: true })}>
|
||||
{locale.createUser}
|
||||
</Button>
|
||||
</div>
|
||||
<Table dataSource={users.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
|
||||
<Table.Column title={locale.username} dataIndex="username" />
|
||||
<Table.Column
|
||||
title={locale.password}
|
||||
dataIndex="password"
|
||||
cell={value => value.replace(/\S/g, '*')}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
dataIndex="username"
|
||||
cell={username => (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => this.setState({ passwordResetUser: username })}
|
||||
>
|
||||
{locale.resetPassword}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
warning
|
||||
onClick={() =>
|
||||
Dialog.confirm({
|
||||
title: locale.deleteUser,
|
||||
content: locale.deleteUserTip,
|
||||
onOk: () =>
|
||||
deleteUser(username).then(() => {
|
||||
this.setState({ pageNo: 1 }, () => this.getUsers());
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{locale.deleteUser}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
{users.totalCount > pageSize && (
|
||||
<Pagination
|
||||
className="users-pagination"
|
||||
current={pageNo}
|
||||
total={users.totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={pageNo => this.setState({ pageNo }, () => this.getUsers())}
|
||||
/>
|
||||
)}
|
||||
<NewUser
|
||||
visible={createUserVisible}
|
||||
onOk={user =>
|
||||
createUser(user).then(res => {
|
||||
this.setState({ pageNo: 1 }, () => this.getUsers());
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.colseCreateUser()}
|
||||
/>
|
||||
<PasswordReset
|
||||
username={passwordResetUser}
|
||||
onOk={user =>
|
||||
passwordReset(user).then(res => {
|
||||
this.getUsers();
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.setState({ passwordResetUser: undefined })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserManagement;
|
@ -10,15 +10,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@import '../authority.scss';
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
export default function ajaxrequest(options) {
|
||||
const promise = $.ajax({
|
||||
url: options.url,
|
||||
timeout: options.timeout, // 超时时间设置,单位毫秒设置为1小时
|
||||
dataType: options.dataType, // 返回的数据格式
|
||||
type: options.type,
|
||||
});
|
||||
return promise.done(data => ({ data }));
|
||||
.users-pagination {
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import UserManagement from './UserManagement';
|
||||
|
||||
export default UserManagement;
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
.filter-panel {
|
||||
text-align: right;
|
||||
padding: 10px 0;
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
import React from 'react';
|
||||
import { Button, ConfigProvider, Dialog, Field, Form, Input, Loading, Tab } from '@alifd/next';
|
||||
import { getParams, request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
|
||||
import './index.scss';
|
||||
import PropTypes from 'prop-types';
|
||||
@ -140,9 +141,12 @@ class ConfigDetail extends React.Component {
|
||||
|
||||
goList() {
|
||||
this.props.history.push(
|
||||
`/configurationManagement?serverId=${this.serverId}&group=${this.searchGroup}&dataId=${
|
||||
this.searchDataId
|
||||
}&namespace=${this.tenant}`
|
||||
generateUrl('/configurationManagement', {
|
||||
serverId: this.serverId,
|
||||
group: this.searchGroup,
|
||||
dataId: this.searchDataId,
|
||||
namespace: this.tenant,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getParams } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import request from '../../../utils/request';
|
||||
import validateContent from 'utils/validateContent';
|
||||
import SuccessDialog from '../../../components/SuccessDialog';
|
||||
@ -96,7 +97,7 @@ class ConfigEditor extends React.Component {
|
||||
dataId: getParams('dataId').trim(),
|
||||
group,
|
||||
},
|
||||
() =>
|
||||
() => {
|
||||
this.getConfig(true).then(res => {
|
||||
if (!res) {
|
||||
this.getConfig();
|
||||
@ -107,7 +108,8 @@ class ConfigEditor extends React.Component {
|
||||
tabActiveKey: 'beta',
|
||||
betaPublishSuccess: true,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (group) {
|
||||
@ -173,7 +175,6 @@ class ConfigEditor extends React.Component {
|
||||
}
|
||||
|
||||
clickTab(tabActiveKey) {
|
||||
console.log('tabActiveKey', tabActiveKey, tabActiveKey === 'beta');
|
||||
this.setState({ tabActiveKey }, () => this.getConfig(tabActiveKey === 'beta'));
|
||||
}
|
||||
|
||||
@ -215,26 +216,20 @@ class ConfigEditor extends React.Component {
|
||||
}
|
||||
|
||||
_publishConfig(beta = false) {
|
||||
const { locale } = this.props;
|
||||
const { betaIps, isNewConfig } = this.state;
|
||||
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
if (beta) {
|
||||
headers.betaIps = betaIps;
|
||||
}
|
||||
const data = { ...this.state.form, content: this.getCodeVal() };
|
||||
const form = { ...this.state.form, content: this.getCodeVal() };
|
||||
const data = new FormData();
|
||||
Object.keys(form).forEach(key => {
|
||||
data.append(key, form[key]);
|
||||
});
|
||||
return request({
|
||||
url: 'v1/cs/configs',
|
||||
method: 'post',
|
||||
data,
|
||||
transformRequest: [
|
||||
function(data) {
|
||||
let ret = '';
|
||||
for (let it in data) {
|
||||
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&';
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
],
|
||||
headers,
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
@ -315,11 +310,11 @@ class ConfigEditor extends React.Component {
|
||||
|
||||
goBack() {
|
||||
const serverId = getParams('serverId') || '';
|
||||
const tenant = getParams('namespace');
|
||||
const searchGroup = getParams('searchGroup') || '';
|
||||
const searchDataId = getParams('searchDataId') || '';
|
||||
const namespace = getParams('namespace');
|
||||
const group = getParams('searchGroup') || '';
|
||||
const dataId = getParams('searchDataId') || '';
|
||||
this.props.history.push(
|
||||
`/configurationManagement?serverId=${serverId}&group=${searchGroup}&dataId=${searchDataId}&namespace=${tenant}`
|
||||
generateUrl('/configurationManagement', { serverId, group, dataId, namespace })
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getParams, request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import { Button, ConfigProvider, Dialog, Field, Form, Input } from '@alifd/next';
|
||||
|
||||
import './index.scss';
|
||||
@ -96,11 +97,10 @@ class ConfigRollback extends React.Component {
|
||||
}
|
||||
|
||||
goList() {
|
||||
const tenant = getParams('namespace');
|
||||
const namespace = getParams('namespace');
|
||||
const { serverId, dataId, group } = this;
|
||||
this.props.history.push(
|
||||
`/historyRollback?serverId=${this.serverId}&group=${this.group}&dataId=${
|
||||
this.dataId
|
||||
}&namespace=${tenant}`
|
||||
generateUrl('/historyRollback', { serverId, dataId, group, namespace })
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import PropTypes from 'prop-types';
|
||||
import { Button, Checkbox, ConfigProvider, Dialog, Field, Form, Input, Loading } from '@alifd/next';
|
||||
import SuccessDialog from '../../../components/SuccessDialog';
|
||||
import { getParams, request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@ -91,13 +92,9 @@ class ConfigSync extends React.Component {
|
||||
const { locale = {} } = this.props;
|
||||
this.tenant = getParams('namespace') || '';
|
||||
this.serverId = getParams('serverId') || 'center';
|
||||
let url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${
|
||||
this.dataId
|
||||
}/group/${this.group}/tenant/${this.tenant}?id=`;
|
||||
let url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${this.group}/tenant/${this.tenant}?id=`;
|
||||
if (this.tenant === 'global' || !this.tenant) {
|
||||
url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${
|
||||
this.group
|
||||
}?id=`;
|
||||
url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${this.group}?id=`;
|
||||
}
|
||||
request({
|
||||
url,
|
||||
@ -168,9 +165,7 @@ class ConfigSync extends React.Component {
|
||||
request({
|
||||
type: 'put',
|
||||
contentType: 'application/json',
|
||||
url: `/diamond-ops/configList/serverId/${this.serverId}/dataId/${payload.dataId}/group/${
|
||||
payload.group
|
||||
}?id=`,
|
||||
url: `/diamond-ops/configList/serverId/${this.serverId}/dataId/${payload.dataId}/group/${payload.group}?id=`,
|
||||
data: JSON.stringify(payload),
|
||||
success(res) {
|
||||
const _payload = {};
|
||||
@ -193,7 +188,7 @@ class ConfigSync extends React.Component {
|
||||
const dataId = this.field.getValue('dataId');
|
||||
const gruop = this.field.getValue('group');
|
||||
this.props.history.push(
|
||||
`/diamond-ops/static/pages/config-sync/index.html?serverId=center&dataId=${dataId}&group=${gruop}`
|
||||
generateUrl('/diamond-ops/static/pages/config-sync/index.html', { dataId, gruop })
|
||||
);
|
||||
}
|
||||
|
||||
@ -209,9 +204,8 @@ class ConfigSync extends React.Component {
|
||||
}
|
||||
|
||||
goResult() {
|
||||
this.props.history.push(
|
||||
`/consistencyEfficacy?serverId=${this.serverId}&dataId=${this.dataId}&group=${this.group}`
|
||||
);
|
||||
const { serverId, dataId, group } = this;
|
||||
this.props.history.push(generateUrl('/consistencyEfficacy', { serverId, dataId, group }));
|
||||
}
|
||||
|
||||
openLoading() {
|
||||
|
@ -133,7 +133,8 @@ class ConfigurationManagement extends React.Component {
|
||||
<div>
|
||||
<div style={{ fontSize: '15px', lineHeight: '22px' }}>
|
||||
{locale.ad}
|
||||
<a href={'https://survey.aliyun.com/survey/k0BjJ2ARC'} target={'_blank'}>
|
||||
{/* eslint-disable */}
|
||||
<a href="https://survey.aliyun.com/survey/k0BjJ2ARC" target="_blank">
|
||||
{locale.questionnaire2}
|
||||
</a>
|
||||
</div>
|
||||
@ -285,9 +286,6 @@ class ConfigurationManagement extends React.Component {
|
||||
}&config_tags=${this.state.config_tags || ''}&pageNo=${pageNo}&pageSize=${
|
||||
this.state.pageSize
|
||||
}`,
|
||||
beforeSend() {
|
||||
self.openLoading();
|
||||
},
|
||||
success(data) {
|
||||
if (data != null) {
|
||||
self.setState({
|
||||
@ -313,9 +311,6 @@ class ConfigurationManagement extends React.Component {
|
||||
currentPage: 0,
|
||||
});
|
||||
},
|
||||
complete() {
|
||||
self.closeLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -438,21 +433,12 @@ class ConfigurationManagement extends React.Component {
|
||||
isPageEnter: e.keyCode && e.keyCode === 13,
|
||||
currentPage: value,
|
||||
},
|
||||
() => {
|
||||
this.getData(value, false);
|
||||
}
|
||||
() => this.getData(value, false)
|
||||
);
|
||||
}
|
||||
|
||||
handlePageSizeChange(pageSize) {
|
||||
this.setState(
|
||||
{
|
||||
pageSize,
|
||||
},
|
||||
() => {
|
||||
this.changePage(1);
|
||||
}
|
||||
);
|
||||
this.setState({ pageSize }, () => this.changePage(1));
|
||||
}
|
||||
|
||||
onInputUpdate() {}
|
||||
@ -689,28 +675,51 @@ class ConfigurationManagement extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
openUri(url, params) {
|
||||
window.open(
|
||||
[
|
||||
url,
|
||||
Object.keys(params)
|
||||
.map(key => `${key}=${params[key]}`)
|
||||
.join('&'),
|
||||
].join('?')
|
||||
);
|
||||
}
|
||||
|
||||
exportData() {
|
||||
let url = `v1/cs/configs?export=true&group=${this.group}&tenant=${getParams(
|
||||
'namespace'
|
||||
)}&appName=${this.appName}&ids=&dataId=${this.dataId}`;
|
||||
window.location.href = url;
|
||||
const { group, appName, dataId, openUri } = this;
|
||||
const { accessToken = '' } = JSON.parse(localStorage.token || '{}');
|
||||
openUri('v1/cs/configs', {
|
||||
export: 'true',
|
||||
tenant: getParams('namespace'),
|
||||
group,
|
||||
appName,
|
||||
dataId,
|
||||
ids: '',
|
||||
accessToken,
|
||||
});
|
||||
}
|
||||
|
||||
exportSelectedData() {
|
||||
const ids = [];
|
||||
const { locale = {} } = this.props;
|
||||
if (configsTableSelected.size === 0) {
|
||||
const { accessToken = '' } = JSON.parse(localStorage.token || '{}');
|
||||
if (!configsTableSelected.size) {
|
||||
Dialog.alert({
|
||||
title: locale.exportSelectedAlertTitle,
|
||||
content: locale.exportSelectedAlertContent,
|
||||
});
|
||||
} else {
|
||||
let idsStr = '';
|
||||
configsTableSelected.forEach((value, key, map) => {
|
||||
idsStr = `${idsStr + key},`;
|
||||
});
|
||||
let url = `v1/cs/configs?export=true&group=&tenant=&appName=&ids=${idsStr}`;
|
||||
window.location.href = url;
|
||||
return;
|
||||
}
|
||||
configsTableSelected.forEach((value, key, map) => ids.push(key));
|
||||
this.openUri('v1/cs/configs', {
|
||||
export: 'true',
|
||||
tenant: '',
|
||||
group: '',
|
||||
appName: '',
|
||||
ids: ids.join(','),
|
||||
accessToken,
|
||||
});
|
||||
}
|
||||
|
||||
multipleSelectionDeletion() {
|
||||
@ -1160,7 +1169,7 @@ class ConfigurationManagement extends React.Component {
|
||||
render() {
|
||||
const { locale = {} } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<BatchHandle ref={ref => (this.batchHandle = ref)} />
|
||||
<Loading
|
||||
shape={'flower'}
|
||||
@ -1172,7 +1181,7 @@ class ConfigurationManagement extends React.Component {
|
||||
<div className={this.state.hasdash ? 'dash-page-container' : ''}>
|
||||
<div
|
||||
className={this.state.hasdash ? 'dash-left-container' : ''}
|
||||
style={{ position: 'relative', padding: 10 }}
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
<div style={{ display: this.inApp ? 'none' : 'block', marginTop: -15 }}>
|
||||
<RegionGroup
|
||||
@ -1424,7 +1433,7 @@ class ConfigurationManagement extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
</Loading>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,11 @@ class DashboardCard extends React.Component {
|
||||
</strong>
|
||||
<strong>
|
||||
<span>
|
||||
{/* eslint-disable */}
|
||||
<a
|
||||
style={{ marginLeft: 10, color: '#33cde5' }}
|
||||
href={item.url}
|
||||
target={'_blank'}
|
||||
target="_blank"
|
||||
>
|
||||
{locale.viewDetails1}
|
||||
</a>
|
||||
|
@ -13,7 +13,4 @@
|
||||
|
||||
.next-pagination-size-selector{
|
||||
position: static !important;
|
||||
}
|
||||
.next-overlay-inner{
|
||||
top:154px !important;
|
||||
}
|
||||
}
|
@ -81,9 +81,7 @@ class HistoryDetail extends React.Component {
|
||||
|
||||
goList() {
|
||||
this.props.history.push(
|
||||
`/historyRollback?serverId=${this.serverId}&group=${this.group}&dataId=${
|
||||
this.dataId
|
||||
}&namespace=${this.tenant}`
|
||||
`/historyRollback?serverId=${this.serverId}&group=${this.group}&dataId=${this.dataId}&namespace=${this.tenant}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -133,9 +133,7 @@ class HistoryRollback extends React.Component {
|
||||
beforeSend() {
|
||||
self.openLoading();
|
||||
},
|
||||
url: `v1/cs/history?search=accurate&dataId=${this.dataId}&group=${
|
||||
this.group
|
||||
}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`,
|
||||
url: `v1/cs/history?search=accurate&dataId=${this.dataId}&group=${this.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`,
|
||||
success(data) {
|
||||
if (data != null) {
|
||||
self.setState({
|
||||
|
@ -183,7 +183,7 @@ class ListeningToQuery extends React.Component {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div style={{ padding: 10 }}>
|
||||
<>
|
||||
<Loading
|
||||
shape="flower"
|
||||
style={{ position: 'relative' }}
|
||||
@ -320,7 +320,7 @@ class ListeningToQuery extends React.Component {
|
||||
,
|
||||
</div>
|
||||
</Loading>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SuccessDialog from '../../../components/SuccessDialog';
|
||||
import { getParams, setParams, request, aliwareIntl } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import {
|
||||
Balloon,
|
||||
Button,
|
||||
@ -192,9 +193,12 @@ class NewConfig extends React.Component {
|
||||
this.tenant = getParams('namespace') || '';
|
||||
this.serverId = getParams('serverId') || '';
|
||||
this.props.history.push(
|
||||
`/configurationManagement?serverId=${this.serverId}&group=${this.searchGroup}&dataId=${
|
||||
this.searchDataId
|
||||
}&namespace=${this.tenant}`
|
||||
generateUrl('/configurationManagement', {
|
||||
serverId: this.serverId,
|
||||
group: this.searchGroup,
|
||||
dataId: this.searchDataId,
|
||||
namespace: this.tenant,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -331,15 +335,15 @@ class NewConfig extends React.Component {
|
||||
}
|
||||
self.successDialog.current.getInstance().openDialog(_payload);
|
||||
},
|
||||
complete() {
|
||||
self.closeLoading();
|
||||
complete: () => {
|
||||
this.closeLoading();
|
||||
},
|
||||
error(res) {
|
||||
error: res => {
|
||||
this.closeLoading();
|
||||
Dialog.alert({
|
||||
language: aliwareIntl.currentLanguageCode || 'zh-cn',
|
||||
content: locale.publishFailed,
|
||||
});
|
||||
self.closeLoading();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -4,8 +4,8 @@ import { withRouter } from 'react-router-dom';
|
||||
|
||||
import './index.scss';
|
||||
import Header from '../../layouts/Header';
|
||||
import { request } from '../../globalLib';
|
||||
import PropTypes from 'prop-types';
|
||||
import { login } from '../../reducers/base';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
@ -24,35 +24,29 @@ class Login extends React.Component {
|
||||
this.field = new Field(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('token')) {
|
||||
const [baseUrl] = location.href.split('#');
|
||||
location.href = `${baseUrl}#/`;
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
const { locale = {} } = this.props;
|
||||
this.field.validate((errors, values) => {
|
||||
if (errors) {
|
||||
return;
|
||||
}
|
||||
request({
|
||||
type: 'post',
|
||||
url: 'v1/auth/login',
|
||||
data: values,
|
||||
success: ({ code, data }) => {
|
||||
if (code === 200) {
|
||||
// TODO: 封装一个方法存储、读取token
|
||||
localStorage.setItem('token', data);
|
||||
// TODO: 使用react router
|
||||
this.props.history.push('/');
|
||||
}
|
||||
if (code === 401) {
|
||||
Message.error({
|
||||
content: locale.invalidUsernameOrPassword,
|
||||
});
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
login(values)
|
||||
.then(res => {
|
||||
localStorage.setItem('token', JSON.stringify(res));
|
||||
this.props.history.push('/');
|
||||
})
|
||||
.catch(() => {
|
||||
Message.error({
|
||||
content: locale.invalidUsernameOrPassword,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -281,14 +281,6 @@ class NameSpace extends React.Component {
|
||||
return <div>{name}</div>;
|
||||
}
|
||||
|
||||
renderConfigCount(value, index, record) {
|
||||
return (
|
||||
<div>
|
||||
{value} / {record.quota}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {} } = this.props;
|
||||
const {
|
||||
@ -301,7 +293,7 @@ class NameSpace extends React.Component {
|
||||
namespaceOperation,
|
||||
} = locale;
|
||||
return (
|
||||
<div style={{ padding: 10 }} className="clearfix">
|
||||
<>
|
||||
<RegionGroup left={namespace} />
|
||||
<div className="fusion-demo">
|
||||
<Loading
|
||||
@ -329,12 +321,7 @@ class NameSpace extends React.Component {
|
||||
cell={this.renderName.bind(this)}
|
||||
/>
|
||||
<Table.Column title={namespaceNumber} dataIndex="namespace" />
|
||||
<Table.Column
|
||||
title={configuration}
|
||||
dataIndex="configCount"
|
||||
cell={this.renderConfigCount.bind(this)}
|
||||
/>
|
||||
|
||||
<Table.Column title={configuration} dataIndex="configCount" />
|
||||
<Table.Column
|
||||
title={namespaceOperation}
|
||||
dataIndex="time"
|
||||
@ -343,12 +330,11 @@ class NameSpace extends React.Component {
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NewNameSpace ref={this.newnamespace} getNameSpaces={this.getNameSpaces.bind(this)} />
|
||||
<EditorNameSpace ref={this.editgroup} getNameSpaces={this.getNameSpaces.bind(this)} />
|
||||
</Loading>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ class Password extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
Switch,
|
||||
} from '@alifd/next';
|
||||
import { request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import EditServiceDialog from '../ServiceDetail/EditServiceDialog';
|
||||
import ShowServiceCodeing from 'components/ShowCodeing/ShowServiceCodeing';
|
||||
@ -92,7 +93,6 @@ class ServiceList extends React.Component {
|
||||
];
|
||||
request({
|
||||
url: `v1/ns/catalog/services?${parameter.join('&')}`,
|
||||
beforeSend: () => this.openLoading(),
|
||||
success: ({ count = 0, serviceList = [] } = {}) => {
|
||||
this.setState({
|
||||
dataSource: serviceList,
|
||||
@ -105,7 +105,6 @@ class ServiceList extends React.Component {
|
||||
total: 0,
|
||||
currentPage: 0,
|
||||
}),
|
||||
complete: () => this.closeLoading(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -293,11 +292,12 @@ class ServiceList extends React.Component {
|
||||
*/
|
||||
<div>
|
||||
<a
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
const { name, groupName } = record;
|
||||
this.props.history.push(
|
||||
`/serviceDetail?name=${record.name}&groupName=${record.groupName}`
|
||||
)
|
||||
}
|
||||
generateUrl('/serviceDetail', { name, groupName })
|
||||
);
|
||||
}}
|
||||
style={{ marginRight: 5 }}
|
||||
>
|
||||
{detail}
|
||||
|
@ -37,10 +37,7 @@ const FormItem = Form.Item;
|
||||
const { Row, Col } = Grid;
|
||||
const { Column } = Table;
|
||||
|
||||
@connect(
|
||||
state => ({ subscriberData: state.subscribers }),
|
||||
{ getSubscribers, removeSubscribers }
|
||||
)
|
||||
@connect(state => ({ subscriberData: state.subscribers }), { getSubscribers, removeSubscribers })
|
||||
@ConfigProvider.config
|
||||
class SubscriberList extends React.Component {
|
||||
static displayName = 'SubscriberList';
|
||||
|
@ -24,15 +24,8 @@ class Welcome extends React.Component {
|
||||
|
||||
render() {
|
||||
const { functionMode } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{functionMode !== '' && (
|
||||
<Redirect
|
||||
to={`/${functionMode === 'naming' ? 'serviceManagement' : 'configurationManagement'}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const path = functionMode === 'naming' ? 'serviceManagement' : 'configurationManagement';
|
||||
return <>{functionMode !== '' && <Redirect to={`/${path}`} />}</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import { Message } from '@alifd/next';
|
||||
import request from '../utils/request';
|
||||
import { UPDATE_USER, SIGN_IN, USER_LIST, ROLE_LIST, PERMISSIONS_LIST } from '../constants';
|
||||
|
||||
const initialState = {
|
||||
users: {
|
||||
totalCount: 0,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: [],
|
||||
},
|
||||
roles: {
|
||||
totalCount: 0,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: [],
|
||||
},
|
||||
permissions: {
|
||||
totalCount: 0,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: [],
|
||||
},
|
||||
};
|
||||
|
||||
const successMsg = res => {
|
||||
if (res.code === 200) {
|
||||
Message.success(res.message);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
* @param {*} params
|
||||
*/
|
||||
const getUsers = params => dispatch =>
|
||||
request.get('v1/auth/users', { params }).then(data => dispatch({ type: USER_LIST, data }));
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
* @param {*} param0
|
||||
*/
|
||||
const createUser = ([username, password]) =>
|
||||
request.post('v1/auth/users', { username, password }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
* @param {*} username
|
||||
*/
|
||||
const deleteUser = username =>
|
||||
request.delete('v1/auth/users', { params: { username } }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
* @param {*} param0
|
||||
*/
|
||||
const passwordReset = ([username, newPassword]) =>
|
||||
request.put('v1/auth/users', { username, newPassword }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
* @param {*} params
|
||||
*/
|
||||
|
||||
const getRoles = params => dispatch =>
|
||||
request.get('v1/auth/roles', { params }).then(data => dispatch({ type: ROLE_LIST, data }));
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
* @param {*} param0
|
||||
*/
|
||||
const createRole = ([role, username]) =>
|
||||
request.post('v1/auth/roles', { role, username }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
* @param {*} param0
|
||||
*/
|
||||
const deleteRole = role =>
|
||||
request.delete('v1/auth/roles', { params: role }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
* @param {*} params
|
||||
*/
|
||||
const getPermissions = params => dispatch =>
|
||||
request
|
||||
.get('v1/auth/permissions', { params })
|
||||
.then(data => dispatch({ type: PERMISSIONS_LIST, data }));
|
||||
|
||||
/**
|
||||
* 给角色添加权限
|
||||
* @param {*} param0
|
||||
*/
|
||||
const createPermission = ([role, resource, action]) =>
|
||||
request.post('v1/auth/permissions', { role, resource, action }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
* @param {*} param0
|
||||
*/
|
||||
const deletePermission = permission =>
|
||||
request.delete('v1/auth/permissions', { params: permission }).then(res => successMsg(res));
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case USER_LIST:
|
||||
return { ...state, users: { ...action.data } };
|
||||
case ROLE_LIST:
|
||||
return { ...state, roles: { ...action.data } };
|
||||
case PERMISSIONS_LIST:
|
||||
return { ...state, permissions: { ...action.data } };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
getUsers,
|
||||
createUser,
|
||||
deleteUser,
|
||||
passwordReset,
|
||||
getRoles,
|
||||
createRole,
|
||||
deleteRole,
|
||||
getPermissions,
|
||||
createPermission,
|
||||
deletePermission,
|
||||
};
|
@ -20,6 +20,12 @@ const initialState = {
|
||||
functionMode: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {*} param0
|
||||
*/
|
||||
const login = user => request.post('v1/auth/users/login', user);
|
||||
|
||||
const getState = () => dispatch =>
|
||||
request
|
||||
.get('v1/console/server/state')
|
||||
@ -52,4 +58,4 @@ export default (state = initialState, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getState };
|
||||
export { getState, login };
|
||||
|
@ -14,5 +14,7 @@
|
||||
import locale from './locale';
|
||||
import base from './base';
|
||||
import subscribers from './subscribers';
|
||||
import authority from './authority';
|
||||
import namespace from './namespace';
|
||||
|
||||
export default { locale, base, subscribers };
|
||||
export default { locale, base, subscribers, authority, namespace };
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
import request from '../utils/request';
|
||||
import { GET_NAMESPACES } from '../constants';
|
||||
|
||||
const initialState = {
|
||||
namespaces: [],
|
||||
};
|
||||
|
||||
const getNamespaces = params => dispatch =>
|
||||
request.get('v1/console/namespaces', { params }).then(response => {
|
||||
const { code, data } = response;
|
||||
dispatch({
|
||||
type: GET_NAMESPACES,
|
||||
data: code === 200 ? data : [],
|
||||
});
|
||||
});
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case GET_NAMESPACES:
|
||||
return { ...state, namespaces: action.data };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { getNamespaces };
|
@ -1,15 +0,0 @@
|
||||
function getValue(key) {
|
||||
if (!document.cookie) return null;
|
||||
const list = document.cookie.split(';') || [];
|
||||
for (const item of list) {
|
||||
const [k = '', v = ''] = item.split('=');
|
||||
if (k.trim() === key) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setValue(key, value) {
|
||||
document.cookie = `${key}=${value}`;
|
||||
}
|
||||
|
||||
export default { getValue, setValue };
|
@ -46,3 +46,26 @@ export const getParameter = (search, name) => {
|
||||
const [, value = ''] = hit.split('=');
|
||||
return value;
|
||||
};
|
||||
|
||||
export const isJsonString = str => {
|
||||
try {
|
||||
if (typeof JSON.parse(str) === 'object') {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const generateUrl = (url, params) => {
|
||||
return [
|
||||
url,
|
||||
'?',
|
||||
Object.keys(params)
|
||||
.map(key => [key, params[key]].join('='))
|
||||
.join('&'),
|
||||
].join('');
|
||||
};
|
||||
|
||||
export const isPlainObject = obj => {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
};
|
||||
|
@ -1,5 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import qs from 'qs';
|
||||
import { Message } from '@alifd/next';
|
||||
import { browserHistory } from 'react-router';
|
||||
import { isPlainObject } from './nacosutil';
|
||||
// import { SUCCESS_RESULT_CODE } from '../constants';
|
||||
|
||||
const API_GENERAL_ERROR_MESSAGE = 'Request error, please try again later!';
|
||||
@ -7,6 +10,29 @@ const API_GENERAL_ERROR_MESSAGE = 'Request error, please try again later!';
|
||||
const request = () => {
|
||||
const instance = axios.create();
|
||||
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
const { url, params, data, method, headers } = config;
|
||||
if (!params) {
|
||||
config.params = {};
|
||||
}
|
||||
if (!url.includes('auth/users/login')) {
|
||||
const { accessToken = '' } = JSON.parse(localStorage.token || '{}');
|
||||
config.params.accessToken = accessToken;
|
||||
config.headers = Object.assign({}, headers, { accessToken });
|
||||
}
|
||||
if (data && isPlainObject(data) && ['post', 'put'].includes(method)) {
|
||||
config.data = qs.stringify(data);
|
||||
if (!headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
config.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
);
|
||||
|
||||
instance.interceptors.response.use(
|
||||
response => {
|
||||
const { success, resultCode, resultMessage = API_GENERAL_ERROR_MESSAGE } = response.data;
|
||||
@ -18,11 +44,26 @@ const request = () => {
|
||||
},
|
||||
error => {
|
||||
if (error.response) {
|
||||
const { data, status } = error.response;
|
||||
Message.error(data && typeof data === 'string' ? data : `HTTP ERROR: ${status}`);
|
||||
} else {
|
||||
Message.error(API_GENERAL_ERROR_MESSAGE);
|
||||
const { data = {}, status } = error.response;
|
||||
let message = `HTTP ERROR: ${status}`;
|
||||
if (typeof data === 'string') {
|
||||
message = data;
|
||||
} else if (typeof data === 'object') {
|
||||
message = data.message;
|
||||
}
|
||||
Message.error(message);
|
||||
|
||||
if (
|
||||
[401, 403].includes(status) &&
|
||||
['unknown user!', 'token invalid', 'token expired!'].includes(message)
|
||||
) {
|
||||
localStorage.removeItem('token');
|
||||
const [baseUrl] = location.href.split('#');
|
||||
location.href = `${baseUrl}#/login`;
|
||||
}
|
||||
return Promise.reject(error.response);
|
||||
}
|
||||
Message.error(API_GENERAL_ERROR_MESSAGE);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -17,6 +17,7 @@ package com.alibaba.nacos.core.auth;
|
||||
|
||||
import com.alibaba.nacos.core.env.ReloadableConfigs;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
@ -70,6 +71,11 @@ public class AuthConfigs {
|
||||
}
|
||||
|
||||
public boolean isAuthEnabled() {
|
||||
// Runtime -D parameter has higher priority:
|
||||
String enabled = System.getProperty("nacos.core.auth.enabled");
|
||||
if (StringUtils.isNotBlank(enabled)) {
|
||||
return BooleanUtils.toBoolean(enabled);
|
||||
}
|
||||
return BooleanUtils.toBoolean(reloadableConfigs.getProperties()
|
||||
.getProperty("nacos.core.auth.enabled", "false"));
|
||||
}
|
||||
|
@ -66,21 +66,22 @@ public class AuthFilter implements Filter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Loggers.AUTH.isDebugEnabled()) {
|
||||
Loggers.AUTH.debug("auth filter start, request: {}", req.getRequestURI());
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
String path = new URI(req.getRequestURI()).getPath();
|
||||
Method method = methodsCache.getMethod(req.getMethod(), path);
|
||||
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException();
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) {
|
||||
|
||||
if (Loggers.AUTH.isDebugEnabled()) {
|
||||
Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI());
|
||||
}
|
||||
|
||||
Secured secured = method.getAnnotation(Secured.class);
|
||||
String action = secured.action().toString();
|
||||
String resource = secured.resource();
|
||||
@ -100,12 +101,11 @@ public class AuthFilter implements Filter {
|
||||
}
|
||||
|
||||
} catch (AccessException e) {
|
||||
if (Loggers.AUTH.isDebugEnabled()) {
|
||||
Loggers.AUTH.debug("access denied, request: {} {}, reason: {}", req.getMethod(), req.getRequestURI(), e.getErrMsg());
|
||||
}
|
||||
resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getErrMsg());
|
||||
return;
|
||||
} catch (NoSuchMethodException e) {
|
||||
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
|
||||
"no such api:" + req.getMethod() + ":" + req.getRequestURI());
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, ExceptionUtil.getAllExceptionMsg(e));
|
||||
return;
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.alibaba.nacos.core.auth;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* Permission to auth
|
||||
*
|
||||
@ -57,4 +59,9 @@ public class Permission {
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ public class StartingSpringApplicationRunListener implements SpringApplicationRu
|
||||
private void logFilePath() {
|
||||
String[] dirNames = new String[]{"logs", "conf", "data"};
|
||||
for (String dirName: dirNames) {
|
||||
LOGGER.info("Nacos Log files: {}{}{}{}", NACOS_HOME, File.separatorChar, dirName, File.separatorChar);
|
||||
LOGGER.info("Nacos {} files: {}{}{}{}", dirName, NACOS_HOME, File.separatorChar, dirName, File.separatorChar);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,6 @@ else
|
||||
fi
|
||||
|
||||
JAVA_OPT="${JAVA_OPT} -Dloader.path=${BASE_DIR}/plugins/health,${BASE_DIR}/plugins/cmdb,${BASE_DIR}/plugins/mysql"
|
||||
JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
|
||||
JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}"
|
||||
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/${SERVER}.jar"
|
||||
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
|
||||
|
@ -192,12 +192,11 @@ CREATE TABLE `roles` (
|
||||
|
||||
CREATE TABLE `permissions` (
|
||||
`role` varchar(50) NOT NULL,
|
||||
`permission` varchar(512) NOT NULL,
|
||||
`gmt_create` bigint NULL,
|
||||
`gmt_modified` bigint NULL,
|
||||
UNIQUE INDEX `idx_role_resource` (`role` ASC, `permission` ASC) USING BTREE
|
||||
`resource` varchar(512) NOT NULL,
|
||||
`action` varchar(8) NOT NULL,
|
||||
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
|
||||
);
|
||||
|
||||
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
|
||||
|
||||
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');
|
||||
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
|
||||
|
@ -193,4 +193,4 @@ CREATE TABLE permissions (
|
||||
|
||||
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
|
||||
|
||||
INSERT INTO roles (username, role) VALUES ('nacos', 'GLOBAL_ADMIN');
|
||||
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@ -206,6 +206,7 @@
|
||||
<descriptors>
|
||||
<descriptor>release-nacos.xml</descriptor>
|
||||
</descriptors>
|
||||
<tarLongFileMode>posix</tarLongFileMode>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-all</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<version>1.2.0-beta.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -99,6 +99,8 @@ public class DistroConsistencyServiceImpl implements EphemeralConsistencyService
|
||||
|
||||
public volatile Notifier notifier = new Notifier();
|
||||
|
||||
private LoadDataTask loadDataTask = new LoadDataTask();
|
||||
|
||||
private Map<String, CopyOnWriteArrayList<RecordListener>> listeners = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, String> syncChecksumTasks = new ConcurrentHashMap<>(16);
|
||||
@ -117,6 +119,22 @@ public class DistroConsistencyServiceImpl implements EphemeralConsistencyService
|
||||
});
|
||||
|
||||
executor.submit(notifier);
|
||||
GlobalExecutor.submit(loadDataTask);
|
||||
}
|
||||
|
||||
private class LoadDataTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
load();
|
||||
if (!initialized) {
|
||||
GlobalExecutor.submit(this, globalConfig.getLoadDataRetryDelayMillis());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Loggers.DISTRO.error("load data failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void load() throws Exception {
|
||||
|
@ -34,6 +34,7 @@ import com.alibaba.nacos.naming.pojo.ClusterInfo;
|
||||
import com.alibaba.nacos.naming.pojo.IpAddressInfo;
|
||||
import com.alibaba.nacos.naming.pojo.ServiceDetailInfo;
|
||||
import com.alibaba.nacos.naming.pojo.ServiceView;
|
||||
import com.alibaba.nacos.naming.web.NamingResourceParser;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -55,7 +56,7 @@ public class CatalogController {
|
||||
@Autowired
|
||||
protected ServiceManager serviceManager;
|
||||
|
||||
@Secured(action = ActionTypes.READ)
|
||||
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
|
||||
@GetMapping("/service")
|
||||
public JSONObject serviceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
|
||||
String serviceName) throws NacosException {
|
||||
@ -96,7 +97,7 @@ public class CatalogController {
|
||||
return detailView;
|
||||
}
|
||||
|
||||
@Secured(action = ActionTypes.READ)
|
||||
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
|
||||
@RequestMapping(value = "/instances")
|
||||
public JSONObject instanceList(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
|
||||
@RequestParam String serviceName,
|
||||
@ -138,7 +139,7 @@ public class CatalogController {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Secured(action = ActionTypes.READ)
|
||||
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
|
||||
@GetMapping("/services")
|
||||
public Object listDetail(@RequestParam(required = false) boolean withInstances,
|
||||
@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
|
||||
|
@ -168,12 +168,6 @@ public class ServiceManager implements RecordListener<Service> {
|
||||
Service service = chooseServiceMap(namespace).get(name);
|
||||
Loggers.RAFT.info("[RAFT-NOTIFIER] datum is deleted, key: {}", key);
|
||||
|
||||
// check again:
|
||||
if (service != null && !service.allIPs().isEmpty()) {
|
||||
Loggers.SRV_LOG.warn("service not empty, key: {}", key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service != null) {
|
||||
service.destroy();
|
||||
consistencyService.remove(KeyBuilder.buildInstanceListKey(namespace, name, true));
|
||||
@ -414,10 +408,6 @@ public class ServiceManager implements RecordListener<Service> {
|
||||
throw new IllegalArgumentException("specified service not exist, serviceName : " + serviceName);
|
||||
}
|
||||
|
||||
if (!service.allIPs().isEmpty()) {
|
||||
throw new IllegalArgumentException("specified service has instances, serviceName : " + serviceName);
|
||||
}
|
||||
|
||||
consistencyService.remove(KeyBuilder.buildServiceMetaKey(namespaceId, serviceName));
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class GlobalConfig {
|
||||
|
||||
@Value("${nacos.naming.distro.taskDispatchPeriod:200}")
|
||||
@Value("${nacos.naming.distro.taskDispatchPeriod:2000}")
|
||||
private int taskDispatchPeriod = 2000;
|
||||
|
||||
@Value("${nacos.naming.distro.batchSyncKeyCount:1000}")
|
||||
@ -42,6 +42,9 @@ public class GlobalConfig {
|
||||
@Value("${nacos.naming.expireInstance:true}")
|
||||
private boolean expireInstance = true;
|
||||
|
||||
@Value("${nacos.naming.distro.loadDataRetryDelayMillis:30000}")
|
||||
private long loadDataRetryDelayMillis = 30000;
|
||||
|
||||
public int getTaskDispatchPeriod() {
|
||||
return taskDispatchPeriod;
|
||||
}
|
||||
@ -61,4 +64,8 @@ public class GlobalConfig {
|
||||
public boolean isExpireInstance() {
|
||||
return expireInstance;
|
||||
}
|
||||
|
||||
public long getLoadDataRetryDelayMillis() {
|
||||
return loadDataRetryDelayMillis;
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public class GlobalExecutor {
|
||||
private static final long SERVER_STATUS_UPDATE_PERIOD = TimeUnit.SECONDS.toMillis(5);
|
||||
|
||||
private static ScheduledExecutorService executorService =
|
||||
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
|
||||
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2, new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
@ -162,6 +162,10 @@ public class GlobalExecutor {
|
||||
executorService.submit(runnable);
|
||||
}
|
||||
|
||||
public static void submit(Runnable runnable, long delay) {
|
||||
executorService.schedule(runnable, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static void submitServiceUpdate(Runnable runnable) {
|
||||
serviceUpdateExecutor.execute(runnable);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user