Browse Source

Add rule configuration support for parameter flow control in Sentinel Dashboard (#176)

- Update dashboard API client to support authority and parameter flow commands
- Add REST API controller for parameter flow rules in dashboard
- Add pages for configuring parameter flow rules
- Update dependencies: upgrade Spring Boot to 2.x and remove unused dependencies
- Other refinements

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
master
Eric Zhao GitHub 6 years ago
parent
commit
b06bb02b2c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1768 additions and 122 deletions
  1. +11
    -19
      sentinel-dashboard/pom.xml
  2. +2
    -10
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java
  3. +28
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandNotFoundException.java
  4. +181
    -44
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java
  5. +114
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java
  6. +106
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/AbstractRuleEntity.java
  7. +51
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java
  8. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java
  9. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java
  10. +76
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java
  11. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java
  12. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java
  13. +6
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/AppInfo.java
  14. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemDegradeRuleStore.java
  15. +2
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java
  16. +36
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemParamFlowRuleStore.java
  17. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemSystemRuleStore.java
  18. +3
    -4
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemoryRuleRepositoryAdapter.java
  19. +97
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/RuleUtils.java
  20. +70
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/VersionUtils.java
  21. +37
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/AuthorityRuleController.java
  22. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/DegradeController.java
  23. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowController.java
  24. +246
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java
  25. +37
    -10
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/Result.java
  26. +1
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/SystemController.java
  27. +16
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js
  28. +344
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js
  29. +7
    -9
      sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
  30. +42
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/services/param_flow_service.js
  31. +1
    -1
      sentinel-dashboard/src/main/webapp/resources/app/views/dashboard/home.html
  32. +119
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html
  33. +111
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html
  34. +1
    -1
      sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
  35. +1
    -1
      sentinel-dashboard/src/main/webapp/resources/dist/js/app.vendor.js
  36. +1
    -0
      sentinel-dashboard/src/main/webapp/resources/gulpfile.js
  37. +13
    -13
      sentinel-dashboard/src/main/webapp/resources/package-lock.json

+ 11
- 19
sentinel-dashboard/pom.xml View File

@@ -13,8 +13,9 @@
<packaging>jar</packaging>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.boot.version>2.0.5.RELEASE</spring.boot.version>
</properties>

<dependencies>
@@ -35,39 +36,30 @@
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.9.RELEASE</version>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>1.5.9.RELEASE</version>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>1.5.9.RELEASE</version>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.9.RELEASE</version>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>

@@ -117,7 +109,7 @@
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<mainClass>com.taobao.csp.sentinel.dashboard.Application</mainClass>
<mainClass>com.taobao.csp.sentinel.dashboard.DashboardApplication</mainClass>
</configuration>
<executions>
<execution>
@@ -132,8 +124,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>



sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/Application.java → sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java View File

@@ -17,19 +17,11 @@ package com.taobao.csp.sentinel.dashboard;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public class DashboardApplication {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
SpringApplication.run(DashboardApplication.class, args);
}

}

+ 28
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandNotFoundException.java View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.client;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public class CommandNotFoundException extends Exception {
public CommandNotFoundException() { }

public CommandNotFoundException(String message) {
super(message);
}
}

+ 181
- 44
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java View File

@@ -16,9 +16,13 @@
package com.taobao.csp.sentinel.dashboard.client;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -29,13 +33,19 @@ import com.alibaba.csp.sentinel.command.vo.NodeVo;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;

import com.taobao.csp.sentinel.dashboard.datasource.entity.DegradeRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.SystemRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import com.taobao.csp.sentinel.dashboard.util.RuleUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultRedirectStrategy;
@@ -56,17 +66,24 @@ import org.springframework.stereotype.Component;
public class SentinelApiClient {

private static Logger logger = LoggerFactory.getLogger(SentinelApiClient.class);
private static final Charset defaultCharset = Charset.forName(SentinelConfig.charset());

private static final Charset DEFAULT_CHARSET = Charset.forName(SentinelConfig.charset());

private static final String RESOURCE_URL_PATH = "jsonTree";
private static final String CLUSTER_NODE_PATH = "clusterNode";
private static final String GET_RULES_PATH = "getRules";
private static final String SET_RULES_PATH = "setRules";
private static final String GET_PARAM_RULE_PATH = "getParamFlowRules";
private static final String SET_PARAM_RULE_PATH = "setParamFlowRules";

private static final String FLOW_RULE_TYPE = "flow";
private static final String DEGRADE_RULE_TYPE = "degrade";
private static final String SYSTEM_RULE_TYPE = "system";
private static final String AUTHORITY_TYPE = "authority";

private CloseableHttpAsyncClient httpClient;

private final String resourceUrlPath = "jsonTree";
private final String clusterNodePath = "clusterNode";
private final String getRulesPath = "getRules";
private final String setRulesPath = "setRules";
private final String flowRuleType = "flow";
private final String degradeRuleType = "degrade";
private final String systemRuleType = "system";
private final boolean enableHttps = false;

public SentinelApiClient() {
IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(3000)
@@ -81,7 +98,7 @@ public class SentinelApiClient {
}

public List<NodeVo> fetchResourceOfMachine(String ip, int port, String type) {
String url = "http://" + ip + ":" + port + "/" + resourceUrlPath + "?type=" + type;
String url = "http://" + ip + ":" + port + "/" + RESOURCE_URL_PATH + "?type=" + type;
String body = httpGetContent(url);
if (body == null) {
return null;
@@ -107,7 +124,7 @@ public class SentinelApiClient {
if (includeZero) {
type = "zero";
}
String url = "http://" + ip + ":" + port + "/" + clusterNodePath + "?type=" + type;
String url = "http://" + ip + ":" + port + "/" + CLUSTER_NODE_PATH + "?type=" + type;
String body = httpGetContent(url);
if (body == null) {
return null;
@@ -121,10 +138,10 @@ public class SentinelApiClient {
}

public List<FlowRuleEntity> fetchFlowRuleOfMachine(String app, String ip, int port) {
String url = "http://" + ip + ":" + port + "/" + getRulesPath + "?type=" + flowRuleType;
String url = "http://" + ip + ":" + port + "/" + GET_RULES_PATH + "?type=" + FLOW_RULE_TYPE;
String body = httpGetContent(url);
logger.info("FlowRule Body:{}", body);
List<FlowRule> rules = parseFlowRule(body);
List<FlowRule> rules = RuleUtils.parseFlowRule(body);
if (rules != null) {
return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule))
.collect(Collectors.toList());
@@ -134,10 +151,10 @@ public class SentinelApiClient {
}

public List<DegradeRuleEntity> fetchDegradeRuleOfMachine(String app, String ip, int port) {
String url = "http://" + ip + ":" + port + "/" + getRulesPath + "?type=" + degradeRuleType;
String url = "http://" + ip + ":" + port + "/" + GET_RULES_PATH + "?type=" + DEGRADE_RULE_TYPE;
String body = httpGetContent(url);
logger.info("Degrade Body:{}", body);
List<DegradeRule> rules = parseDegradeRule(body);
List<DegradeRule> rules = RuleUtils.parseDegradeRule(body);
if (rules != null) {
return rules.stream().map(rule -> DegradeRuleEntity.fromDegradeRule(app, ip, port, rule))
.collect(Collectors.toList());
@@ -147,10 +164,10 @@ public class SentinelApiClient {
}

public List<SystemRuleEntity> fetchSystemRuleOfMachine(String app, String ip, int port) {
String url = "http://" + ip + ":" + port + "/" + getRulesPath + "?type=" + systemRuleType;
String url = "http://" + ip + ":" + port + "/" + GET_RULES_PATH + "?type=" + SYSTEM_RULE_TYPE;
String body = httpGetContent(url);
logger.info("SystemRule Body:{}", body);
List<SystemRule> rules = parseSystemRule(body);
List<SystemRule> rules = RuleUtils.parseSystemRule(body);
if (rules != null) {
return rules.stream().map(rule -> SystemRuleEntity.fromSystemRule(app, ip, port, rule))
.collect(Collectors.toList());
@@ -159,6 +176,68 @@ public class SentinelApiClient {
}
}

/**
* Fetch all parameter flow rules from provided machine.
*
* @param app application name
* @param ip machine client IP
* @param port machine client port
* @return all retrieved parameter flow rules
* @since 0.2.1
*/
public CompletableFuture<List<ParamFlowRuleEntity>> fetchParamFlowRulesOfMachine(String app, String ip, int port) {
try {
AssertUtil.notEmpty(app, "Bad app name");
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
URIBuilder uriBuilder = new URIBuilder();
String commandName = GET_PARAM_RULE_PATH;
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(commandName);
return executeCommand(commandName, uriBuilder.build())
.thenApply(RuleUtils::parseParamFlowRule)
.thenApply(rules -> rules.stream()
.map(e -> ParamFlowRuleEntity.fromAuthorityRule(app, ip, port, e))
.collect(Collectors.toList())
);
} catch (Exception e) {
logger.error("Error when fetching parameter flow rules", e);
return newFailedFuture(e);
}
}

/**
* Fetch all authority rules from provided machine.
*
* @param app application name
* @param ip machine client IP
* @param port machine client port
* @return all retrieved authority rules
* @since 0.2.1
*/
public List<AuthorityRuleEntity> fetchAuthorityRulesOfMachine(String app, String ip, int port) {
AssertUtil.notEmpty(app, "Bad app name");
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(GET_RULES_PATH)
.setParameter("type", AUTHORITY_TYPE);
try {
String body = httpGetContent(uriBuilder.build().toString());
return Optional.ofNullable(body)
.map(RuleUtils::parseAuthorityRule)
.map(rules -> rules.stream()
.map(e -> AuthorityRuleEntity.fromAuthorityRule(app, ip, port, e))
.collect(Collectors.toList())
)
.orElse(null);
} catch (URISyntaxException e) {
logger.error("Error when fetching authority rules", e);
return null;
}
}

/**
* set rules of the machine. rules == null will return immediately;
* rules.isEmpty() means setting the rules to empty.
@@ -178,12 +257,12 @@ public class SentinelApiClient {
}
String data = JSON.toJSONString(rules.stream().map(FlowRuleEntity::toFlowRule).collect(Collectors.toList()));
try {
data = URLEncoder.encode(data, defaultCharset.name());
data = URLEncoder.encode(data, DEFAULT_CHARSET.name());
} catch (UnsupportedEncodingException e) {
logger.info("encode rule error", e);
return false;
}
String url = "http://" + ip + ":" + port + "/" + setRulesPath + "?type=" + flowRuleType + "&data=" + data;
String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + FLOW_RULE_TYPE + "&data=" + data;
String result = httpGetContent(url);
logger.info("setFlowRule: " + result);
return true;
@@ -209,12 +288,13 @@ public class SentinelApiClient {
String data = JSON.toJSONString(
rules.stream().map(DegradeRuleEntity::toDegradeRule).collect(Collectors.toList()));
try {
data = URLEncoder.encode(data, defaultCharset.name());
data = URLEncoder.encode(data, DEFAULT_CHARSET.name());
} catch (UnsupportedEncodingException e) {
logger.info("encode rule error", e);
return false;
}
String url = "http://" + ip + ":" + port + "/" + setRulesPath + "?type=" + degradeRuleType + "&data=" + data;
String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + DEGRADE_RULE_TYPE + "&data="
+ data;
String result = httpGetContent(url);
logger.info("setDegradeRule: " + result);
return true;
@@ -240,42 +320,93 @@ public class SentinelApiClient {
String data = JSON.toJSONString(
rules.stream().map(SystemRuleEntity::toSystemRule).collect(Collectors.toList()));
try {
data = URLEncoder.encode(data, defaultCharset.name());
data = URLEncoder.encode(data, DEFAULT_CHARSET.name());
} catch (UnsupportedEncodingException e) {
logger.info("encode rule error", e);
return false;
}
String url = "http://" + ip + ":" + port + "/" + setRulesPath + "?type=" + systemRuleType + "&data=" + data;
String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + SYSTEM_RULE_TYPE + "&data=" + data;
String result = httpGetContent(url);
logger.info("setSystemRule: " + result);
return true;
}

private List<FlowRule> parseFlowRule(String body) {
public CompletableFuture<Void> setParamFlowRuleOfMachine(String app, String ip, int port, List<ParamFlowRuleEntity> rules) {
if (rules == null) {
return CompletableFuture.completedFuture(null);
}
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
return JSON.parseArray(body, FlowRule.class);
} catch (Exception e) {
logger.info("parser FlowRule error: ", e);
return null;
String data = JSON.toJSONString(
rules.stream().map(ParamFlowRuleEntity::getRule).collect(Collectors.toList())
);
data = URLEncoder.encode(data, DEFAULT_CHARSET.name());
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(SET_PARAM_RULE_PATH)
.setParameter("data", data);
return executeCommand(SET_PARAM_RULE_PATH, uriBuilder.build())
.thenCompose(e -> {
if ("success".equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Push parameter flow rules to client failed: " + e);
return newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when setting parameter flow rule", ex);
return newFailedFuture(ex);
}
}

private List<DegradeRule> parseDegradeRule(String body) {
try {
return JSON.parseArray(body, DegradeRule.class);
} catch (Exception e) {
logger.info("parser DegradeRule error: ", e);
return null;
}
private boolean isSuccess(int statusCode) {
return statusCode >= 200 && statusCode < 300;
}

private List<SystemRule> parseSystemRule(String body) {
try {
return JSON.parseArray(body, SystemRule.class);
} catch (Exception e) {
logger.info("parser SystemRule error: ", e);
return null;
private CompletableFuture<String> executeCommand(String command, URI uri) {
CompletableFuture<String> future = new CompletableFuture<>();
if (StringUtil.isBlank(command) || uri == null) {
future.completeExceptionally(new IllegalArgumentException("Bad URL or command name"));
return future;
}
final HttpGet httpGet = new HttpGet(uri);
httpClient.execute(httpGet, new FutureCallback<HttpResponse>() {
@Override
public void completed(final HttpResponse response) {
int statusCode = response.getStatusLine().getStatusCode();
try {
String value = getBody(response);
if (isSuccess(statusCode)) {
future.complete(value);
} else {
if (statusCode == 400) {
future.completeExceptionally(new CommandNotFoundException(command));
} else {
future.completeExceptionally(new IllegalStateException(value));
}
}

} catch (Exception ex) {
future.completeExceptionally(ex);
logger.error("HTTP request failed: " + uri.toString(), ex);
}
}

@Override
public void failed(final Exception ex) {
future.completeExceptionally(ex);
logger.error("HTTP request failed: " + uri.toString(), ex);
}

@Override
public void cancelled() {
future.complete(null);
}
});
return future;
}

private String httpGetContent(String url) {
@@ -321,10 +452,16 @@ public class SentinelApiClient {
charset = contentType.getCharset();
} catch (Exception ignore) {
}
return EntityUtils.toString(response.getEntity(), charset != null ? charset : defaultCharset);
return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET);
}

public void close() throws Exception {
httpClient.close();
}

private <R> CompletableFuture<R> newFailedFuture(Throwable ex) {
CompletableFuture<R> future = new CompletableFuture<>();
future.completeExceptionally(ex);
return future;
}
}

+ 114
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java View File

@@ -0,0 +1,114 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public class SentinelVersion {

private int majorVersion;
private int minorVersion;
private int fixVersion;
private String postfix;

public int getMajorVersion() {
return majorVersion;
}

public SentinelVersion setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
return this;
}

public int getMinorVersion() {
return minorVersion;
}

public SentinelVersion setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
return this;
}

public int getFixVersion() {
return fixVersion;
}

public SentinelVersion setFixVersion(int fixVersion) {
this.fixVersion = fixVersion;
return this;
}

public String getPostfix() {
return postfix;
}

public SentinelVersion setPostfix(String postfix) {
this.postfix = postfix;
return this;
}

public boolean greaterThan(SentinelVersion version) {
if (version == null) {
return true;
}
return this.majorVersion > version.majorVersion
|| this.minorVersion > version.minorVersion
|| this.fixVersion > version.fixVersion;
}

public boolean greaterOrEqual(SentinelVersion version) {
if (version == null) {
return true;
}
return this.majorVersion >= version.majorVersion
|| this.minorVersion >= version.minorVersion
|| this.fixVersion >= version.fixVersion;
}

@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }

SentinelVersion that = (SentinelVersion)o;

if (majorVersion != that.majorVersion) { return false; }
if (minorVersion != that.minorVersion) { return false; }
if (fixVersion != that.fixVersion) { return false; }
return postfix != null ? postfix.equals(that.postfix) : that.postfix == null;
}

@Override
public int hashCode() {
int result = majorVersion;
result = 31 * result + minorVersion;
result = 31 * result + fixVersion;
result = 31 * result + (postfix != null ? postfix.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "SentinelVersion{" +
"majorVersion=" + majorVersion +
", minorVersion=" + minorVersion +
", fixVersion=" + fixVersion +
", postfix='" + postfix + '\'' +
'}';
}
}

+ 106
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/AbstractRuleEntity.java View File

@@ -0,0 +1,106 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.Date;

import com.alibaba.csp.sentinel.slots.block.AbstractRule;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public abstract class AbstractRuleEntity<T extends AbstractRule> implements RuleEntity {

protected Long id;

protected String app;
protected String ip;
protected Integer port;

protected T rule;

private Date gmtCreate;
private Date gmtModified;

@Override
public Long getId() {
return id;
}

@Override
public void setId(Long id) {
this.id = id;
}

@Override
public String getApp() {
return app;
}

public AbstractRuleEntity<T> setApp(String app) {
this.app = app;
return this;
}

@Override
public String getIp() {
return ip;
}

public AbstractRuleEntity<T> setIp(String ip) {
this.ip = ip;
return this;
}

@Override
public Integer getPort() {
return port;
}

public AbstractRuleEntity<T> setPort(Integer port) {
this.port = port;
return this;
}

public T getRule() {
return rule;
}

public AbstractRuleEntity<T> setRule(T rule) {
this.rule = rule;
return this;
}

@Override
public Date getGmtCreate() {
return gmtCreate;
}

public AbstractRuleEntity<T> setGmtCreate(Date gmtCreate) {
this.gmtCreate = gmtCreate;
return this;
}

public Date getGmtModified() {
return gmtModified;
}

public AbstractRuleEntity<T> setGmtModified(Date gmtModified) {
this.gmtModified = gmtModified;
return this;
}
}

+ 51
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.util.AssertUtil;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public class AuthorityRuleEntity extends AbstractRuleEntity<AuthorityRule> {

public AuthorityRuleEntity(AuthorityRule authorityRule) {
AssertUtil.notNull(authorityRule, "Authority rule should not be null");
this.rule = authorityRule;
}

public static AuthorityRuleEntity fromAuthorityRule(String app, String ip, Integer port, AuthorityRule rule) {
AuthorityRuleEntity entity = new AuthorityRuleEntity(rule);
entity.setApp(app);
entity.setIp(ip);
entity.setPort(port);
return entity;
}

public String getLimitApp() {
return rule.getLimitApp();
}

public String getResource() {
return rule.getResource();
}

public int getStrategy() {
return rule.getStrategy();
}
}

sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/DegradeRuleEntity.java → sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity;
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.Date;


sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/FlowRuleEntity.java → sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity;
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.Date;


+ 76
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java View File

@@ -0,0 +1,76 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.List;

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.util.AssertUtil;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public class ParamFlowRuleEntity extends AbstractRuleEntity<ParamFlowRule> {

public ParamFlowRuleEntity() {}

public ParamFlowRuleEntity(ParamFlowRule rule) {
AssertUtil.notNull(rule, "Authority rule should not be null");
this.rule = rule;
}

public static ParamFlowRuleEntity fromAuthorityRule(String app, String ip, Integer port, ParamFlowRule rule) {
ParamFlowRuleEntity entity = new ParamFlowRuleEntity(rule);
entity.setApp(app);
entity.setIp(ip);
entity.setPort(port);
return entity;
}

@JsonIgnore
public String getLimitApp() {
return rule.getLimitApp();
}

@JsonIgnore
public String getResource() {
return rule.getResource();
}

@JsonIgnore
public int getBlockGrade() {
return rule.getBlockGrade();
}

@JsonIgnore
public Integer getParamIdx() {
return rule.getParamIdx();
}

@JsonIgnore
public double getCount() {
return rule.getCount();
}

@JsonIgnore
public List<ParamFlowItem> getParamFlowItemList() {
return rule.getParamFlowItemList();
}
}

sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/RuleEntity.java → sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity;
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.Date;


sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/SystemRuleEntity.java → sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.datasource.entity;
package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.Date;


+ 6
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/AppInfo.java View File

@@ -15,6 +15,7 @@
*/
package com.taobao.csp.sentinel.dashboard.discovery;

import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;

@@ -53,4 +54,9 @@ public class AppInfo {
return machines.add(machineInfo);
}

public Optional<MachineInfo> getMachine(String ip, int port) {
return machines.stream()
.filter(e -> e.getIp().equals(ip) && e.getPort().equals(port))
.findFirst();
}
}

+ 1
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemDegradeRuleStore.java View File

@@ -17,7 +17,7 @@ package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.taobao.csp.sentinel.dashboard.datasource.entity.DegradeRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import org.springframework.stereotype.Component;

/**


+ 2
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java View File

@@ -17,7 +17,7 @@ package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.taobao.csp.sentinel.dashboard.datasource.entity.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import org.springframework.stereotype.Component;

/**
@@ -27,6 +27,7 @@ import org.springframework.stereotype.Component;
*/
@Component
public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter<FlowRuleEntity> {

private static AtomicLong ids = new AtomicLong(0);

@Override


+ 36
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemParamFlowRuleStore.java View File

@@ -0,0 +1,36 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import org.springframework.stereotype.Component;

/**
* @author Eric Zhao
* @since 0.2.1
*/
@Component
public class InMemParamFlowRuleStore extends InMemoryRuleRepositoryAdapter<ParamFlowRuleEntity> {

private static AtomicLong ids = new AtomicLong(0);

@Override
protected long nextId() {
return ids.incrementAndGet();
}
}

+ 1
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemSystemRuleStore.java View File

@@ -17,7 +17,7 @@ package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.taobao.csp.sentinel.dashboard.datasource.entity.SystemRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import org.springframework.stereotype.Component;

/**


+ 3
- 4
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemoryRuleRepositoryAdapter.java View File

@@ -21,7 +21,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.taobao.csp.sentinel.dashboard.datasource.entity.RuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;

/**
@@ -83,14 +83,13 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
if (entities == null) {
return new ArrayList<>();
}
return entities.values().stream()
.collect(Collectors.toList());
return new ArrayList<>(entities.values());
}

/**
* Get next unused id.
*
* @return
* @return next unused id
*/
abstract protected long nextId();
}

+ 97
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/RuleUtils.java View File

@@ -0,0 +1,97 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.util;

import java.util.List;

import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public final class RuleUtils {

private static final Logger LOGGER = LoggerFactory.getLogger(RuleUtils.class);

public static List<FlowRule> parseFlowRule(String body) {
try {
return JSON.parseArray(body, FlowRule.class);
} catch (Exception e) {
LOGGER.error("parser FlowRule error: ", e);
return null;
}
}

public static List<DegradeRule> parseDegradeRule(String body) {
try {
return JSON.parseArray(body, DegradeRule.class);
} catch (Exception e) {
LOGGER.error("parser DegradeRule error: ", e);
return null;
}
}

public static List<AuthorityRule> parseAuthorityRule(String body) {
if (StringUtil.isBlank(body)) {
return null;
}
try {
return JSON.parseArray(body, AuthorityRule.class);
} catch (Exception e) {
LOGGER.error("Error when parsing authority rules", e);
return null;
}
}

/**
* Parse parameter flow rules.
*
* @param body raw string content
* @return parsed rule list; null if error occurs or empty content
*/
public static List<ParamFlowRule> parseParamFlowRule(String body) {
if (StringUtil.isBlank(body)) {
return null;
}
try {
return JSON.parseArray(body, ParamFlowRule.class);
} catch (Exception e) {
LOGGER.error("Error when parsing parameter flow rules", e);
return null;
}
}

public static List<SystemRule> parseSystemRule(String body) {
try {
return JSON.parseArray(body, SystemRule.class);
} catch (Exception e) {
LOGGER.info("parser SystemRule error: ", e);
return null;
}
}

private RuleUtils() {}
}

+ 70
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/VersionUtils.java View File

@@ -0,0 +1,70 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.util;

import java.util.Optional;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.datasource.entity.SentinelVersion;

/**
* Util class for parsing version.
*
* @author Eric Zhao
* @since 0.2.1
*/
public final class VersionUtils {

/**
* Parse version of Sentinel from raw string.
*
* @param s version string
* @return parsed {@link SentinelVersion} if the version is valid; empty if
* there is something wrong with the format
*/
public static Optional<SentinelVersion> parseVersion(String s) {
if (StringUtil.isBlank(s)) {
return Optional.empty();
}
try {
SentinelVersion version = new SentinelVersion();
String[] postArr = s.split("-");
if (postArr.length > 1) {
version.setPostfix(postArr[1]);
}
String[] arr = postArr[0].split("\\.");
if (arr.length == 2) {
version.setMajorVersion(Integer.valueOf(arr[0]))
.setMinorVersion(Integer.valueOf(arr[1]))
.setFixVersion(0);
} else if (arr.length == 3) {
version.setMajorVersion(Integer.valueOf(arr[0]))
.setMinorVersion(Integer.valueOf(arr[1]))
.setFixVersion(Integer.valueOf(arr[2]));
} else {
// Wrong format, return empty.
return Optional.empty();
}
return Optional.of(version);
} catch (Exception ex) {
// Parse fail, return empty.
return Optional.empty();
}
}

private VersionUtils() {}
}

+ 37
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/AuthorityRuleController.java View File

@@ -0,0 +1,37 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.view;

import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/authority")
public class AuthorityRuleController {

private final Logger logger = LoggerFactory.getLogger(AuthorityRuleController.class);

@Autowired
private SentinelApiClient sentinelApiClient;
}

+ 1
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/DegradeController.java View File

@@ -20,7 +20,7 @@ import java.util.List;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.datasource.entity.DegradeRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemDegradeRuleStore;


+ 1
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowController.java View File

@@ -20,7 +20,7 @@ import java.util.List;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.datasource.entity.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemFlowRuleStore;


+ 246
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java View File

@@ -0,0 +1,246 @@
/*
* 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.
*/
package com.taobao.csp.sentinel.dashboard.view;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.client.CommandNotFoundException;
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.repository.rule.RuleRepository;
import com.taobao.csp.sentinel.dashboard.util.VersionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {

private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);

@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;
@Autowired
private RuleRepository<ParamFlowRuleEntity, Long> repository;

private boolean checkIfSupported(String app, String ip, int port) {
try {
return Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version020)))
.orElse(true);
// If error occurred or cannot retrieve machine info, return true.
} catch (Exception ex) {
return true;
}
}

@GetMapping("/rules")
public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
.thenApply(repository::saveAll)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when querying parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when querying parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}

private boolean isNotSupported(Throwable ex) {
return ex instanceof CommandNotFoundException;
}

@PostMapping("/rule")
public Result<ParamFlowRuleEntity> apiAddParamFlowRule(@RequestBody ParamFlowRuleEntity entity) {
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when adding new parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when adding new parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}

private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null || entity.getPort() <= 0) {
return Result.ofFail(-1, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (entity.getCount() < 0) {
return Result.ofFail(-1, "count should be valid");
}
if (entity.getBlockGrade() != RuleConstant.FLOW_GRADE_QPS) {
return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control");
}
if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
return Result.ofFail(-1, "paramIdx should be valid");
}
return null;
}

@PutMapping("/rule/{id}")
public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id, @RequestBody ParamFlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(null);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when updating parameter flow rules, id=" + id, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}

@DeleteMapping("/rule/{id}")
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get();
return Result.ofSuccess(id);
} catch (ExecutionException ex) {
logger.error("Error when deleting parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when deleting parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}

private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
}

private <R> Result<R> unsupportedVersion() {
return Result.ofFail(4041, "Sentinel client version not supported for parameter flow control");
}

private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}

+ 37
- 10
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/Result.java View File

@@ -17,27 +17,31 @@ package com.taobao.csp.sentinel.dashboard.view;

/**
* @author leyou
* @author Eric Zhao
*/
public class Result<R> {

private boolean success;
private int code;
private String msg;
private R data;

public static <R> Result<R> ofSuccess(R data) {
Result<R> result = new Result<>();
result.setMsg("success");
result.setData(data);
return result;
return new Result<R>()
.setSuccess(true)
.setMsg("success")
.setData(data);
}

public static <R> Result<R> ofSuccessMsg(String msg) {
Result<R> result = new Result<>();
result.setMsg(msg);
return result;
return new Result<R>()
.setSuccess(true)
.setMsg(msg);
}

public static <R> Result<R> ofFail(int code, String msg) {
Result<R> result = new Result<>();
result.setSuccess(false);
result.setCode(code);
result.setMsg(msg);
return result;
@@ -45,32 +49,55 @@ public class Result<R> {

public static <R> Result<R> ofThrowable(int code, Throwable throwable) {
Result<R> result = new Result<>();
result.setSuccess(false);
result.setCode(code);
result.setMsg(throwable.getClass().getName() + ", " + throwable.getMessage());
return result;
}

public boolean isSuccess() {
return success;
}

public Result<R> setSuccess(boolean success) {
this.success = success;
return this;
}

public int getCode() {
return code;
}

public void setCode(int code) {
public Result<R> setCode(int code) {
this.code = code;
return this;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
public Result<R> setMsg(String msg) {
this.msg = msg;
return this;
}

public R getData() {
return data;
}

public void setData(R data) {
public Result<R> setData(R data) {
this.data = data;
return this;
}

@Override
public String toString() {
return "Result{" +
"success=" + success +
", code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}

+ 1
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/SystemController.java View File

@@ -20,7 +20,7 @@ import java.util.List;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.datasource.entity.SystemRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemSystemRuleStore;


+ 16
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js View File

@@ -82,6 +82,22 @@ angular
}
})

.state('dashboard.paramFlow', {
templateUrl: 'app/views/param_flow.html',
url: '/paramFlow/:app',
controller: 'ParamFlowController',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/param_flow.js',
]
});
}]
}
})

.state('dashboard.degrade', {
templateUrl: 'app/views/degrade.html',
url: '/degrade/:app',


+ 344
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js View File

@@ -0,0 +1,344 @@
/**
* Parameter flow control controller.
*
* @author Eric Zhao
*/
angular.module('sentinelDashboardApp').controller('ParamFlowController', ['$scope', '$stateParams', 'ParamFlowService', 'ngDialog',
'MachineService',
function ($scope, $stateParams, ParamFlowService, ngDialog,
MachineService) {
const UNSUPPORTED_CODE = 4041;
$scope.app = $stateParams.app;
$scope.curExItem = {};

$scope.paramItemClassTypeList = [
'int', 'double', 'java.lang.String', 'long', 'float', 'char', 'byte'
];

$scope.rulesPageConfig = {
pageSize: 10,
currentPageIndex: 1,
totalPage: 1,
totalCount: 0,
};
$scope.macsInputConfig = {
searchField: ['text', 'value'],
persist: true,
create: false,
maxItems: 1,
render: {
item: function (data, escape) {
return '<div>' + escape(data.text) + '</div>';
}
},
onChange: function (value, oldValue) {
$scope.macInputModel = value;
}
};

function updateSingleParamItem(arr, v, t, c) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].object === v && arr[i].classType === t) {
arr[i].count = c;
return;
}
}
arr.push({object: v, classType: t, count: c});
}

function removeSingleParamItem(arr, v, t) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].object === v && arr[i].classType === t) {
arr.splice(i, 1);
break;
}
}
}

function isNumberClass(classType) {
return classType === 'int' || classType === 'double' ||
classType === 'float' || classType === 'long' || classType === 'short';
}

function isByteClass(classType) {
return classType === 'byte';
}

function notNumberAtLeastZero(num) {
return num === undefined || num === '' || isNaN(num) || num < 0;
}

function notGoodNumber(num) {
return num === undefined || num === '' || isNaN(num);
}

function notGoodNumberBetweenExclusive(num, l ,r) {
return num === undefined || num === '' || isNaN(num) || num < l || num > r;
}

$scope.notValidParamItem = (curExItem) => {
if (isNumberClass(curExItem.classType) && notGoodNumber(curExItem.object)) {
return true;
}
if (isByteClass(curExItem.classType) && notGoodNumberBetweenExclusive(curExItem.object, -128, 127)) {
return true;
}
return curExItem.object === undefined || curExItem.classType === undefined ||
notNumberAtLeastZero(curExItem.count);
};

$scope.addParamItem = () => {
updateSingleParamItem($scope.currentRule.rule.paramFlowItemList,
$scope.curExItem.object, $scope.curExItem.classType, $scope.curExItem.count);
let oldItem = $scope.curExItem;
$scope.curExItem = {classType: oldItem.classType};
};

$scope.removeParamItem = (v, t) => {
removeSingleParamItem($scope.currentRule.rule.paramFlowItemList, v, t);
};

function getMachineRules() {
if (!$scope.macInputModel) {
return;
}
let mac = $scope.macInputModel.split(':');
ParamFlowService.queryMachineRules($scope.app, mac[0], mac[1])
.success(function (data) {
if (data.code == 0 && data.data) {
$scope.loadError = undefined;
$scope.rules = data.data;
$scope.rulesPageConfig.totalCount = $scope.rules.length;
} else {
$scope.rules = [];
$scope.rulesPageConfig.totalCount = 0;
if (data.code === UNSUPPORTED_CODE) {
$scope.loadError = {message: "机器 " + mac[0] + ":" + mac[1] + " 的 Sentinel 客户端版本不支持热点参数限流功能,请升级至 0.2.0 以上版本并引入 sentinel-parameter-flow-control 依赖。"}
} else {
$scope.loadError = {message: data.msg}
}
}
})
.error((data, header, config, status) => {
$scope.loadError = {message: "未知错误"}
});
};
$scope.getMachineRules = getMachineRules;
getMachineRules();

var paramFlowRuleDialog;

$scope.editRule = function (rule) {
$scope.currentRule = rule;
$scope.paramFlowRuleDialog = {
title: '编辑热点规则',
type: 'edit',
confirmBtnText: '保存',
showAdvanceButton: rule.rule.paramFlowItemList === undefined || rule.rule.paramFlowItemList.length <= 0
};
paramFlowRuleDialog = ngDialog.open({
template: '/app/views/dialog/param-flow-rule-dialog.html',
width: 680,
overlay: true,
scope: $scope
});
$scope.curExItem = {};
};

$scope.addNewRule = function () {
var mac = $scope.macInputModel.split(':');
$scope.currentRule = {
app: $scope.app,
ip: mac[0],
port: mac[1],
rule: {
blockGrade: 1,
paramFlowItemList: [],
count: 0,
limitApp: 'default',
}
};
$scope.paramFlowRuleDialog = {
title: '新增热点规则',
type: 'add',
confirmBtnText: '新增',
showAdvanceButton: true,
};
paramFlowRuleDialog = ngDialog.open({
template: '/app/views/dialog/param-flow-rule-dialog.html',
width: 680,
overlay: true,
scope: $scope
});
$scope.curExItem = {};
};

$scope.onOpenAdvanceClick = function () {
$scope.paramFlowRuleDialog.showAdvanceButton = false;
};
$scope.onCloseAdvanceClick = function () {
$scope.paramFlowRuleDialog.showAdvanceButton = true;
};

function checkRuleValid(rule) {
if (!rule.resource || rule.resource === '') {
alert('资源名称不能为空');
return false;
}
if (rule.blockGrade !== 1) {
alert('未知的限流模式');
return false;
}
if (rule.count < 0) {
alert('限流阈值必须大于等于 0');
return false;
}
if (rule.paramIdx === undefined || rule.paramIdx === '' || isNaN(rule.paramIdx) || rule.paramIdx < 0) {
alert('热点参数索引必须大于等于 0');
return false;
}
if (rule.paramFlowItemList !== undefined) {
for (let i = 0; i < rule.paramFlowItemList.length; i++) {
let item = rule.paramFlowItemList[i];
if ($scope.notValidParamItem(item)) {
alert('热点参数例外项不合法,请检查值和类型是否正确:参数为 ' + item.object + ', 类型为 ' +
item.classType + ', 限流阈值为 ' + item.count);
return false;
}
}
}
return true;
}

$scope.saveRule = function () {
if (!checkRuleValid($scope.currentRule.rule)) {
return;
}
if ($scope.paramFlowRuleDialog.type === 'add') {
addNewRuleAndPush($scope.currentRule);
} else if ($scope.paramFlowRuleDialog.type === 'edit') {
saveRuleAndPush($scope.currentRule, true);
}
};

function addNewRuleAndPush(rule) {
ParamFlowService.addNewRule(rule).success((data) => {
if (data.success) {
getMachineRules();
paramFlowRuleDialog.close();
} else {
alert('添加规则失败:' + data.msg);
}
}).error((data) => {
if (data) {
alert('添加规则失败:' + data.msg);
} else {
alert("添加规则失败:未知错误");
}
});
};

function saveRuleAndPush(rule, edit) {
ParamFlowService.saveRule(rule).success(function (data) {
if (data.success) {
alert("修改规则成功");
getMachineRules();
if (edit) {
paramFlowRuleDialog.close();
} else {
confirmDialog.close();
}
} else {
alert('修改规则失败:' + data.msg);
}
}).error((data) => {
if (data) {
alert('修改规则失败:' + data.msg);
} else {
alert("修改规则失败:未知错误");
}
});
}

function deleteRuleAndPush(entity) {
if (entity.id === undefined || isNaN(entity.id)) {
alert('规则 ID 不合法!');
return;
}
ParamFlowService.deleteRule(entity).success((data) => {
if (data.code == 0) {
getMachineRules();
confirmDialog.close();
} else {
alert('删除规则失败:' + data.msg);
}
}).error((data) => {
if (data) {
alert('删除规则失败:' + data.msg);
} else {
alert("删除规则失败:未知错误");
}
});
};

var confirmDialog;
$scope.deleteRule = function (ruleEntity) {
$scope.currentRule = ruleEntity;
console.log('deleting: ' + ruleEntity);
$scope.confirmDialog = {
title: '删除热点规则',
type: 'delete_rule',
attentionTitle: '请确认是否删除如下热点参数限流规则',
attention: '资源名: ' + ruleEntity.rule.resource + ', 热点参数索引: ' + ruleEntity.rule.paramIdx +
', 限流模式: ' + (ruleEntity.rule.blockGrade === 1 ? 'QPS' : '未知') + ', 限流阈值: ' + ruleEntity.rule.count,
confirmBtnText: '删除',
};
confirmDialog = ngDialog.open({
template: '/app/views/dialog/confirm-dialog.html',
scope: $scope,
overlay: true
});
};

$scope.confirm = function () {
if ($scope.confirmDialog.type == 'delete_rule') {
deleteRuleAndPush($scope.currentRule);
} else {
console.error('error');
}
};

queryAppMachines();

function queryAppMachines() {
MachineService.getAppMachines($scope.app).success(
function (data) {
if (data.code == 0) {
// $scope.machines = data.data;
if (data.data) {
$scope.machines = [];
$scope.macsInputOptions = [];
data.data.forEach(function (item) {
if (item.health) {
$scope.macsInputOptions.push({
text: item.ip + ':' + item.port,
value: item.ip + ':' + item.port
});
}
});
}
if ($scope.macsInputOptions.length > 0) {
$scope.macInputModel = $scope.macsInputOptions[0].value;
}
} else {
$scope.macsInputOptions = [];
}
}
);
};
$scope.$watch('macInputModel', function () {
if ($scope.macInputModel) {
getMachineRules();
}
});
}]);

+ 7
- 9
sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html View File

@@ -9,21 +9,15 @@
</span>
</div>
</li>

<!--<li ui-sref-active="active"><a ui-sref="dashboard.home"><i-->
<!--style="color: #000;" class="fa fa-dashboard fa-fw"></i> <span-->
<!--style="color: black; font-size:16px;">主控制板</span> </a></li>-->
<li ui-sref-active="active">
<a ui-sref="dashboard.home" style="font-size:16px;">
<span class="glyphicon glyphicon-dashboard"></span>
&nbsp;&nbsp;首页</a>
</li>

<li ng-class="{active: true}" ng-repeat="entry in apps | filter: { app: searchApp}">{{dropDown}}
<a href="" ng-click="click($event)" collapse="{{collpaseall == 1}}" style="font-size: 16px;">
<!--<i class="glyphicon glyphicon-chevron-right"></i>-->
<!--<span class="fa arrow"></span>-->
&nbsp; &nbsp;{{entry.app}}
<li ng-class="{active: true}" ng-repeat="entry in apps | filter: { app: searchApp }">{{dropDown}}
<a href="#" ng-click="click($event)" collapse="{{collpaseall == 1}}" style="font-size: 16px;word-break: break-word;">
&nbsp;{{entry.app}}
<span class="fa arrow"></span>
</a>

@@ -46,6 +40,10 @@
<a ui-sref="dashboard.degrade({app: entry.app})">
<i class="glyphicon glyphicon-flash"></i>&nbsp;&nbsp;降级规则</a>
</li>
<li ui-sref-active="active">
<a ui-sref="dashboard.paramFlow({app: entry.app})">
<i class="glyphicon glyphicon-fire"></i>&nbsp;&nbsp;热点规则</a>
</li>
<li ui-sref-active="active">
<a ui-sref="dashboard.system({app: entry.app})">
<i class="glyphicon glyphicon-lock"></i>&nbsp;&nbsp;系统规则</a>


+ 42
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/services/param_flow_service.js View File

@@ -0,0 +1,42 @@
/**
* Parameter flow control service.
*
* @author Eric Zhao
*/
angular.module('sentinelDashboardApp').service('ParamFlowService', ['$http', function ($http) {
this.queryMachineRules = function(app, ip, port) {
var param = {
app: app,
ip: ip,
port: port
};
return $http({
url: '/paramFlow/rules',
params: param,
method: 'GET'
});
};

this.addNewRule = function(rule) {
return $http({
url: '/paramFlow/rule',
data: rule,
method: 'POST'
});
};

this.saveRule = function (entity) {
return $http({
url: '/paramFlow/rule/' + entity.id,
data: entity,
method: 'PUT'
});
};

this.deleteRule = function (entity) {
return $http({
url: '/paramFlow/rule/' + entity.id,
method: 'DELETE'
});
};
}]);

+ 1
- 1
sentinel-dashboard/src/main/webapp/resources/app/views/dashboard/home.html View File

@@ -1,7 +1,7 @@
<div>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">欢迎使用Sentinel控制台</h1>
<h1 class="page-header">欢迎使用 Sentinel 控制台</h1>
</div>
<!-- /.col-lg-12 -->
</div>


+ 119
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html View File

@@ -0,0 +1,119 @@
<div>
<span class="brand" style="font-weight:bold;">{{paramFlowRuleDialog.title}}</span>
<div class="card" style="margin-top: 20px;margin-bottom: 10px;">
<div class="panel-body">
<div class="clearfix">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">资源名</label>
<div class="col-sm-9">
<input type="text" ng-if="paramFlowRuleDialog.type == 'edit'" class="form-control" placeholder="资源名" ng-model='currentRule.rule.resource' disabled="" />
<input type="text" ng-if="paramFlowRuleDialog.type == 'add'" class="form-control highlight-border" placeholder="资源名" ng-model='currentRule.rule.resource' required />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">限流模式</label>
<p class="col-sm-9 control-label" style="text-align: left; font-weight: normal;">QPS 模式</p>
</div>

<div class="form-group">
<label class="col-sm-2 control-label">参数索引</label>
<div class="col-sm-9">
<input type="number" class="form-control highlight-border" ng-model='currentRule.rule.paramIdx' placeholder='请填入传入的热点参数的索引(从 0 开始)' />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">限流阈值</label>
<div class="col-sm-9">
<input type="number" class="form-control highlight-border" ng-model='currentRule.rule.count' placeholder='请填入限流阈值' />
</div>
</div>


<!-- exclusion item part start -->
<div ng-if="!paramFlowRuleDialog.showAdvanceButton">
<hr />
<div class="form-group">
<div class="form-group" style="text-align: center">
<label class="control-label">参数例外项</label>
</div>

<div class="form-group">
<label class="col-sm-2 control-label">参数类型</label>
<div class="col-md-9">
<select ng-model="curExItem.classType" ng-options="classType for classType in paramItemClassTypeList" class="form-control" placeholder="请选择参数类型">
</select>
</div>
</div>

<div class="form-group">
<label class="col-sm-2 control-label">参数值</label>
<div class="col-md-3">
<input ng-model="curExItem.object" type="text" class="form-control" placeholder="例外项参数值">
</div>

<label class="col-sm-2 control-label">限流阈值</label>
<div class="col-md-3">
<input type="number" ng-model="curExItem.count" class="form-control" placeholder="限流阈值">
</div>

<div class="col-md-2">
<button type="button" class="btn btn-success"
ng-disabled="notValidParamItem(curExItem)"
ng-click="addParamItem()">
<span class="fa fa-plus">&nbsp;添加</span>
</button>
</div>
</div>

<div>
<div class="col-md-12">
<table class="table table-condensed table-hover">
<thead>
<th>参数值</th>
<th>参数类型</th>
<th>限流阈值</th>
<th>操作</th>
</thead>
<tbody>
<tr ng-repeat="paramItem in currentRule.rule.paramFlowItemList">
<td><input ng-model="paramItem.object" type="text" class="form-control" placeholder="例外项参数"></td>
<td>
<p>{{paramItem.classType}}</p>
</td>
<td>
<input type="number" ng-model="paramItem.count" class="form-control" placeholder="限流阈值">
</td>
<td>
<button type="button" class="btn btn-danger"
ng-click="removeParamItem(paramItem.object, paramItem.classType)"><span
class="fa fa-trash-o">&nbsp;删除</span></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

</div>
</div>

<!-- exclusion item part end -->
<div class="form-group text-center">
<a ng-click="onOpenAdvanceClick()" ng-if="paramFlowRuleDialog.showAdvanceButton">高级选项</a>
<a ng-click="onCloseAdvanceClick()" ng-if="!paramFlowRuleDialog.showAdvanceButton">关闭高级选项</a>
</div>
</form>
</div>
<div class="separator"></div>
<div clss="row" style="margin-top: 20px;">
<button class="btn btn-default-inverse" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="closeThisDialog()">取消</button>
<button class="btn btn-danger-inverse" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="saveRule()">{{paramFlowRuleDialog.confirmBtnText}}</button>
<button ng-if="paramFlowRuleDialog.saveAndContinueBtnText" class="btn btn-default" style="float:right; height: 30px;font-size: 12px;"
ng-click="saveRuleAndContinue()">{{paramFlowRuleDialog.saveAndContinueBtnText}}</button>
</div>
</div>
</div>
</div>

+ 111
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html View File

@@ -0,0 +1,111 @@
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
<div class="col-md-6" ng-if="!loadError">
<button class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewRule()">
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增热点限流规则</button>
</div>
</div>
<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">热点参数限流规则</span>
<button class="btn btn-primary" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;" ng-click="getMachineRules()">刷新</button>
<input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey">
<div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">
<selectize id="gsInput" class="selectize-input-200" config="macsInputConfig" options="macsInputOptions" ng-model="macInputModel"
placeholder="机器"></selectize>
</div>
</div>

<!-- error panel -->
<div class="row clearfix" ng-if="loadError">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-body">
<center>
<p>{{loadError.message}}</p>
</center>
</div>
</div>
</div>
</div>

<!-- Table and pagination start -->
<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td style="width: 40%">
资源名
</td>
<td style="width: 10%;">
参数索引
</td>
<td style="width: 10%;">
流控模式
</td>
<td style="width: 10%;">
单机阈值
</td>
<td style="width: 10%;">
例外项数目
</td>
<td style="width: 12%;">
操作
</td>
</tr>
</thead>
<tbody>
<tr dir-paginate="ruleEntity in rules | filter: searchKey | itemsPerPage: rulesPageConfig.pageSize " current-page="rulesPageConfig.currentPageIndex"
pagination-id="entriesPagination">
<td style="word-wrap:break-word;word-break:break-all;">{{ruleEntity.rule.resource}}</td>
<td style="word-wrap:break-word;word-break:break-all;">{{ruleEntity.rule.paramIdx}}</td>
<td>
{{ruleEntity.rule.blockGrade == 1 ? 'QPS' : '未知'}}
</td>
<td style="word-wrap:break-word;word-break:break-all;">
{{ruleEntity.rule.count}}
</td>
<td>
{{ruleEntity.rule.paramFlowItemList == undefined ? 0 : ruleEntity.rule.paramFlowItemList.length}}
</td>
<td>
<button class="btn btn-xs btn-default" type="button" ng-click="editRule(ruleEntity)" style="font-size: 12px; height:25px;">编辑</button>
<button class="btn btn-xs btn-default" type="button" ng-click="deleteRule(ruleEntity)" style="font-size: 12px; height:25px;">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
<div class="pagination-footer" ng-if="!loadError">
<dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html" pagination-id="entriesPagination"
on-page-change="">
</dir-pagination-controls>
<div class="tools" style="">
<span>共 {{rulesPageConfig.totalCount}} 条记录, </span>
<span>每页 <input class="form-control" ng-model="rulesPageConfig.pageSize"> 条记录</span>
<!--<span>第 {{rulesPageConfig.currentPageIndex}} / {{rulesPageConfig.totalPage}} 页</span>-->
</div>
<!-- .tools -->
</div>
<!-- pagination-footer -->
<!-- Table and pagination end -->

</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->

+ 1
- 1
sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
File diff suppressed because it is too large
View File


+ 1
- 1
sentinel-dashboard/src/main/webapp/resources/dist/js/app.vendor.js
File diff suppressed because it is too large
View File


+ 1
- 0
sentinel-dashboard/src/main/webapp/resources/gulpfile.js View File

@@ -50,6 +50,7 @@ const JS_APP = [
'app/scripts/services/machineservice.js',
'app/scripts/services/identityservice.js',
'app/scripts/services/metricservice.js',
'app/scripts/services/param_flow_service.js',
];

gulp.task('lib', function () {


+ 13
- 13
sentinel-dashboard/src/main/webapp/resources/package-lock.json View File

@@ -2220,7 +2220,7 @@
},
"gulp-uglify": {
"version": "3.0.1",
"resolved": "http://registry.npm.alibaba-inc.com/gulp-uglify/download/gulp-uglify-3.0.1.tgz",
"resolved": "http://registry.npm.taobao.org/gulp-uglify/download/gulp-uglify-3.0.1.tgz",
"integrity": "sha1-jT7uRmUhvqaxD9dd/3Kt+LfqLZc=",
"dev": true,
"requires": {
@@ -3017,14 +3017,14 @@
"dev": true
},
"make-error": {
"version": "1.3.4",
"resolved": "http://registry.npm.alibaba-inc.com/make-error/download/make-error-1.3.4.tgz",
"integrity": "sha1-GZeO1XX56VRdL/jBPjO10Ypn1TU=",
"version": "1.3.5",
"resolved": "http://registry.npm.taobao.org/make-error/download/make-error-1.3.5.tgz",
"integrity": "sha1-7+ToH22yjK3WBccPKcgxtY73dsg=",
"dev": true
},
"make-error-cause": {
"version": "1.2.2",
"resolved": "http://registry.npm.alibaba-inc.com/make-error-cause/download/make-error-cause-1.2.2.tgz",
"resolved": "http://registry.npm.taobao.org/make-error-cause/download/make-error-cause-1.2.2.tgz",
"integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=",
"dev": true,
"requires": {
@@ -4659,24 +4659,24 @@
}
},
"uglify-js": {
"version": "3.4.5",
"resolved": "http://registry.npm.alibaba-inc.com/uglify-js/download/uglify-js-3.4.5.tgz",
"integrity": "sha1-ZQiJwHZs8Pb9U0bOoJzSEvVEvmk=",
"version": "3.4.9",
"resolved": "http://registry.npm.taobao.org/uglify-js/download/uglify-js-3.4.9.tgz",
"integrity": "sha1-rwLxgMEgfXZDLkc+0koo9KeCuuM=",
"dev": true,
"requires": {
"commander": "~2.16.0",
"commander": "~2.17.1",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.16.0",
"resolved": "http://registry.npm.alibaba-inc.com/commander/download/commander-2.16.0.tgz",
"integrity": "sha1-8WOQWTmWzrTz7rAgsx14Uo9/ilA=",
"version": "2.17.1",
"resolved": "http://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz",
"integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78=",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "http://registry.npm.alibaba-inc.com/source-map/download/source-map-0.6.1.tgz",
"resolved": "http://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
"integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
}


Loading…
Cancel
Save