- 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
@@ -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> | |||
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
@@ -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; | |||
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
@@ -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; | |||
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
/** | |||
@@ -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 | |||
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
/** | |||
@@ -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(); | |||
} |
@@ -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() {} | |||
} |
@@ -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() {} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
@@ -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; | |||
@@ -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); | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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; | |||
@@ -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', | |||
@@ -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(); | |||
} | |||
}); | |||
}]); |
@@ -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> | |||
首页</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>--> | |||
{{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;"> | |||
{{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> 降级规则</a> | |||
</li> | |||
<li ui-sref-active="active"> | |||
<a ui-sref="dashboard.paramFlow({app: entry.app})"> | |||
<i class="glyphicon glyphicon-fire"></i> 热点规则</a> | |||
</li> | |||
<li ui-sref-active="active"> | |||
<a ui-sref="dashboard.system({app: entry.app})"> | |||
<i class="glyphicon glyphicon-lock"></i> 系统规则</a> | |||
@@ -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,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> | |||
@@ -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"> 添加</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"> 删除</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> |
@@ -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> 新增热点限流规则</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 --> | |||
@@ -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 () { | |||
@@ -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 | |||
} | |||