- Add `AuthController` and `SimpleWebAuthServiceImpl` - Update `AuthFilter` - Add a simple login page and frontend interceptor to support auth check and session storagemaster
@@ -20,12 +20,19 @@ mvn clean package | |||
```bash | |||
java -Dserver.port=8080 \ | |||
-Dserver.servlet.session.timeout=7200 \ | |||
-Dauth.username=sentinel \ | |||
-Dauth.password=123456 \ | |||
-Dcsp.sentinel.dashboard.server=localhost:8080 \ | |||
-Dproject.name=sentinel-dashboard \ | |||
-jar target/sentinel-dashboard.jar | |||
``` | |||
上述命令中我们指定几个 JVM 参数,其中 `-Dserver.port=8080` 用于指定 Spring Boot 启动端口为 `8080`,其余几个是 Sentinel 客户端的参数。 | |||
上述命令中我们指定几个 JVM 参数,其中: | |||
`-Dserver.port=8080` 用于指定 Spring Boot 启动端口为 `8080`; | |||
`-Dserver.servlet.session.timeout=7200` 用于指定 Spring Boot 服务器端会话的过期时间,如不带后缀的7200表示7200秒,60m表示60分钟,默认为30分钟; | |||
`-Dauth.username=sentinel`、 `-Dauth.password=123456` 用于指定控制台的登录用户和密码分别为sentinel和123456,如果省略这2个参数,默认用户和密码均为sentinel; | |||
其余几个是 Sentinel 客户端的参数。 | |||
为便于演示,我们对控制台本身加入了流量控制功能,具体做法是引入 `CommonFilter` 这个 Sentinel 拦截器。上述 JVM 参数的含义是: | |||
| 参数 | 作用 | | |||
@@ -55,6 +55,8 @@ Sentinel 提供了多种规则来保护系统的不同部分。流量控制规 | |||
项 | 类型 | 默认值 | 最小值 | 描述 | |||
--- | --- | --- | --- | --- | |||
sentinel.dashboard.auth.username | String | sentinel | 无 | 登录控制台的用户,默认sentinel | |||
sentinel.dashboard.auth.password | String | sentinel | 无 | 登录控制台的密码,默认sentinel | |||
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 | |||
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 | |||
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 | |||
@@ -0,0 +1,78 @@ | |||
/* | |||
* 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.alibaba.csp.sentinel.dashboard.auth; | |||
import org.springframework.context.annotation.Primary; | |||
import org.springframework.stereotype.Component; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpSession; | |||
/** | |||
* @author cdfive | |||
* @since 1.6.0 | |||
*/ | |||
@Primary | |||
@Component | |||
public class SimpleWebAuthServiceImpl implements AuthService<HttpServletRequest> { | |||
public static final String WEB_SESSTION_KEY = "session_sentinel_admin"; | |||
@Override | |||
public AuthUser getAuthUser(HttpServletRequest request) { | |||
HttpSession session = request.getSession(); | |||
Object sentinelUserObj = session.getAttribute(SimpleWebAuthServiceImpl.WEB_SESSTION_KEY); | |||
if (sentinelUserObj != null && sentinelUserObj instanceof AuthUser) { | |||
return (AuthUser) sentinelUserObj; | |||
} | |||
return null; | |||
} | |||
public static final class SimpleWebAuthUserImpl implements AuthUser { | |||
private String username; | |||
public SimpleWebAuthUserImpl(String username) { | |||
this.username = username; | |||
} | |||
@Override | |||
public boolean authTarget(String target, PrivilegeType privilegeType) { | |||
return true; | |||
} | |||
@Override | |||
public boolean isSuperUser() { | |||
return true; | |||
} | |||
@Override | |||
public String getNickName() { | |||
return username; | |||
} | |||
@Override | |||
public String getLoginName() { | |||
return username; | |||
} | |||
@Override | |||
public String getId() { | |||
return username; | |||
} | |||
} | |||
} |
@@ -37,6 +37,16 @@ public class DashboardConfig { | |||
public static final int DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS = 60_000; | |||
/** | |||
* Login username | |||
*/ | |||
public static final String CONFIG_AUTH_USERNAME = "sentinel.dashboard.auth.username"; | |||
/** | |||
* Login password | |||
*/ | |||
public static final String CONFIG_AUTH_PASSWORD = "sentinel.dashboard.auth.password"; | |||
/** | |||
* Hide application name in sidebar when it has no healthy machines after specific period in millisecond. | |||
*/ | |||
@@ -70,7 +80,22 @@ public class DashboardConfig { | |||
} | |||
return ""; | |||
} | |||
protected static String getConfigStr(String name) { | |||
if (cacheMap.containsKey(name)) { | |||
return (String) cacheMap.get(name); | |||
} | |||
String val = getConfig(name); | |||
if (StringUtils.isBlank(val)) { | |||
return null; | |||
} | |||
cacheMap.put(name, val); | |||
return val; | |||
} | |||
protected static int getConfigInt(String name, int defaultVal, int minVal) { | |||
if (cacheMap.containsKey(name)) { | |||
return (int)cacheMap.get(name); | |||
@@ -84,7 +109,15 @@ public class DashboardConfig { | |||
cacheMap.put(name, val); | |||
return val; | |||
} | |||
public static String getAuthUsername() { | |||
return getConfigStr(CONFIG_AUTH_USERNAME); | |||
} | |||
public static String getAuthPassword() { | |||
return getConfigStr(CONFIG_AUTH_PASSWORD); | |||
} | |||
public static int getHideAppNoMachineMillis() { | |||
return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000); | |||
} | |||
@@ -15,21 +15,8 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.config; | |||
import java.io.IOException; | |||
import java.io.PrintWriter; | |||
import javax.servlet.Filter; | |||
import javax.servlet.FilterChain; | |||
import javax.servlet.FilterConfig; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.ServletRequest; | |||
import javax.servlet.ServletResponse; | |||
import javax.servlet.http.HttpServletRequest; | |||
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.AuthUser; | |||
import com.alibaba.csp.sentinel.dashboard.filter.AuthFilter; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
@@ -40,6 +27,8 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry | |||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; | |||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |||
import javax.servlet.Filter; | |||
/** | |||
* @author leyou | |||
*/ | |||
@@ -49,7 +38,7 @@ public class WebConfig implements WebMvcConfigurer { | |||
private final Logger logger = LoggerFactory.getLogger(WebConfig.class); | |||
@Autowired | |||
private AuthService<HttpServletRequest> authService; | |||
private AuthFilter authFilter; | |||
@Override | |||
public void addResourceHandlers(ResourceHandlerRegistry registry) { | |||
@@ -81,29 +70,7 @@ public class WebConfig implements WebMvcConfigurer { | |||
@Bean | |||
public FilterRegistrationBean authenticationFilterRegistration() { | |||
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); | |||
registration.setFilter(new Filter() { | |||
@Override | |||
public void init(FilterConfig filterConfig) throws ServletException { } | |||
@Override | |||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, | |||
FilterChain filterChain) throws IOException, ServletException { | |||
HttpServletRequest request = (HttpServletRequest)servletRequest; | |||
AuthUser authUser = authService.getAuthUser(request); | |||
// authentication fail | |||
if (authUser == null) { | |||
PrintWriter writer = servletResponse.getWriter(); | |||
writer.append("login needed"); | |||
writer.flush(); | |||
} else { | |||
filterChain.doFilter(servletRequest, servletResponse); | |||
} | |||
} | |||
@Override | |||
public void destroy() { } | |||
}); | |||
registration.setFilter(authFilter); | |||
registration.addUrlPatterns("/*"); | |||
registration.setName("authenticationFilter"); | |||
registration.setOrder(0); | |||
@@ -0,0 +1,80 @@ | |||
/* | |||
* 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.alibaba.csp.sentinel.dashboard.controller; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||
import com.alibaba.csp.sentinel.dashboard.auth.SimpleWebAuthServiceImpl; | |||
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; | |||
import com.alibaba.csp.sentinel.dashboard.domain.Result; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Value; | |||
import org.springframework.http.MediaType; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RequestMethod; | |||
import org.springframework.web.bind.annotation.RestController; | |||
import javax.servlet.http.HttpServletRequest; | |||
/** | |||
* @author cdfive | |||
* @since 1.6.0 | |||
*/ | |||
@RestController | |||
@RequestMapping(value = "/auth", produces = MediaType.APPLICATION_JSON_VALUE) | |||
public class AuthController { | |||
private static Logger LOGGER = LoggerFactory.getLogger(AuthController.class); | |||
@Value("${auth.username:sentinel}") | |||
private String authUsername; | |||
@Value("${auth.password:sentinel}") | |||
private String authPassword; | |||
@RequestMapping(value = "/login", method = RequestMethod.POST) | |||
public Result login(HttpServletRequest request, String username, String password) { | |||
if (StringUtils.isNotBlank(DashboardConfig.getAuthUsername())) { | |||
authUsername = DashboardConfig.getAuthUsername(); | |||
} | |||
if (StringUtils.isNotBlank(DashboardConfig.getAuthPassword())) { | |||
authPassword = DashboardConfig.getAuthPassword(); | |||
} | |||
/** | |||
* If auth.username or auth.password is blank(set in application.properties or VM arguments), | |||
* auth will pass, as the front side validate the input which can't be blank, | |||
* so user can input any username or password(both are not blank) to login in that case. | |||
*/ | |||
if ( StringUtils.isNotBlank(authUsername) && !authUsername.equals(username) | |||
|| StringUtils.isNotBlank(authPassword) && !authPassword.equals(password)) { | |||
LOGGER.error("Login failed: Invalid username or password, username=" + username + ", password=" + password); | |||
return Result.ofFail(-1, "Invalid username or password"); | |||
} | |||
AuthService.AuthUser authUser = new SimpleWebAuthServiceImpl.SimpleWebAuthUserImpl(username); | |||
request.getSession().setAttribute(SimpleWebAuthServiceImpl.WEB_SESSTION_KEY, authUser); | |||
return Result.ofSuccess(authUser); | |||
} | |||
@RequestMapping(value = "/logout", method = RequestMethod.POST) | |||
public Result logout(HttpServletRequest request) { | |||
request.getSession().invalidate(); | |||
return Result.ofSuccess(null); | |||
} | |||
} |
@@ -0,0 +1,118 @@ | |||
/* | |||
* 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.alibaba.csp.sentinel.dashboard.filter; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.beans.factory.annotation.Value; | |||
import org.springframework.http.HttpStatus; | |||
import org.springframework.stereotype.Component; | |||
import javax.servlet.Filter; | |||
import javax.servlet.FilterChain; | |||
import javax.servlet.FilterConfig; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.ServletRequest; | |||
import javax.servlet.ServletResponse; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.List; | |||
/** | |||
* Servlet Filter that authenticate requests. | |||
* | |||
* Note: | |||
* Some urls are excluded as they needn't auth, such as: | |||
* | |||
* Index url: / | |||
* Authentication request url: /login,logout | |||
* Used for client: /registry/machine | |||
* Static resources: htm,html,js and so on. | |||
* | |||
* The excluded urls and urlSuffixes are configured in application.properties | |||
* | |||
* @author cdfive | |||
* @since 1.6.0 | |||
*/ | |||
@Component | |||
public class AuthFilter implements Filter { | |||
private static final String URL_SUFFIX_DOT = "."; | |||
/**Some urls which needn't auth, such as /auth/login,/registry/machine and so on*/ | |||
@Value("#{'${auth.filter.exclude-urls}'.split(',')}") | |||
private List<String> authFilterExcludeUrls; | |||
/**Some urls with suffixes which needn't auth, such as htm,html,js and so on*/ | |||
@Value("#{'${auth.filter.exclude-url-suffixes}'.split(',')}") | |||
private List<String> authFilterExcludeUrlSuffixes; | |||
/**Authentication using AuthService interface*/ | |||
@Autowired | |||
private AuthService<HttpServletRequest> authService; | |||
@Override | |||
public void init(FilterConfig filterConfig) throws ServletException { | |||
} | |||
@Override | |||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | |||
HttpServletRequest httpRequest = (HttpServletRequest) request; | |||
String requestURI = httpRequest.getRequestURI(); | |||
// Exclude the urls which needn't auth | |||
if (authFilterExcludeUrls.contains(requestURI)) { | |||
chain.doFilter(request, response); | |||
return; | |||
} | |||
// Exclude the urls with suffixes which needn't auth | |||
for (String authFilterExcludeUrlSuffix : authFilterExcludeUrlSuffixes) { | |||
if (StringUtils.isBlank(authFilterExcludeUrlSuffix)) { | |||
continue; | |||
} | |||
// Add . for url suffix so that we needn't add . in property file | |||
if (!authFilterExcludeUrlSuffix.startsWith(URL_SUFFIX_DOT)) { | |||
authFilterExcludeUrlSuffix = URL_SUFFIX_DOT + authFilterExcludeUrlSuffix; | |||
} | |||
if (requestURI.endsWith(authFilterExcludeUrlSuffix)) { | |||
chain.doFilter(request, response); | |||
return; | |||
} | |||
} | |||
AuthService.AuthUser authUser = authService.getAuthUser(httpRequest); | |||
HttpServletResponse httpResponse = (HttpServletResponse) response; | |||
if (authUser == null) { | |||
// If auth fail, set response status code to 401 | |||
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); | |||
} else { | |||
chain.doFilter(request, response); | |||
} | |||
} | |||
@Override | |||
public void destroy() { | |||
} | |||
} |
@@ -8,3 +8,9 @@ logging.level.org.springframework.web=INFO | |||
logging.file=${user.home}/logs/csp/sentinel-dashboard.log | |||
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n | |||
#logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n | |||
#auth settings | |||
auth.filter.exclude-urls=/,/auth/login,/auth/logout,/registry/machine | |||
auth.filter.exclude-url-suffixes=htm,html,js,css,map,ico,ttf,woff | |||
auth.username=sentinel | |||
auth.password=sentinel |
@@ -23,16 +23,57 @@ angular | |||
'selectize', | |||
'angularUtils.directives.dirPagination' | |||
]) | |||
.config(['$stateProvider', '$urlRouterProvider', '$ocLazyLoadProvider', | |||
function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) { | |||
$ocLazyLoadProvider.config({ | |||
debug: false, | |||
events: true, | |||
}); | |||
.factory('AuthInterceptor', ['$window', '$state', function ($window, $state) { | |||
var authInterceptor = { | |||
'responseError' : function(response) { | |||
if (response.status == 401) { | |||
// If not auth, clear session in localStorage and jump to the login page | |||
$window.localStorage.removeItem("session_sentinel_admin"); | |||
$state.go('login'); | |||
} | |||
return response; | |||
}, | |||
'response' : function(response) { | |||
return response; | |||
}, | |||
'request' : function(config) { | |||
return config; | |||
}, | |||
'requestError' : function(config){ | |||
return config; | |||
} | |||
}; | |||
return authInterceptor; | |||
}]) | |||
.config(['$stateProvider', '$urlRouterProvider', '$ocLazyLoadProvider', '$httpProvider', | |||
function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider, $httpProvider) { | |||
$httpProvider.interceptors.push('AuthInterceptor'); | |||
$ocLazyLoadProvider.config({ | |||
debug: false, | |||
events: true, | |||
}); | |||
$urlRouterProvider.otherwise('/dashboard/home'); | |||
$urlRouterProvider.otherwise('/dashboard/home'); | |||
$stateProvider | |||
.state('login', { | |||
url: '/login', | |||
templateUrl: 'app/views/login.html', | |||
controller: 'LoginCtl', | |||
resolve: { | |||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { | |||
return $ocLazyLoad.load({ | |||
name: 'sentinelDashboardApp', | |||
files: [ | |||
'app/scripts/controllers/login.js', | |||
] | |||
}); | |||
}] | |||
} | |||
}) | |||
$stateProvider | |||
.state('dashboard', { | |||
url: '/dashboard', | |||
templateUrl: 'app/views/dashboard/main.html', | |||
@@ -0,0 +1,36 @@ | |||
var app = angular.module('sentinelDashboardApp'); | |||
app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService', | |||
function ($scope, $state, $window, LoginService) { | |||
// If auth, jump to the index page directly | |||
if ($window.localStorage.getItem('session_sentinel_admin')) { | |||
$state.go('dashboard'); | |||
} | |||
$scope.login = function () { | |||
if (!$scope.username) { | |||
alert('请输入用户名'); | |||
return; | |||
} | |||
if (!$scope.password) { | |||
alert('请输入密码'); | |||
return; | |||
} | |||
var param = {"username": $scope.username, "password": $scope.password}; | |||
LoginService.login(param).success(function (data) { | |||
if (data.code == 0) { | |||
$window.localStorage.setItem('session_sentinel_admin', { | |||
username: data.data | |||
}); | |||
$state.go('dashboard'); | |||
} else { | |||
alert(data.msg); | |||
} | |||
}); | |||
}; | |||
}] | |||
); |
@@ -3,6 +3,11 @@ | |||
<div class="navbar-brand"> | |||
<span style="color: #fff;font-size: 26px;">Sentinel 控制台</span> | |||
</div> | |||
<ul class="nav navbar-nav navbar-right"> | |||
<li> | |||
<a href="javascript:void(0);" ng-click="logout()" style="margin: 3px 15px 0px 0px;"><span class="glyphicon glyphicon-log-out"></span>退出</a> | |||
</li> | |||
</ul> | |||
</nav> | |||
<!-- end nav --> | |||
<sidebar></sidebar> |
@@ -5,12 +5,26 @@ | |||
* # adminPosHeader | |||
*/ | |||
angular.module('sentinelDashboardApp') | |||
.directive('header', [function () { | |||
.directive('header', ['AuthService', function () { | |||
return { | |||
templateUrl: 'app/scripts/directives/header/header.html', | |||
restrict: 'E', | |||
replace: true, | |||
controller: function ($scope) { | |||
controller: function ($scope, $state, $window, AuthService) { | |||
if (!$window.localStorage.getItem('session_sentinel_admin')) { | |||
$state.go('login'); | |||
} | |||
$scope.logout = function () { | |||
AuthService.logout().success(function (data) { | |||
if (data.code == 0) { | |||
$window.localStorage.removeItem("session_sentinel_admin"); | |||
$state.go('login'); | |||
} else { | |||
alert('logout error'); | |||
} | |||
}); | |||
} | |||
} | |||
} | |||
}]); |
@@ -0,0 +1,18 @@ | |||
var app = angular.module('sentinelDashboardApp'); | |||
app.service('AuthService', ['$http', function ($http) { | |||
this.login = function (param) { | |||
return $http({ | |||
url: '/auth/login', | |||
params: param, | |||
method: 'POST' | |||
}) | |||
} | |||
this.logout = function () { | |||
return $http({ | |||
url: '/auth/logout', | |||
method: 'POST' | |||
}) | |||
} | |||
}]); |
@@ -0,0 +1,29 @@ | |||
<div class="container"> | |||
<div class="row" style="margin: 200px auto 15px auto; display: table;"> | |||
<h1 id='login_title'>Sentinel控制台</h1> | |||
</div> | |||
<div class="row"> | |||
<div class="col-md-4" > | |||
</div> | |||
<div class="col-md-4"> | |||
<form class="form-horizontal"> | |||
<div class="form-group"> | |||
<label class="col-md-2 control-label">用户</label> | |||
<div class="col-md-9"> | |||
<input class="form-control" type="text" ng-model="username" autofocus="autofocus"/> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<label class="col-md-2 control-label">密码</label> | |||
<div class="col-md-9"> | |||
<input class="form-control" type="password" ng-model="password" /> | |||
</div> | |||
</div> | |||
<div class="form-group btn-group" style="margin: 0px auto;display: table;"> | |||
<button class="btn btn-success btn-primary" ng-click="login()">登录</button> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</div> |
@@ -43,6 +43,7 @@ const CSS_APP = [ | |||
const JS_APP = [ | |||
'app/scripts/app.js', | |||
'app/scripts/filters/filters.js', | |||
'app/scripts/services/auth_service.js', | |||
'app/scripts/services/appservice.js', | |||
'app/scripts/services/flow_service_v1.js', | |||
'app/scripts/services/flow_service_v2.js', | |||
@@ -24,11 +24,34 @@ import org.junit.contrib.java.lang.system.EnvironmentVariables; | |||
public class DashboardConfigTest { | |||
@Rule | |||
public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); | |||
@Test | |||
public void testGetConfigStr() { | |||
// clear cache | |||
DashboardConfig.clearCache(); | |||
// if not set, return null | |||
assertEquals(null, DashboardConfig.getConfigStr("a")); | |||
// test property | |||
System.setProperty("a", "111"); | |||
assertEquals("111", DashboardConfig.getConfigStr("a")); | |||
// test env | |||
environmentVariables.set("a", "222"); | |||
// return value in cache | |||
assertEquals("111", DashboardConfig.getConfigStr("a")); | |||
// clear cache and then test | |||
DashboardConfig.clearCache(); | |||
assertEquals("222", DashboardConfig.getConfigStr("a")); | |||
} | |||
@Test | |||
public void testGetConfigInt() { | |||
// skip cache | |||
// clear cache | |||
DashboardConfig.clearCache(); | |||
// default value | |||
assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); | |||
DashboardConfig.clearCache(); | |||