Merge pull request #16 from alibaba/develop

拉代码
This commit is contained in:
邪影oO 2020-03-19 08:51:31 +08:00 committed by GitHub
commit 96fec3ab27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 5728 additions and 5042 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ target
node_modules
test/derby.log
derby.log
work

View File

@ -97,9 +97,12 @@ Contributors are welcomed to join Nacos project. Please check [CONTRIBUTING](./C
* users-nacos@googlegroups.com: Nacos usage general discussion.
* dev-nacos@googlegroups.com: Nacos developer discussion (APIs, feature design, etc).
* commits-nacos@googlegroups.com: Commits notice, very high frequency.
* Join us from DingDing.
* Join us from DingDing(Group 1: 21708933(full), Group 2: 30438813).
![cwex](https://img.alicdn.com/tfs/TB1bpBlQmrqK1RjSZK9XXXyypXa-830-972.png_288x480q80.jpg)
## Download
- [Github Release](https://github.com/alibaba/nacos/releases)
- [Baidu Netdisk](https://pan.baidu.com/s/1186nmlqPGows9gUZKAx8Zw) Fetch Code : `rest`
## Who is using

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</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</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</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -232,7 +232,9 @@ public class ServerHttpAgent implements HttpAgent {
}
private String getUrl(String serverAddr, String relativePath) {
return serverAddr + "/" + serverListMgr.getContentPath() + relativePath;
String contextPath = serverListMgr.getContentPath().startsWith("/") ?
serverListMgr.getContentPath() : "/" + serverListMgr.getContentPath();
return serverAddr + contextPath + relativePath;
}
public static String getAppname() {
@ -274,14 +276,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;
/**
@ -87,6 +84,7 @@ public class SecurityProxy {
username = properties.getProperty(PropertyKeyConst.USERNAME, StringUtils.EMPTY);
password = properties.getProperty(PropertyKeyConst.PASSWORD, StringUtils.EMPTY);
contextPath = properties.getProperty(PropertyKeyConst.CONTEXT_PATH, "/nacos");
contextPath = contextPath.startsWith("/") ? contextPath : "/" + contextPath;
}
public boolean login(List<String> servers) {
@ -102,7 +100,7 @@ public class SecurityProxy {
return true;
}
}
} catch (Throwable t) {
} catch (Throwable ignore) {
}
return false;
@ -111,7 +109,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 +119,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

@ -20,6 +20,7 @@ import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.config.impl.HttpSimpleClient;
import org.slf4j.Logger;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.Properties;
@ -174,12 +175,12 @@ public class ParamUtil {
@Override
public String call() {
String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
return org.apache.commons.lang3.StringUtils.isNotBlank(namespace) ? namespace : StringUtils.EMPTY;
return StringUtils.isNotBlank(namespace) ? namespace : StringUtils.EMPTY;
}
});
}
if (org.apache.commons.lang3.StringUtils.isBlank(namespaceTmp)) {
if (StringUtils.isBlank(namespaceTmp)) {
namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE);
}
return StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : StringUtils.EMPTY;

View File

@ -36,14 +36,15 @@ public class NamingTest {
public void testServiceList() throws Exception {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "11.160.165.126:8848");
properties.put(PropertyKeyConst.NAMESPACE, "t1");
properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
properties.put(PropertyKeyConst.USERNAME, "nacos");
properties.put(PropertyKeyConst.PASSWORD, "nacos");
NamingService namingService = NacosFactory.createNamingService(properties);
Instance instance = new Instance();
instance.setIp("1.1.1.1");
instance.setPort(80);
instance.setPort(800);
instance.setWeight(2);
Map<String, String> map = new HashMap<String, String>();
map.put("netType", "external");
@ -56,7 +57,6 @@ public class NamingTest {
// expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
// ListView<String> serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
Thread.sleep(1000000000L);
}

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

@ -177,6 +177,7 @@ public class ConfigController {
String tenant,
@RequestParam(value = "tag", required = false) String tag)
throws IOException, ServletException, NacosException {
tenant = processTenant(tenant);
// check params
ParamUtils.checkParam(dataId, group, "datumId", "content");
ParamUtils.checkParam(tag);
@ -282,6 +283,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 +294,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 +381,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,
@ -428,6 +433,7 @@ public class ConfigController {
defaultValue = StringUtils.EMPTY) String tenant,
@RequestParam(value = "ids", required = false) List<Long> ids) {
ids.removeAll(Collections.singleton(null));
tenant = processTenant(tenant);
List<ConfigAllInfo> dataList = persistService.findAllConfigInfo4Export(dataId, group, tenant, appName, ids);
List<ZipUtils.ZipItem> zipItemList = new ArrayList<>();
StringBuilder metaData = null;
@ -545,6 +551,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,
@ -615,4 +622,11 @@ public class ConfigController {
return ResultBuilder.buildSuccessResult("克隆成功", saveResult);
}
private String processTenant(String tenant){
if (StringUtils.isEmpty(tenant) || NAMESPACE_PUBLIC_KEY.equalsIgnoreCase(tenant)) {
return "";
}
return tenant;
}
}

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

@ -20,7 +20,6 @@ import com.alibaba.nacos.config.server.constant.Constants;
import com.alibaba.nacos.config.server.monitor.MetricsMonitor;
import com.alibaba.nacos.config.server.service.notify.NotifyService;
import com.alibaba.nacos.config.server.service.notify.NotifyService.HttpResult;
import com.alibaba.nacos.config.server.utils.LogUtil;
import com.alibaba.nacos.config.server.utils.PropertyUtil;
import com.alibaba.nacos.config.server.utils.RunningConfigUtils;
import com.alibaba.nacos.config.server.utils.event.EventDispatcher;
@ -32,10 +31,9 @@ import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@ -44,9 +42,14 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog;
import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog;
@ -60,17 +63,21 @@ import static com.alibaba.nacos.core.utils.SystemUtils.*;
@Service
public class ServerListService implements ApplicationListener<WebServerInitializedEvent> {
@Autowired
private Environment env;
@Autowired
private ServletContext servletContext;
private final ServletContext servletContext;
@Value("${server.port:8848}")
private int port;
@Value("${useAddressServer}")
private Boolean isUseAddressServer = true;
public ServerListService(ServletContext servletContext) {
this.servletContext = servletContext;
}
@PostConstruct
public void init() {
serverPort = System.getProperty("nacos.server.port", "8848");
String envDomainName = System.getenv("address_server_domain");
if (StringUtils.isBlank(envDomainName)) {
domainName = System.getProperty("address.server.domain", "jmenv.tbsite.net");
@ -88,55 +95,23 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
addressServerUrl = "http://" + domainName + ":" + addressPort + addressUrl;
envIdUrl = "http://" + domainName + ":" + addressPort + "/env";
defaultLog.info("ServerListService address-server port:" + serverPort);
defaultLog.info("ServerListService address-server port:" + addressPort);
defaultLog.info("ADDRESS_SERVER_URL:" + addressServerUrl);
isHealthCheck = PropertyUtil.isHealthCheck();
maxFailCount = PropertyUtil.getMaxHealthCheckFailCount();
try {
String val = null;
val = env.getProperty("useAddressServer");
if (val != null && FALSE_STR.equals(val)) {
isUseAddressServer = false;
}
fatalLog.warn("useAddressServer:{}", isUseAddressServer);
} catch (Exception e) {
fatalLog.error("read application.properties wrong", e);
}
fatalLog.warn("useAddressServer:{}", isUseAddressServer);
GetServerListTask task = new GetServerListTask();
task.run();
if (null == serverList || serverList.isEmpty()) {
if (CollectionUtils.isEmpty(serverList)) {
fatalLog.error("########## cannot get serverlist, so exit.");
throw new RuntimeException("cannot get serverlist, so exit.");
} else {
TimerTaskService.scheduleWithFixedDelay(task, 0L, 5L, TimeUnit.SECONDS);
}
httpclient.start();
CheckServerHealthTask checkServerHealthTask = new CheckServerHealthTask();
TimerTaskService.scheduleWithFixedDelay(checkServerHealthTask, 0L, 5L, TimeUnit.SECONDS);
}
public String getEnvId() {
String envId = "";
int i = 0;
do {
envId = getEnvIdHttp();
if (StringUtils.isBlank(envId)) {
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
LogUtil.defaultLog.error("sleep interrupt");
}
}
} while (StringUtils.isBlank(envId) && i < 5);
if (!StringUtils.isBlank(envId)) {
} else {
LogUtil.defaultLog.error("envId is blank");
}
return envId;
}
public List<String> getServerList() {
return new ArrayList<String>(serverList);
@ -161,21 +136,15 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
/**
* serverList has changed
*/
static public class ServerlistChangeEvent implements EventDispatcher.Event {
static public class ServerListChangeEvent implements EventDispatcher.Event {
}
private void updateIfChanged(List<String> newList) {
if (newList.isEmpty()) {
if (CollectionUtils.isEmpty(newList)||newList.equals(serverList)) {
return;
}
boolean isContainSelfIp = false;
for (String ipPortTmp : newList) {
if (ipPortTmp.contains(LOCAL_IP)) {
isContainSelfIp = true;
break;
}
}
boolean isContainSelfIp = newList.stream().anyMatch(ipPortTmp -> ipPortTmp.contains(LOCAL_IP));
if (isContainSelfIp) {
isInIpList = true;
@ -186,30 +155,23 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
fatalLog.error("########## [serverlist] self ip {} not in serverlist {}", selfAddr, newList);
}
if (newList.equals(serverList)) {
return;
}
serverList = new ArrayList<String>(newList);
List<String> unhealthRemoved = new ArrayList<String>();
for (String unhealthIp : serverListUnhealth) {
if (!newList.contains(unhealthIp)) {
unhealthRemoved.add(unhealthIp);
if(!serverListUnhealth.isEmpty()){
List<String> unhealthyRemoved = serverListUnhealth.stream()
.filter(unhealthyIp -> !newList.contains(unhealthyIp)).collect(Collectors.toList());
serverListUnhealth.removeAll(unhealthyRemoved);
List<String> unhealthyCountRemoved = serverIp2unhealthCount.keySet().stream()
.filter(key -> !newList.contains(key)).collect(Collectors.toList());
for (String unhealthyCountTmp : unhealthyCountRemoved) {
serverIp2unhealthCount.remove(unhealthyCountTmp);
}
}
serverListUnhealth.removeAll(unhealthRemoved);
List<String> unhealthCountRemoved = new ArrayList<String>();
for (Map.Entry<String, Integer> ip2UnhealthCountTmp : serverIp2unhealthCount.entrySet()) {
if (!newList.contains(ip2UnhealthCountTmp.getKey())) {
unhealthCountRemoved.add(ip2UnhealthCountTmp.getKey());
}
}
for (String unhealthCountTmp : unhealthCountRemoved) {
serverIp2unhealthCount.remove(unhealthCountTmp);
}
defaultLog.warn("[serverlist] updated to {}", serverList);
@ -217,7 +179,7 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
/**
* 非并发fireEvent
*/
EventDispatcher.fireEvent(new ServerlistChangeEvent());
EventDispatcher.fireEvent(new ServerListChangeEvent());
}
/**
@ -256,7 +218,7 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
if (HttpServletResponse.SC_OK == result.code) {
isAddressServerHealth = true;
addressServerFailCcount = 0;
addressServerFailCount = 0;
List<String> lines = IoUtils.readLines(new StringReader(result.content));
List<String> ips = new ArrayList<String>(lines.size());
for (String serverAddr : lines) {
@ -266,16 +228,16 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
}
return ips;
} else {
addressServerFailCcount++;
if (addressServerFailCcount >= maxFailCount) {
addressServerFailCount++;
if (addressServerFailCount >= maxFailCount) {
isAddressServerHealth = false;
}
defaultLog.error("[serverlist] failed to get serverlist, error code {}", result.code);
return Collections.emptyList();
}
} catch (IOException e) {
addressServerFailCcount++;
if (addressServerFailCcount >= maxFailCount) {
addressServerFailCount++;
if (addressServerFailCount >= maxFailCount) {
isAddressServerHealth = false;
}
defaultLog.error("[serverlist] exception, " + e.toString(), e);
@ -302,23 +264,7 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
}
}
private String getEnvIdHttp() {
try {
// "http://jmenv.tbsite.net:8080/env";
HttpResult result = NotifyService.invokeURL(envIdUrl, null, null);
if (HttpServletResponse.SC_OK == result.code) {
return result.content.trim();
} else {
defaultLog.error("[envId] failed to get envId, error code {}", result.code);
return "";
}
} catch (IOException e) {
defaultLog.error("[envId] exception, " + e.toString(), e);
return "";
}
}
class GetServerListTask implements Runnable {
@Override
@ -338,18 +284,18 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
String url = "http://" + serverIp + servletContext.getContextPath() + Constants.HEALTH_CONTROLLER_PATH;
// "/nacos/health";
HttpGet request = new HttpGet(url);
httpclient.execute(request, new AyscCheckServerHealthCallBack(serverIp));
httpclient.execute(request, new AsyncCheckServerHealthCallBack(serverIp));
}
long endCheckTime = System.currentTimeMillis();
long cost = endCheckTime - startCheckTime;
defaultLog.debug("checkServerHealth cost: {}", cost);
}
class AyscCheckServerHealthCallBack implements FutureCallback<HttpResponse> {
class AsyncCheckServerHealthCallBack implements FutureCallback<HttpResponse> {
private String serverIp;
public AyscCheckServerHealthCallBack(String serverIp) {
public AsyncCheckServerHealthCallBack(String serverIp) {
this.serverIp = serverIp;
}
@ -363,29 +309,16 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
@Override
public void failed(Exception ex) {
int failCount = serverIp2unhealthCount.compute(serverIp,(key,oldValue)->{
if(oldValue == null){
return 1;
}
return oldValue+1;
});
if (failCount > maxFailCount) {
if (!serverListUnhealth.contains(serverIp)) {
serverListUnhealth.add(serverIp);
}
defaultLog.error("unhealthIp:{}, unhealthCount:{}", serverIp, failCount);
MetricsMonitor.getUnhealthException().increment();
}
computeFailCount();
}
@Override
public void cancelled() {
int failCount = serverIp2unhealthCount.compute(serverIp,(key,oldValue)->{
if(oldValue == null){
return 1;
computeFailCount();
}
return oldValue+1;
});
private void computeFailCount() {
int failCount = serverIp2unhealthCount.compute(serverIp,(key,oldValue)->oldValue == null?1:oldValue+1);
if (failCount > maxFailCount) {
if (!serverListUnhealth.contains(serverIp)) {
serverListUnhealth.add(serverIp);
@ -435,15 +368,15 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
static final int TIMEOUT = 5000;
private int maxFailCount = 12;
private static volatile List<String> serverList = new ArrayList<String>();
private static volatile List<String> serverListUnhealth = new ArrayList<String>();
private static volatile List<String> serverListUnhealth = Collections.synchronizedList(new ArrayList<String>());;
private static volatile boolean isAddressServerHealth = true;
private static volatile int addressServerFailCcount = 0;
private static volatile int addressServerFailCount = 0;
private static volatile boolean isInIpList = true;
/**
* ip unhealth count
*/
private static volatile Map<String, Integer> serverIp2unhealthCount = new HashMap<String, Integer>();
private static Map<String, Integer> serverIp2unhealthCount = new ConcurrentHashMap<>();
private RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(PropertyUtil.getNotifyConnectTimeout())
.setSocketTimeout(PropertyUtil.getNotifySocketTimeout()).build();
@ -451,18 +384,13 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
private CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig)
.build();
/**
* server之间通信的端口
*/
public String serverPort;
public String domainName;
public String addressPort;
public String addressUrl;
public String envIdUrl;
public String addressServerUrl;
private Boolean isUseAddressServer = true;
private boolean isHealthCheck = true;
private final static String FALSE_STR = "false";
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
@ -474,6 +402,10 @@ public class ServerListService implements ApplicationListener<WebServerInitializ
}
setServerList(new ArrayList<String>(newList));
}
httpclient.start();
CheckServerHealthTask checkServerHealthTask = new CheckServerHealthTask();
TimerTaskService.scheduleWithFixedDelay(checkServerHealthTask, 0L, 5L, TimeUnit.SECONDS);
}
}

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 (
@ -198,4 +199,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

@ -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 (
@ -193,4 +194,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</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.*;
@ -106,6 +109,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,
@ -154,6 +158,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 +176,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

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

@ -35,3 +35,9 @@ 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
nacos.naming.empty-service.auto-clean=true
nacos.naming.empty-service.clean.initial-delay-ms=50000
nacos.naming.empty-service.clean.period-time-ms=30000
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,15 @@ 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';
export const GET_CONFIGURATION = 'GET_CONFIGURATION';

View File

@ -12,9 +12,16 @@
*/
import projectConfig from './config';
import moment from 'moment';
import $ from 'jquery';
import i18DocObj from './i18ndoc';
import { Message } from '@alifd/next';
function goLogin() {
const url = window.location.href;
localStorage.removeItem('token');
const base_url = url.split('#')[0];
console.log('base_url', base_url);
window.location = `${base_url}#/login`;
}
const global = window;
@ -205,120 +212,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 +454,7 @@ const request = (function(_global) {
} catch (e) {}
// 设置自动loading效果
if (serviceObj.autoLoading) {
nacosUtils.openLoading();
// nacosUtils.openLoading();
const prevComplete = config.complete;
config.complete = function() {
nacosUtils.closeLoading();
@ -597,11 +490,22 @@ const request = (function(_global) {
// 处理后置中间件
config = handleMiddleWare.apply(this, [config, ...args, middlewareBackList]);
let token = {};
try {
token = JSON.parse(localStorage.token);
} catch (e) {
console.log('Token Erro', localStorage.token, e);
goLogin();
}
const { accessToken = '' } = 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 +519,17 @@ const request = (function(_global) {
success => {},
error => {
// 处理403 forbidden
if (error && (error.status === 403 || error.status === 401)) {
// 跳转至login页
// TODO: react-router 重写改造成本比较高这里先hack
const url = window.location.href;
// TODO: 后端返回细致的错误码如果原始密码不对 不应该直接跳到登陆页
if (url.includes('password')) {
return;
}
const base_url = url.split('#')[0];
window.location = `${base_url}#/login`;
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)
) {
goLogin();
}
return error;
}
);
}
@ -645,10 +549,9 @@ export {
nacosEvent,
nacosUtils,
aliwareGetCookieByKeyName,
aliwareIntl,
removeParams,
getParams,
setParam,
setParams,
removeParams,
request,
};

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,6 @@ import { LANGUAGE_KEY, REDUX_DEVTOOLS } from './constants';
import Login from './pages/Login';
import Namespace from './pages/NameSpace';
import Password from './pages/Password';
import Newconfig from './pages/ConfigurationManagement/NewConfig';
import Configsync from './pages/ConfigurationManagement/ConfigSync';
import Configdetail from './pages/ConfigurationManagement/ConfigDetail';
@ -44,6 +43,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,17 +67,13 @@ 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 = [
{ path: '/', exact: true, render: () => <Redirect to="/welcome" /> },
{ path: '/welcome', component: Welcome },
{ path: '/namespace', component: Namespace },
{ path: '/password', component: Password },
{ path: '/newconfig', component: Newconfig },
{ path: '/configsync', component: Configsync },
{ path: '/configdetail', component: Configdetail },
@ -89,12 +87,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

@ -18,14 +18,13 @@ import { connect } from 'react-redux';
import { ConfigProvider, Dropdown, Menu } from '@alifd/next';
import siteConfig from '../config';
import { changeLanguage } from '@/reducers/locale';
import PasswordReset from '../pages/AuthorityControl/UserManagement/PasswordReset';
import { passwordReset } from '../reducers/authority';
import './index.scss';
@withRouter
@connect(
state => ({ ...state.locale }),
{ changeLanguage }
)
@connect(state => ({ ...state.locale }), { changeLanguage })
@ConfigProvider.config
class Header extends React.Component {
static displayName = 'Header';
@ -38,6 +37,8 @@ class Header extends React.Component {
changeLanguage: PropTypes.func,
};
state = { passwordResetUser: '' };
switchLang = () => {
const { language = 'en-US', changeLanguage } = this.props;
const currentLanguage = language === 'en-US' ? 'zh-CN' : 'en-US';
@ -50,16 +51,23 @@ class Header extends React.Component {
};
changePassword = () => {
this.props.history.push('/password');
this.setState({
passwordResetUser: this.getUsername(),
});
};
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 '';
};
@ -71,6 +79,7 @@ class Header extends React.Component {
location: { pathname },
} = this.props;
const { home, docs, blog, community, languageSwitchButton } = locale;
const { passwordResetUser = '' } = this.state;
const BASE_URL = `https://nacos.io/${language.toLocaleLowerCase()}/`;
const NAV_MENU = [
{ id: 1, title: home, link: BASE_URL },
@ -79,45 +88,56 @@ class Header extends React.Component {
{ id: 4, title: community, link: `${BASE_URL}community/index.html` },
];
return (
<header className="header-container header-container-primary">
<div className="header-body">
<a
href={`https://nacos.io/${language.toLocaleLowerCase()}/`}
target="_blank"
rel="noopener noreferrer"
>
<img
src="img/logo-2000-390.svg"
className="logo"
alt={siteConfig.name}
title={siteConfig.name}
/>
</a>
{/* if is login page, we will show logout */}
{pathname !== '/login' && (
<Dropdown trigger={<div className="logout">{this.getUsername()}</div>}>
<Menu>
<Menu.Item onClick={this.logout}>{locale.logout}</Menu.Item>
<Menu.Item onClick={this.changePassword}>{locale.changePassword}</Menu.Item>
</Menu>
</Dropdown>
)}
<span className="language-switch language-switch-primary" onClick={this.switchLang}>
{languageSwitchButton}
</span>
<div className="header-menu header-menu-open">
<ul>
{NAV_MENU.map(item => (
<li key={item.id} className="menu-item menu-item-primary">
<a href={item.link} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
</li>
))}
</ul>
<>
<header className="header-container header-container-primary">
<div className="header-body">
<a
href={`https://nacos.io/${language.toLocaleLowerCase()}/`}
target="_blank"
rel="noopener noreferrer"
>
<img
src="img/logo-2000-390.svg"
className="logo"
alt={siteConfig.name}
title={siteConfig.name}
/>
</a>
{/* if is login page, we will show logout */}
{pathname !== '/login' && (
<Dropdown trigger={<div className="logout">{this.getUsername()}</div>}>
<Menu>
<Menu.Item onClick={this.logout}>{locale.logout}</Menu.Item>
<Menu.Item onClick={this.changePassword}>{locale.changePassword}</Menu.Item>
</Menu>
</Dropdown>
)}
<span className="language-switch language-switch-primary" onClick={this.switchLang}>
{languageSwitchButton}
</span>
<div className="header-menu header-menu-open">
<ul>
{NAV_MENU.map(item => (
<li key={item.id} className="menu-item menu-item-primary">
<a href={item.link} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
</header>
</header>
<PasswordReset
username={passwordResetUser}
onOk={user =>
passwordReset(user).then(res => {
return res;
})
}
onCancel={() => this.setState({ passwordResetUser: undefined })}
/>
</>
);
}
}

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

@ -9,4 +9,4 @@
* 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, 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

@ -11,6 +11,7 @@
* limitations under the License.
*/
import Password from './Password';
export default Password;
.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

@ -39,15 +39,20 @@ import ShowCodeing from 'components/ShowCodeing';
import DeleteDialog from 'components/DeleteDialog';
import DashboardCard from './DashboardCard';
import { getParams, setParams, request, aliwareIntl } from '@/globalLib';
import axios from 'axios';
import { connect } from 'react-redux';
import { getConfigs } from '../../../reducers/configuration';
import './index.scss';
import { LANGUAGE_KEY } from '../../../constants';
const { Panel } = Collapse;
const { Row, Col } = Grid;
const configsTableSelected = new Map();
@connect(
state => ({
configurations: state.configuration.configurations,
}),
{ getConfigs }
)
@ConfigProvider.config
class ConfigurationManagement extends React.Component {
static displayName = 'ConfigurationManagement';
@ -133,7 +138,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>
@ -195,25 +201,6 @@ class ConfigurationManagement extends React.Component {
}
}
/**
* 回车事件
*/
keyDownSearch(e) {
const theEvent = e || window.event;
const code = theEvent.keyCode || theEvent.which || theEvent.charCode;
if (this.state.isPageEnter) {
this.setState({
isPageEnter: false,
});
return false;
}
if (code === 13) {
this.getData();
return false;
}
return true;
}
navTo(url, record) {
this.serverId = getParams('serverId') || '';
this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数
@ -236,18 +223,6 @@ class ConfigurationManagement extends React.Component {
});
}
UNSAFE_componentWillMount() {
window.addEventListener('keydown', this.keyDownSearch.bind(this), false);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.keyDownSearch.bind(this));
}
onSearch() {}
onChange() {}
cleanAndGetData(needclean = false) {
if (needclean) {
this.dataId = '';
@ -269,54 +244,33 @@ class ConfigurationManagement extends React.Component {
}
getData(pageNo = 1, clearSelect = true) {
const self = this;
if (this.state.loading) {
return;
}
this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数
this.serverId = getParams('serverId') || '';
let urlPrefix = '';
const params = {
dataId: this.dataId,
group: this.group,
appName: this.appName,
config_tags: this.state.config_tags.join(','),
pageNo,
pageSize: this.state.pageSize,
};
if (this.dataId.indexOf('*') !== -1 || this.group.indexOf('*') !== -1) {
urlPrefix = 'v1/cs/configs?search=blur';
params.search = 'blur';
} else {
urlPrefix = 'v1/cs/configs?search=accurate';
params.search = 'accurate';
}
request({
url: `${urlPrefix}&dataId=${this.dataId}&group=${this.group}&appName=${
this.appName
}&config_tags=${this.state.config_tags || ''}&pageNo=${pageNo}&pageSize=${
this.state.pageSize
}`,
beforeSend() {
self.openLoading();
},
success(data) {
if (data != null) {
self.setState({
dataSource: data.pageItems,
total: data.totalCount,
currentPage: data.pageNumber,
});
if (clearSelect) {
self.setState({
selectedRecord: [],
selectedKeys: [],
});
}
}
self.setState({
tenant: self.tenant,
});
},
error(data) {
self.setState({
dataSource: [],
total: 0,
currentPage: 0,
});
},
complete() {
self.closeLoading();
},
});
this.setState({ loading: true });
this.props.getConfigs(params).then(() =>
this.setState({
loading: false,
selectedRecord: [],
selectedKeys: [],
tenant: this.tenant,
})
);
}
showMore() {}
@ -435,28 +389,17 @@ class ConfigurationManagement extends React.Component {
changePage(value, e) {
this.setState(
{
isPageEnter: e.keyCode && e.keyCode === 13,
isPageEnter: e && 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() {}
chooseFieldChange(fieldValue) {
this.setState({
fieldValue,
@ -502,16 +445,10 @@ class ConfigurationManagement extends React.Component {
});
}
getDataId(value) {
this.dataId = value;
this.setState({
dataId: value,
});
}
setConfigTags(value) {
this.setState({
config_tags: value,
config_tags: value || [],
tagLst: value,
});
}
@ -618,8 +555,6 @@ class ConfigurationManagement extends React.Component {
});
}
onPageSelectAll(selected, records) {}
getBatchFailedContent(res) {
const { locale = {} } = this.props;
return (
@ -629,7 +564,7 @@ class ConfigurationManagement extends React.Component {
<Collapse style={{ width: '500px' }}>
{'failedItems' in res.data && res.data.failedItems.length > 0 ? (
<Panel title={locale.failedEntry + res.data.failedItems.length}>
<Table dataSource={res.data.failedItems} fixedHeader maxBodyHeight={400}>
<Table dataSource={res.data.failedItems} fixedHeader>
<Table.Column title={'Data ID'} dataIndex={'dataId'} />
<Table.Column title={'Group'} dataIndex={'group'} />
</Table>
@ -639,7 +574,7 @@ class ConfigurationManagement extends React.Component {
)}
{'succeededItems' in res.data && res.data.succeededItems.length > 0 ? (
<Panel title={locale.successfulEntry + res.data.succeededItems.length}>
<Table dataSource={res.data.succeededItems} fixedHeader maxBodyHeight={400}>
<Table dataSource={res.data.succeededItems} fixedHeader>
<Table.Column title={'Data ID'} dataIndex={'dataId'} />
<Table.Column title={'Group'} dataIndex={'group'} />
</Table>
@ -649,7 +584,7 @@ class ConfigurationManagement extends React.Component {
)}
{'unprocessedItems' in res.data && res.data.unprocessedItems.length > 0 ? (
<Panel title={locale.unprocessedEntry + res.data.unprocessedItems.length}>
<Table dataSource={res.data.unprocessedItems} fixedHeader maxBodyHeight={400}>
<Table dataSource={res.data.unprocessedItems} fixedHeader>
<Table.Column title={'Data ID'} dataIndex={'dataId'} />
<Table.Column title={'Group'} dataIndex={'group'} />
</Table>
@ -689,28 +624,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() {
@ -835,7 +793,7 @@ class ConfigurationManagement extends React.Component {
title: locale.cloningConfiguration,
footer: false,
content: (
<div>
<>
<div style={{ marginBottom: 10 }}>
<span style={{ color: '#999', marginRight: 5 }}>{locale.source}</span>
<span style={{ color: '#49D2E7' }}>{self.state.nownamespace_name} </span>|{' '}
@ -953,25 +911,19 @@ class ConfigurationManagement extends React.Component {
</Button>
</div>
<div style={{ marginBottom: 10 }}>
<span style={{ color: '#00AA00', 'font-weight': 'bold' }}>
<span style={{ color: '#00AA00', fontWeight: 'bold' }}>
{locale.cloneEditableTitle}
</span>
</div>
<div>
<Table dataSource={editableTableData}>
<Table.Column
title="Data Id"
dataIndex="dataId"
cell={renderEditableTableCellDataId}
/>
<Table.Column
title="Group"
dataIndex="group"
cell={renderEditableTableCellGroup}
/>
</Table>
</div>
</div>
<Table dataSource={editableTableData}>
<Table.Column
title="Data Id"
dataIndex="dataId"
cell={renderEditableTableCellDataId}
/>
<Table.Column title="Group" dataIndex="group" cell={renderEditableTableCellGroup} />
</Table>
</>
),
});
},
@ -1158,273 +1110,268 @@ class ConfigurationManagement extends React.Component {
}
render() {
const { locale = {} } = this.props;
const { locale = {}, configurations = {} } = this.props;
return (
<div>
<>
<BatchHandle ref={ref => (this.batchHandle = ref)} />
<Loading
shape={'flower'}
style={{ position: 'relative', width: '100%', overflow: 'auto' }}
visible={this.state.loading}
tip={'Loading...'}
color={'#333'}
>
<div className={this.state.hasdash ? 'dash-page-container' : ''}>
<div
className={this.state.hasdash ? 'dash-left-container' : ''}
style={{ position: 'relative', padding: 10 }}
>
<div style={{ display: this.inApp ? 'none' : 'block', marginTop: -15 }}>
<RegionGroup
namespaceCallBack={this.cleanAndGetData.bind(this)}
setNowNameSpace={this.setNowNameSpace.bind(this)}
/>
</div>
<div
style={{
display: this.inApp ? 'none' : 'block',
position: 'relative',
width: '100%',
overflow: 'hidden',
height: '40px',
}}
>
<h3
style={{
height: 30,
width: '100%',
lineHeight: '30px',
padding: 0,
margin: 0,
paddingLeft: 10,
borderLeft: '3px solid #09c',
color: '#ccc',
fontSize: '12px',
}}
>
<span style={{ fontSize: '14px', color: '#000', marginRight: 8 }}>
{locale.configurationManagement8}
</span>
<span style={{ fontSize: '14px', color: '#000', marginRight: 8 }}>|</span>
<span style={{ fontSize: '14px', color: '#000', marginRight: 8 }}>
{this.state.nownamespace_name}
</span>
<span style={{ fontSize: '14px', color: '#000', marginRight: 18 }}>
{this.state.nownamespace_id}
</span>
{locale.queryResults}
<strong style={{ fontWeight: 'bold' }}> {this.state.total} </strong>
{locale.articleMeetRequirements}
</h3>
<div
style={{ position: 'absolute', textAlign: 'right', zIndex: 2, right: 0, top: 0 }}
/>
</div>
<div
style={{
position: 'relative',
marginTop: 10,
height: this.state.isAdvancedQuery ? 'auto' : 42,
overflow: 'hidden',
}}
>
<Form inline>
<Form.Item label={'Data ID:'}>
<Input
htmlType={'text'}
placeholder={locale.fuzzyd}
style={{ width: 200 }}
value={this.state.dataId}
onChange={this.getDataId.bind(this)}
/>
</Form.Item>
<Form.Item label={'Group:'}>
<Select.AutoComplete
style={{ width: 200 }}
size={'medium'}
placeholder={locale.fuzzyg}
dataSource={this.state.groups}
value={this.state.group}
onChange={this.setGroup.bind(this)}
hasClear
/>
</Form.Item>
<Form.Item label={''}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.selectAll.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=dashsearch'}
>
{locale.query}
</Button>
</Form.Item>
<Form.Item
style={
this.inApp
? { display: 'none' }
: { verticalAlign: 'middle', marginTop: 0, marginLeft: 10 }
}
>
<div
style={{ color: '#33cde5', fontSize: 12, cursor: 'pointer' }}
onClick={this.changeAdvancedQuery}
>
<span style={{ marginRight: 5, lineHeight: '28px' }}>
{locale.advancedQuery9}
</span>
<Icon
type={
this.state.isAdvancedQuery ? 'arrow-up-filling' : 'arrow-down-filling'
}
size={'xs'}
/>
</div>
</Form.Item>
<Form.Item label={''}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.exportData.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsExport'}
>
{locale.export}
</Button>
</Form.Item>
<Form.Item label={''}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.importData.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsExport'}
>
{locale.import}
</Button>
</Form.Item>
<br />
<Form.Item
style={this.inApp ? { display: 'none' } : {}}
label={locale.application0}
>
<Input
htmlType={'text'}
placeholder={locale.app1}
style={{ width: 200 }}
value={this.state.appName}
onChange={this.setAppName.bind(this)}
/>
</Form.Item>
<Form.Item label={locale.tags}>
<Select
style={{ width: 200 }}
size={'medium'}
hasArrow
mode="tag"
filterLocal={false}
placeholder={locale.pleaseEnterTag}
dataSource={this.state.tagLst}
value={this.state.config_tags}
onChange={this.setConfigTags.bind(this)}
hasClear
/>
</Form.Item>
</Form>
<div style={{ position: 'absolute', right: 10, top: 4 }}>
<Icon
type={'add'}
size={'medium'}
style={{
color: 'black',
marginRight: 0,
verticalAlign: 'middle',
cursor: 'pointer',
backgroundColor: '#eee',
border: '1px solid #ddd',
padding: '3px 6px',
}}
onClick={this.chooseEnv.bind(this)}
/>
</div>
</div>
<div>
<Table
dataSource={this.state.dataSource}
locale={{ empty: locale.pubNoData }}
fixedHeader
maxBodyHeight={400}
ref={'dataTable'}
rowSelection={this.state.rowSelection}
>
<Table.Column title={'Data Id'} dataIndex={'dataId'} />
<Table.Column title={'Group'} dataIndex={'group'} />
{!this.inApp ? (
<Table.Column title={locale.application} dataIndex={'appName'} />
) : (
<div />
)}
<Table.Column title={locale.operation} cell={this.renderCol.bind(this)} />
</Table>
{this.state.dataSource.length > 0 && (
<div style={{ marginTop: 10, overflow: 'hidden' }}>
<div style={{ float: 'left' }}>
<Button
type={'primary'}
warning
style={{ marginRight: 10 }}
onClick={this.multipleSelectionDeletion.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsDelete'}
>
{locale.deleteAction}
</Button>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.exportSelectedData.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsExport'}
>
{locale.exportSelected}
</Button>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.cloneSelectedDataConfirm.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsClone'}
>
{locale.clone}
</Button>
</div>
<div style={{ float: 'right' }}>
<Pagination
style={{ float: 'right' }}
pageSizeList={[10, 20, 30]}
pageSizeSelector={'dropdown'}
onPageSizeChange={this.handlePageSizeChange.bind(this)}
current={this.state.currentPage}
total={this.state.total}
pageSize={this.state.pageSize}
onChange={this.changePage.bind(this)}
/>
</div>
</div>
)}
</div>
<ShowCodeing ref={this.showcode} />
<DeleteDialog ref={this.deleteDialog} />
<div className={this.state.hasdash ? 'dash-page-container' : ''}>
<div
className={this.state.hasdash ? 'dash-left-container' : ''}
style={{ position: 'relative' }}
>
<div style={{ display: this.inApp ? 'none' : 'block', marginTop: -15 }}>
<RegionGroup
namespaceCallBack={this.cleanAndGetData.bind(this)}
setNowNameSpace={this.setNowNameSpace.bind(this)}
/>
</div>
{this.state.hasdash && (
<div
className={'dash-right-container'}
style={{ overflow: 'auto', height: window.innerHeight - 40 }}
<div
style={{
display: this.inApp ? 'none' : 'block',
position: 'relative',
width: '100%',
overflow: 'hidden',
height: '40px',
}}
>
<h3
style={{
height: 30,
width: '100%',
lineHeight: '30px',
padding: 0,
margin: 0,
paddingLeft: 10,
borderLeft: '3px solid #09c',
color: '#ccc',
fontSize: '12px',
}}
>
{this.state.contentList.map((v, i) => (
<DashboardCard data={v} height={'auto'} key={`show${i}`} />
))}
<span style={{ fontSize: '14px', color: '#000', marginRight: 8 }}>
{locale.configurationManagement8}
</span>
<span style={{ fontSize: '14px', color: '#000', marginRight: 8 }}>|</span>
<span style={{ fontSize: '14px', color: '#000', marginRight: 8 }}>
{this.state.nownamespace_name}
</span>
<span style={{ fontSize: '14px', color: '#000', marginRight: 18 }}>
{this.state.nownamespace_id}
</span>
{locale.queryResults}
<strong style={{ fontWeight: 'bold' }}> {configurations.totalCount} </strong>
{locale.articleMeetRequirements}
</h3>
<div
style={{ position: 'absolute', textAlign: 'right', zIndex: 2, right: 0, top: 0 }}
/>
</div>
<div
style={{
position: 'relative',
marginTop: 10,
height: this.state.isAdvancedQuery ? 'auto' : 42,
overflow: 'hidden',
}}
>
<Form inline>
<Form.Item label="Data ID:">
<Input
htmlType="text"
placeholder={locale.fuzzyd}
style={{ width: 200 }}
onChange={dataId => {
this.dataId = dataId;
this.setState({ dataId });
}}
onPressEnter={() => this.getData()}
/>
</Form.Item>
<Form.Item label="Group:">
<Select.AutoComplete
style={{ width: 200 }}
size={'medium'}
placeholder={locale.fuzzyg}
dataSource={this.state.groups}
value={this.state.group}
onChange={this.setGroup.bind(this)}
onPressEnter={() => this.getData()}
hasClear
/>
</Form.Item>
<Form.Item label={''}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.selectAll.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=dashsearch'}
>
{locale.query}
</Button>
</Form.Item>
<Form.Item
style={
this.inApp
? { display: 'none' }
: { verticalAlign: 'middle', marginTop: 0, marginLeft: 10 }
}
>
<div
style={{ color: '#33cde5', fontSize: 12, cursor: 'pointer' }}
onClick={this.changeAdvancedQuery}
>
<span style={{ marginRight: 5, lineHeight: '28px' }}>
{locale.advancedQuery9}
</span>
<Icon
type={this.state.isAdvancedQuery ? 'arrow-up-filling' : 'arrow-down-filling'}
size={'xs'}
/>
</div>
</Form.Item>
<Form.Item label={''}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.exportData.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsExport'}
>
{locale.export}
</Button>
</Form.Item>
<Form.Item label={''}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
onClick={this.importData.bind(this)}
data-spm-click={'gostr=/aliyun;locaid=configsExport'}
>
{locale.import}
</Button>
</Form.Item>
<br />
<Form.Item
style={this.inApp ? { display: 'none' } : {}}
label={locale.application0}
>
<Input
htmlType={'text'}
placeholder={locale.app1}
style={{ width: 200 }}
value={this.state.appName}
onChange={this.setAppName.bind(this)}
onPressEnter={() => this.getData()}
/>
</Form.Item>
<Form.Item label={locale.tags}>
<Select
style={{ width: 200 }}
size="medium"
hasArrow
mode="tag"
placeholder={locale.pleaseEnterTag}
dataSource={this.state.tagLst}
value={this.state.config_tags}
onChange={this.setConfigTags.bind(this)}
showSearch
onSearch={val => {
const { tagLst } = this.state;
if (!tagLst.includes(val)) {
this.setState({ tagLst: tagLst.concat(val) });
}
}}
hasClear
/>
</Form.Item>
</Form>
<div style={{ position: 'absolute', right: 10, top: 4 }}>
<Icon
type="add"
size="medium"
style={{
color: 'black',
marginRight: 0,
verticalAlign: 'middle',
cursor: 'pointer',
backgroundColor: '#eee',
border: '1px solid #ddd',
padding: '3px 6px',
}}
onClick={this.chooseEnv.bind(this)}
/>
</div>
</div>
<Table
className="configuration-table"
dataSource={configurations.pageItems}
locale={{ empty: locale.pubNoData }}
ref="dataTable"
loading={this.state.loading}
rowSelection={this.state.rowSelection}
>
<Table.Column title={'Data Id'} dataIndex={'dataId'} />
<Table.Column title={'Group'} dataIndex={'group'} />
{!this.inApp && <Table.Column title={locale.application} dataIndex="appName" />}
<Table.Column title={locale.operation} cell={this.renderCol.bind(this)} />
</Table>
{configurations.totalCount > 0 && (
<>
<div style={{ float: 'left' }}>
{[
{
warning: true,
text: locale.deleteAction,
locaid: 'configsDelete',
onClick: () => this.multipleSelectionDeletion(),
},
{
text: locale.exportSelected,
locaid: 'configsExport',
onClick: () => this.exportSelectedData(),
},
{
text: locale.clone,
locaid: 'configsDelete',
onClick: () => this.cloneSelectedDataConfirm(),
},
].map(item => (
<Button
warning={item.warning}
type="primary"
style={{ marginRight: 10 }}
onClick={item.onClick}
data-spm-click={`gostr=/aliyun;locaid=${item.locaid}`}
>
{item.text}
</Button>
))}
</div>
<Pagination
style={{ float: 'right' }}
pageSizeList={[10, 20, 30]}
pageSizePosition="start"
pageSizeSelector="dropdown"
popupProps={{ align: 'bl tl' }}
onPageSizeChange={val => this.handlePageSizeChange(val)}
current={configurations.pageNumber}
total={configurations.totalCount}
pageSize={this.state.pageSize}
onChange={this.changePage.bind(this)}
/>
</>
)}
<ShowCodeing ref={this.showcode} />
<DeleteDialog ref={this.deleteDialog} />
</div>
</Loading>
</div>
{this.state.hasdash && (
<div className="dash-right-container">
{this.state.contentList.map((v, i) => (
<DashboardCard data={v} height={'auto'} key={`show${i}`} />
))}
</div>
)}
</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

@ -11,9 +11,9 @@
* limitations under the License.
*/
.next-pagination-size-selector{
.next-pagination-size-selector {
position: static !important;
}
.next-overlay-inner{
top:154px !important;
.configuration-table {
margin-bottom: 20px;
}

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

@ -15,7 +15,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { ConfigProvider, Field, Form, Input, Loading, Pagination, Table } from '@alifd/next';
import RegionGroup from 'components/RegionGroup';
import { getParams, setParams, request, aliwareIntl } from '@/globalLib';
import { getParams, setParams, request } from '@/globalLib';
import './index.scss';
@ -83,31 +83,6 @@ class HistoryRollback extends React.Component {
});
}
/**
* 回车事件
*/
keyDownSearch(e) {
const theEvent = e || window.event;
const code = theEvent.keyCode || theEvent.which || theEvent.charCode;
if (code === 13) {
this.getData();
return false;
}
return true;
}
UNSAFE_componentWillMount() {
window.addEventListener('keydown', this.keyDownSearch.bind(this), false);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.keyDownSearch.bind(this));
}
onSearch() {}
onChange() {}
cleanAndGetData(needclean = false) {
if (needclean) {
this.dataId = '';
@ -133,9 +108,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({
@ -151,8 +124,6 @@ class HistoryRollback extends React.Component {
});
}
showMore() {}
renderCol(value, index, record) {
const { locale = {} } = this.props;
return (
@ -175,60 +146,12 @@ class HistoryRollback extends React.Component {
this.getData(value);
}
onInputUpdate() {}
chooseFieldChange(fieldValue) {
this.setState({
fieldValue,
});
}
showSelect(value) {
this.setState({
selectValue: value,
});
if (value.indexOf('appName') !== -1) {
this.setState({
showAppName: true,
});
} else {
this.setState({
showAppName: false,
});
}
if (value.indexOf('group') !== -1) {
this.setState({
showgroup: true,
});
} else {
this.setState({
showgroup: false,
});
}
this.chooseFieldChange(value);
}
getAppName(value) {
this.appName = value;
this.setState({
appName: value,
});
}
getDataId(value) {
this.dataId = value;
this.setState({
dataId: value,
});
}
getGroup(value) {
this.group = value;
this.setState({
group: value,
});
}
selectAll() {
this.dataId = this.field.getValue('dataId');
this.group = this.field.getValue('group');
@ -265,10 +188,6 @@ class HistoryRollback extends React.Component {
chooseEnv(value) {}
renderLastTime(value, index, record) {
return aliwareIntl.intlTimeFormat(record.lastModifiedTime);
}
goDetail(record) {
this.serverId = getParams('serverId') || 'center';
this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数
@ -373,8 +292,18 @@ class HistoryRollback extends React.Component {
<Table.Column title="Group" dataIndex="group" />
<Table.Column
title={locale.lastUpdateTime}
dataIndex="time"
cell={this.renderLastTime.bind(this)}
dataIndex="lastModifiedTime"
cell={val => {
if (!val) {
return '';
}
try {
const date = new Date(val);
return date.toLocaleString();
} catch (e) {
return '';
}
}}
/>
<Table.Column title={locale.operation} cell={this.renderCol.bind(this)} />
</Table>

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,
@ -144,6 +145,13 @@ class NewConfig extends React.Component {
}
}
tagSearch(value) {
const { tagLst } = this.state;
if (!tagLst.includes(value)) {
this.setState({ tagLst: [value, ...tagLst] });
}
}
setConfigTags(value) {
if (value.length > 5) {
value.pop();
@ -154,6 +162,7 @@ class NewConfig extends React.Component {
}
});
this.setState({
tagLst: value,
config_tags: value,
});
}
@ -192,9 +201,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 +343,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();
},
});
};
@ -493,6 +505,7 @@ class NewConfig extends React.Component {
>
<Select
size={'medium'}
showSearch
hasArrow
style={{ width: '100%', height: '100%!important' }}
autoWidth
@ -503,6 +516,7 @@ class NewConfig extends React.Component {
dataSource={this.state.tagLst}
value={this.state.config_tags}
onChange={this.setConfigTags.bind(this)}
onSearch={val => this.tagSearch(val)}
hasClear
/>
</FormItem>

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

@ -1,178 +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.
*/
import React from 'react';
import PropTypes from 'prop-types';
import RegionGroup from 'components/RegionGroup';
import { ConfigProvider, Input, Field, Form, Message } from '@alifd/next';
import { getParams, setParams, request } from '../../globalLib';
import './index.scss';
const FormItem = Form.Item;
@ConfigProvider.config
class Password extends React.Component {
static displayName = 'Password';
static propTypes = {
locale: PropTypes.object,
};
constructor(props) {
super(props);
this.field = new Field(this);
this.state = {};
}
componentDidMount() {}
validatePassword(rule, value, callback) {
const { locale = {} } = this.props;
if (this.field.getValue('newPassword') !== this.field.getValue('confirmNewPassword')) {
callback(locale.passwordNotConsistent);
} else {
callback();
}
}
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: () => {
Message.error({
content: locale.invalidUsernameOrPassword,
});
},
});
});
};
changePassword() {
const { locale = {} } = this.props;
this.field.validate((errors, values) => {
if (errors) {
return;
}
request({
type: 'put',
url: 'v1/auth/password',
data: values,
success: ({ code, data }) => {
if (code === 200) {
window.localStorage.clear();
this.props.history.push('/login');
}
if (code === 401) {
Message.error({
content: locale.invalidPassword,
});
}
},
error: () => {
Message.error({
content: locale.invalidPassword,
});
},
});
});
}
render() {
const { locale = {} } = this.props;
const formItemLayout = {
labelCol: { fixedSpan: 6 },
wrapperCol: { span: 18 },
};
return (
<div style={{ padding: 10 }}>
<RegionGroup left={locale.changePassword} />
<Form style={{ width: '300px' }} field={this.field}>
<FormItem label={locale.oldPassword} required {...formItemLayout}>
<Input
htmlType="password"
placeholder={locale.pleaseInputOldPassword}
{...this.field.init('oldPassword', {
rules: [
{
required: true,
message: locale.passwordRequired,
},
],
})}
disabled={this.state.type === 0}
/>
</FormItem>
<FormItem label={locale.newPassword} required {...formItemLayout}>
<Input
htmlType="password"
placeholder={locale.pleaseInputNewPassword}
{...this.field.init('newPassword', {
rules: [
{
required: true,
message: locale.passwordRequired,
},
],
})}
disabled={this.state.type === 0}
/>
</FormItem>
<FormItem label={locale.checkPassword} required {...formItemLayout}>
<Input
htmlType="password"
placeholder={locale.pleaseInputNewPasswordAgain}
{...this.field.init('confirmNewPassword', {
rules: [
{
required: true,
message: locale.passwordRequired,
},
{ validator: this.validatePassword.bind(this) },
],
})}
disabled={this.state.type === 0}
/>
</FormItem>
<FormItem label=" " {...formItemLayout}>
<Form.Submit type="primary" onClick={this.changePassword.bind(this)}>
{locale.changePassword}
</Form.Submit>
</FormItem>
</Form>
</div>
);
}
}
export default Password;

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

@ -0,0 +1,35 @@
/*
* 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_CONFIGURATION } from '../constants';
const initialState = {
configurations: [],
};
const getConfigs = params => dispatch =>
request
.get('v1/cs/configs', { params })
.then(data => dispatch({ type: GET_CONFIGURATION, data }));
export default (state = initialState, action) => {
switch (action.type) {
case GET_CONFIGURATION:
return { ...state, configurations: action.data };
default:
return state;
}
};
export { getConfigs };

View File

@ -14,5 +14,8 @@
import locale from './locale';
import base from './base';
import subscribers from './subscribers';
import authority from './authority';
import namespace from './namespace';
import configuration from './configuration';
export default { locale, base, subscribers };
export default { locale, base, subscribers, authority, namespace, configuration };

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,12 +1,52 @@
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!';
function goLogin() {
const url = window.location.href;
localStorage.removeItem('token');
const base_url = url.split('#')[0];
window.location.href = `${base_url}#/login`;
}
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')) {
let token = {};
try {
token = JSON.parse(localStorage.token);
} catch (e) {
console.log(e);
goLogin();
}
const { accessToken = '' } = 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 +58,24 @@ 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)
) {
goLogin();
}
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</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

@ -43,6 +43,14 @@ server.port=8848
# nacos.naming.expireInstance=true
### If enable the empty service auto clean, services with an empty instance are automatically cleared
nacos.naming.empty-service.auto-clean=false
### The empty service cleanup task delays startup time in milliseconds
nacos.naming.empty-service.clean.initial-delay-ms=60000
### The empty service cleanup task cycle execution time in milliseconds
nacos.naming.empty-service.clean.period-time-ms=20000
#*************** CMDB Module Related Configurations ***************#
### The interval to dump external CMDB in seconds:
# nacos.cmdb.dumpTaskInterval=3600
@ -105,7 +113,7 @@ nacos.core.auth.default.token.expire.seconds=18000
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay.
nacos.core.auth.caching.enabled=true
nacos.core.auth.caching.enabled=false
#*************** Istio Related Configurations ***************#

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</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -206,6 +206,7 @@
<descriptors>
<descriptor>release-nacos.xml</descriptor>
</descriptors>
<tarLongFileMode>posix</tarLongFileMode>
</configuration>
<executions>
<execution>

Some files were not shown because too many files have changed in this diff Show More