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:
nkorange 2020-02-27 16:18:54 +08:00
commit cc0a4915c7
130 changed files with 5029 additions and 4176 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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";
}

View File

@ -128,6 +128,7 @@ public class NamingProxy {
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
refreshSrvIfNeed();
securityProxy.login(getServerList());
}
public List<String> getServerListFromEndpoint() {

View File

@ -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));

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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("*")

View File

@ -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,

View File

@ -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);

View File

@ -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 (

View File

@ -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 (

View File

@ -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>-->

View File

@ -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);

View File

@ -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);

View File

@ -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 ?

View File

@ -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!");
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -30,6 +30,7 @@
"generator-star-spacing": "off",
"wrap-iife": "off",
"arrow-parens": "off",
"indent": "off"
"indent": "off",
"comma-dangle": "off"
}
}

View File

@ -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({

View File

@ -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"
}

View File

@ -125,9 +125,7 @@ class RegionGroup extends React.Component {
? false
: window.location.search.indexOf('hideTopbar=') === -1,
},
() => {
this.setRegionWidth();
}
() => this.setRegionWidth()
);
}

View File

@ -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';

View File

@ -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

View File

@ -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,

View File

@ -1217,10 +1217,6 @@ form.vertical-margin-lg .form-group {
border-color: #e0e0e0 !important;
}
.main-container {
padding: 10px;
}
.row-bg-green {
background-color: #e4fdda;
}

View File

@ -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 '';
};

View File

@ -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>
</>
);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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',
};

View File

@ -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;

View File

@ -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;

View File

@ -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.
*/

View File

@ -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;

View File

@ -0,0 +1,7 @@
# 权限控制
> AuthorityControl
1. UserManagement => 用户管理
2. RolesManagement => 角色管理
3. PermissionsManagement => 权限管理

View File

@ -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;

View File

@ -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;

View File

@ -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.
*/

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>
&nbsp;&nbsp;&nbsp;
<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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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,
})
);
}

View File

@ -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 })
);
}

View File

@ -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 })
);
}

View File

@ -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() {

View File

@ -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>
</>
);
}
}

View File

@ -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>

View File

@ -13,7 +13,4 @@
.next-pagination-size-selector{
position: static !important;
}
.next-overlay-inner{
top:154px !important;
}
}

View File

@ -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}`
);
}

View File

@ -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({

View File

@ -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>
</>
);
}
}

View File

@ -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();
},
});
};

View File

@ -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,
});
},
});
});
});
};

View File

@ -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>
</>
);
}
}

View File

@ -27,6 +27,7 @@ class Password extends React.Component {
static propTypes = {
locale: PropTypes.object,
history: PropTypes.object,
};
constructor(props) {

View File

@ -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}

View File

@ -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';

View File

@ -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}`} />}</>;
}
}

View File

@ -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,
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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]';
};

View File

@ -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

View File

@ -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>

View File

@ -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"));
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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}"

View File

@ -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');

View File

@ -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');

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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,

View File

@ -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));
}

View File

@ -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;
}
}

View File

@ -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