- 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> | <packaging>jar</packaging> | ||||
<properties> | <properties> | ||||
<maven.compiler.target>1.8</maven.compiler.target> | |||||
<maven.compiler.source>1.8</maven.compiler.source> | <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> | </properties> | ||||
<dependencies> | <dependencies> | ||||
@@ -35,39 +36,30 @@ | |||||
</dependency> | </dependency> | ||||
<dependency> | <dependency> | ||||
<groupId>com.alibaba.csp</groupId> | <groupId>com.alibaba.csp</groupId> | ||||
<artifactId>sentinel-transport-common</artifactId> | |||||
<artifactId>sentinel-parameter-flow-control</artifactId> | |||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-thymeleaf</artifactId> | |||||
<version>1.5.9.RELEASE</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-starter-web</artifactId> | <artifactId>spring-boot-starter-web</artifactId> | ||||
<version>1.5.9.RELEASE</version> | |||||
<version>${spring.boot.version}</version> | |||||
</dependency> | </dependency> | ||||
<dependency> | <dependency> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-starter-logging</artifactId> | <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> | ||||
<dependency> | <dependency> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-devtools</artifactId> | <artifactId>spring-boot-devtools</artifactId> | ||||
<version>1.5.9.RELEASE</version> | |||||
<version>${spring.boot.version}</version> | |||||
<optional>true</optional> | <optional>true</optional> | ||||
</dependency> | </dependency> | ||||
<dependency> | <dependency> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-starter-test</artifactId> | <artifactId>spring-boot-starter-test</artifactId> | ||||
<version>1.5.9.RELEASE</version> | |||||
<version>${spring.boot.version}</version> | |||||
<scope>test</scope> | <scope>test</scope> | ||||
</dependency> | </dependency> | ||||
@@ -117,7 +109,7 @@ | |||||
<artifactId>spring-boot-maven-plugin</artifactId> | <artifactId>spring-boot-maven-plugin</artifactId> | ||||
<configuration> | <configuration> | ||||
<fork>true</fork> | <fork>true</fork> | ||||
<mainClass>com.taobao.csp.sentinel.dashboard.Application</mainClass> | |||||
<mainClass>com.taobao.csp.sentinel.dashboard.DashboardApplication</mainClass> | |||||
</configuration> | </configuration> | ||||
<executions> | <executions> | ||||
<execution> | <execution> | ||||
@@ -132,8 +124,8 @@ | |||||
<groupId>org.apache.maven.plugins</groupId> | <groupId>org.apache.maven.plugins</groupId> | ||||
<artifactId>maven-compiler-plugin</artifactId> | <artifactId>maven-compiler-plugin</artifactId> | ||||
<configuration> | <configuration> | ||||
<source>1.8</source> | |||||
<target>1.8</target> | |||||
<source>${maven.compiler.source}</source> | |||||
<target>${maven.compiler.target}</target> | |||||
</configuration> | </configuration> | ||||
</plugin> | </plugin> | ||||
@@ -17,19 +17,11 @@ package com.taobao.csp.sentinel.dashboard; | |||||
import org.springframework.boot.SpringApplication; | import org.springframework.boot.SpringApplication; | ||||
import org.springframework.boot.autoconfigure.SpringBootApplication; | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
import org.springframework.boot.builder.SpringApplicationBuilder; | |||||
import org.springframework.boot.web.support.SpringBootServletInitializer; | |||||
@SpringBootApplication | @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) { | 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; | package com.taobao.csp.sentinel.dashboard.client; | ||||
import java.io.UnsupportedEncodingException; | import java.io.UnsupportedEncodingException; | ||||
import java.net.URI; | |||||
import java.net.URISyntaxException; | |||||
import java.net.URLEncoder; | import java.net.URLEncoder; | ||||
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | |||||
import java.util.concurrent.CompletableFuture; | |||||
import java.util.concurrent.CountDownLatch; | import java.util.concurrent.CountDownLatch; | ||||
import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
import java.util.concurrent.atomic.AtomicReference; | 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.degrade.DegradeRule; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | ||||
import com.alibaba.csp.sentinel.slots.system.SystemRule; | 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.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.HttpResponse; | ||||
import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||
import org.apache.http.client.utils.URIBuilder; | |||||
import org.apache.http.concurrent.FutureCallback; | import org.apache.http.concurrent.FutureCallback; | ||||
import org.apache.http.entity.ContentType; | import org.apache.http.entity.ContentType; | ||||
import org.apache.http.impl.client.DefaultRedirectStrategy; | import org.apache.http.impl.client.DefaultRedirectStrategy; | ||||
@@ -56,17 +66,24 @@ import org.springframework.stereotype.Component; | |||||
public class SentinelApiClient { | public class SentinelApiClient { | ||||
private static Logger logger = LoggerFactory.getLogger(SentinelApiClient.class); | 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 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() { | public SentinelApiClient() { | ||||
IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(3000) | 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) { | 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); | String body = httpGetContent(url); | ||||
if (body == null) { | if (body == null) { | ||||
return null; | return null; | ||||
@@ -107,7 +124,7 @@ public class SentinelApiClient { | |||||
if (includeZero) { | if (includeZero) { | ||||
type = "zero"; | type = "zero"; | ||||
} | } | ||||
String url = "http://" + ip + ":" + port + "/" + clusterNodePath + "?type=" + type; | |||||
String url = "http://" + ip + ":" + port + "/" + CLUSTER_NODE_PATH + "?type=" + type; | |||||
String body = httpGetContent(url); | String body = httpGetContent(url); | ||||
if (body == null) { | if (body == null) { | ||||
return null; | return null; | ||||
@@ -121,10 +138,10 @@ public class SentinelApiClient { | |||||
} | } | ||||
public List<FlowRuleEntity> fetchFlowRuleOfMachine(String app, String ip, int port) { | 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); | String body = httpGetContent(url); | ||||
logger.info("FlowRule Body:{}", body); | logger.info("FlowRule Body:{}", body); | ||||
List<FlowRule> rules = parseFlowRule(body); | |||||
List<FlowRule> rules = RuleUtils.parseFlowRule(body); | |||||
if (rules != null) { | if (rules != null) { | ||||
return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule)) | return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule)) | ||||
.collect(Collectors.toList()); | .collect(Collectors.toList()); | ||||
@@ -134,10 +151,10 @@ public class SentinelApiClient { | |||||
} | } | ||||
public List<DegradeRuleEntity> fetchDegradeRuleOfMachine(String app, String ip, int port) { | 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); | String body = httpGetContent(url); | ||||
logger.info("Degrade Body:{}", body); | logger.info("Degrade Body:{}", body); | ||||
List<DegradeRule> rules = parseDegradeRule(body); | |||||
List<DegradeRule> rules = RuleUtils.parseDegradeRule(body); | |||||
if (rules != null) { | if (rules != null) { | ||||
return rules.stream().map(rule -> DegradeRuleEntity.fromDegradeRule(app, ip, port, rule)) | return rules.stream().map(rule -> DegradeRuleEntity.fromDegradeRule(app, ip, port, rule)) | ||||
.collect(Collectors.toList()); | .collect(Collectors.toList()); | ||||
@@ -147,10 +164,10 @@ public class SentinelApiClient { | |||||
} | } | ||||
public List<SystemRuleEntity> fetchSystemRuleOfMachine(String app, String ip, int port) { | 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); | String body = httpGetContent(url); | ||||
logger.info("SystemRule Body:{}", body); | logger.info("SystemRule Body:{}", body); | ||||
List<SystemRule> rules = parseSystemRule(body); | |||||
List<SystemRule> rules = RuleUtils.parseSystemRule(body); | |||||
if (rules != null) { | if (rules != null) { | ||||
return rules.stream().map(rule -> SystemRuleEntity.fromSystemRule(app, ip, port, rule)) | return rules.stream().map(rule -> SystemRuleEntity.fromSystemRule(app, ip, port, rule)) | ||||
.collect(Collectors.toList()); | .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; | * set rules of the machine. rules == null will return immediately; | ||||
* rules.isEmpty() means setting the rules to empty. | * 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())); | String data = JSON.toJSONString(rules.stream().map(FlowRuleEntity::toFlowRule).collect(Collectors.toList())); | ||||
try { | try { | ||||
data = URLEncoder.encode(data, defaultCharset.name()); | |||||
data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); | |||||
} catch (UnsupportedEncodingException e) { | } catch (UnsupportedEncodingException e) { | ||||
logger.info("encode rule error", e); | logger.info("encode rule error", e); | ||||
return false; | 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); | String result = httpGetContent(url); | ||||
logger.info("setFlowRule: " + result); | logger.info("setFlowRule: " + result); | ||||
return true; | return true; | ||||
@@ -209,12 +288,13 @@ public class SentinelApiClient { | |||||
String data = JSON.toJSONString( | String data = JSON.toJSONString( | ||||
rules.stream().map(DegradeRuleEntity::toDegradeRule).collect(Collectors.toList())); | rules.stream().map(DegradeRuleEntity::toDegradeRule).collect(Collectors.toList())); | ||||
try { | try { | ||||
data = URLEncoder.encode(data, defaultCharset.name()); | |||||
data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); | |||||
} catch (UnsupportedEncodingException e) { | } catch (UnsupportedEncodingException e) { | ||||
logger.info("encode rule error", e); | logger.info("encode rule error", e); | ||||
return false; | 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); | String result = httpGetContent(url); | ||||
logger.info("setDegradeRule: " + result); | logger.info("setDegradeRule: " + result); | ||||
return true; | return true; | ||||
@@ -240,42 +320,93 @@ public class SentinelApiClient { | |||||
String data = JSON.toJSONString( | String data = JSON.toJSONString( | ||||
rules.stream().map(SystemRuleEntity::toSystemRule).collect(Collectors.toList())); | rules.stream().map(SystemRuleEntity::toSystemRule).collect(Collectors.toList())); | ||||
try { | try { | ||||
data = URLEncoder.encode(data, defaultCharset.name()); | |||||
data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); | |||||
} catch (UnsupportedEncodingException e) { | } catch (UnsupportedEncodingException e) { | ||||
logger.info("encode rule error", e); | logger.info("encode rule error", e); | ||||
return false; | 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); | String result = httpGetContent(url); | ||||
logger.info("setSystemRule: " + result); | logger.info("setSystemRule: " + result); | ||||
return true; | 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 { | 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) { | private String httpGetContent(String url) { | ||||
@@ -321,10 +452,16 @@ public class SentinelApiClient { | |||||
charset = contentType.getCharset(); | charset = contentType.getCharset(); | ||||
} catch (Exception ignore) { | } 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 { | public void close() throws Exception { | ||||
httpClient.close(); | 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 | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * 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; | import java.util.Date; | ||||
@@ -13,7 +13,7 @@ | |||||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * 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; | 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 | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * 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; | import java.util.Date; | ||||
@@ -13,7 +13,7 @@ | |||||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * 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; | import java.util.Date; | ||||
@@ -15,6 +15,7 @@ | |||||
*/ | */ | ||||
package com.taobao.csp.sentinel.dashboard.discovery; | package com.taobao.csp.sentinel.dashboard.discovery; | ||||
import java.util.Optional; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.TreeSet; | import java.util.TreeSet; | ||||
@@ -53,4 +54,9 @@ public class AppInfo { | |||||
return machines.add(machineInfo); | 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 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; | import org.springframework.stereotype.Component; | ||||
/** | /** | ||||
@@ -17,7 +17,7 @@ package com.taobao.csp.sentinel.dashboard.repository.rule; | |||||
import java.util.concurrent.atomic.AtomicLong; | 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; | import org.springframework.stereotype.Component; | ||||
/** | /** | ||||
@@ -27,6 +27,7 @@ import org.springframework.stereotype.Component; | |||||
*/ | */ | ||||
@Component | @Component | ||||
public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter<FlowRuleEntity> { | public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter<FlowRuleEntity> { | ||||
private static AtomicLong ids = new AtomicLong(0); | private static AtomicLong ids = new AtomicLong(0); | ||||
@Override | @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 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; | import org.springframework.stereotype.Component; | ||||
/** | /** | ||||
@@ -21,7 +21,7 @@ import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||
import java.util.stream.Collectors; | 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; | import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | ||||
/** | /** | ||||
@@ -83,14 +83,13 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem | |||||
if (entities == null) { | if (entities == null) { | ||||
return new ArrayList<>(); | return new ArrayList<>(); | ||||
} | } | ||||
return entities.values().stream() | |||||
.collect(Collectors.toList()); | |||||
return new ArrayList<>(entities.values()); | |||||
} | } | ||||
/** | /** | ||||
* Get next unused id. | * Get next unused id. | ||||
* | * | ||||
* @return | |||||
* @return next unused id | |||||
*/ | */ | ||||
abstract protected long nextId(); | 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.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.discovery.MachineInfo; | ||||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | ||||
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemDegradeRuleStore; | 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.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.discovery.MachineInfo; | ||||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | ||||
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemFlowRuleStore; | 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 leyou | ||||
* @author Eric Zhao | |||||
*/ | */ | ||||
public class Result<R> { | public class Result<R> { | ||||
private boolean success; | |||||
private int code; | private int code; | ||||
private String msg; | private String msg; | ||||
private R data; | private R data; | ||||
public static <R> Result<R> ofSuccess(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) { | 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) { | public static <R> Result<R> ofFail(int code, String msg) { | ||||
Result<R> result = new Result<>(); | Result<R> result = new Result<>(); | ||||
result.setSuccess(false); | |||||
result.setCode(code); | result.setCode(code); | ||||
result.setMsg(msg); | result.setMsg(msg); | ||||
return result; | return result; | ||||
@@ -45,32 +49,55 @@ public class Result<R> { | |||||
public static <R> Result<R> ofThrowable(int code, Throwable throwable) { | public static <R> Result<R> ofThrowable(int code, Throwable throwable) { | ||||
Result<R> result = new Result<>(); | Result<R> result = new Result<>(); | ||||
result.setSuccess(false); | |||||
result.setCode(code); | result.setCode(code); | ||||
result.setMsg(throwable.getClass().getName() + ", " + throwable.getMessage()); | result.setMsg(throwable.getClass().getName() + ", " + throwable.getMessage()); | ||||
return result; | return result; | ||||
} | } | ||||
public boolean isSuccess() { | |||||
return success; | |||||
} | |||||
public Result<R> setSuccess(boolean success) { | |||||
this.success = success; | |||||
return this; | |||||
} | |||||
public int getCode() { | public int getCode() { | ||||
return code; | return code; | ||||
} | } | ||||
public void setCode(int code) { | |||||
public Result<R> setCode(int code) { | |||||
this.code = code; | this.code = code; | ||||
return this; | |||||
} | } | ||||
public String getMsg() { | public String getMsg() { | ||||
return msg; | return msg; | ||||
} | } | ||||
public void setMsg(String msg) { | |||||
public Result<R> setMsg(String msg) { | |||||
this.msg = msg; | this.msg = msg; | ||||
return this; | |||||
} | } | ||||
public R getData() { | public R getData() { | ||||
return data; | return data; | ||||
} | } | ||||
public void setData(R data) { | |||||
public Result<R> setData(R data) { | |||||
this.data = 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.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.discovery.MachineInfo; | ||||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | ||||
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemSystemRuleStore; | 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', { | .state('dashboard.degrade', { | ||||
templateUrl: 'app/views/degrade.html', | templateUrl: 'app/views/degrade.html', | ||||
url: '/degrade/:app', | 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> | </span> | ||||
</div> | </div> | ||||
</li> | </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"> | <li ui-sref-active="active"> | ||||
<a ui-sref="dashboard.home" style="font-size:16px;"> | <a ui-sref="dashboard.home" style="font-size:16px;"> | ||||
<span class="glyphicon glyphicon-dashboard"></span> | <span class="glyphicon glyphicon-dashboard"></span> | ||||
首页</a> | 首页</a> | ||||
</li> | </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> | <span class="fa arrow"></span> | ||||
</a> | </a> | ||||
@@ -46,6 +40,10 @@ | |||||
<a ui-sref="dashboard.degrade({app: entry.app})"> | <a ui-sref="dashboard.degrade({app: entry.app})"> | ||||
<i class="glyphicon glyphicon-flash"></i> 降级规则</a> | <i class="glyphicon glyphicon-flash"></i> 降级规则</a> | ||||
</li> | </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"> | <li ui-sref-active="active"> | ||||
<a ui-sref="dashboard.system({app: entry.app})"> | <a ui-sref="dashboard.system({app: entry.app})"> | ||||
<i class="glyphicon glyphicon-lock"></i> 系统规则</a> | <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> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="col-lg-12"> | <div class="col-lg-12"> | ||||
<h1 class="page-header">欢迎使用Sentinel控制台</h1> | |||||
<h1 class="page-header">欢迎使用 Sentinel 控制台</h1> | |||||
</div> | </div> | ||||
<!-- /.col-lg-12 --> | <!-- /.col-lg-12 --> | ||||
</div> | </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/machineservice.js', | ||||
'app/scripts/services/identityservice.js', | 'app/scripts/services/identityservice.js', | ||||
'app/scripts/services/metricservice.js', | 'app/scripts/services/metricservice.js', | ||||
'app/scripts/services/param_flow_service.js', | |||||
]; | ]; | ||||
gulp.task('lib', function () { | gulp.task('lib', function () { | ||||
@@ -2220,7 +2220,7 @@ | |||||
}, | }, | ||||
"gulp-uglify": { | "gulp-uglify": { | ||||
"version": "3.0.1", | "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=", | "integrity": "sha1-jT7uRmUhvqaxD9dd/3Kt+LfqLZc=", | ||||
"dev": true, | "dev": true, | ||||
"requires": { | "requires": { | ||||
@@ -3017,14 +3017,14 @@ | |||||
"dev": true | "dev": true | ||||
}, | }, | ||||
"make-error": { | "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 | "dev": true | ||||
}, | }, | ||||
"make-error-cause": { | "make-error-cause": { | ||||
"version": "1.2.2", | "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=", | "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", | ||||
"dev": true, | "dev": true, | ||||
"requires": { | "requires": { | ||||
@@ -4659,24 +4659,24 @@ | |||||
} | } | ||||
}, | }, | ||||
"uglify-js": { | "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, | "dev": true, | ||||
"requires": { | "requires": { | ||||
"commander": "~2.16.0", | |||||
"commander": "~2.17.1", | |||||
"source-map": "~0.6.1" | "source-map": "~0.6.1" | ||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"commander": { | "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 | "dev": true | ||||
}, | }, | ||||
"source-map": { | "source-map": { | ||||
"version": "0.6.1", | "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=", | "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", | ||||
"dev": true | "dev": true | ||||
} | } | ||||