Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
8fd1ddbf2b
|
@ -40,6 +40,8 @@ public class MsJSR223Processor extends MsTestElement {
|
|||
if (StringUtils.isNotEmpty(name) && !config.isOperating()) {
|
||||
processor.setName(this.getName() + "<->" + name);
|
||||
}
|
||||
processor.setProperty("MS-ID", this.getId());
|
||||
|
||||
processor.setProperty(TestElement.TEST_CLASS, JSR223Sampler.class.getName());
|
||||
processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
|
||||
processor.setProperty("cacheKey", "true");
|
||||
|
|
|
@ -82,6 +82,7 @@ public class MsDubboSampler extends MsTestElement {
|
|||
}
|
||||
sampler.setProperty(TestElement.TEST_CLASS, DubboSample.class.getName());
|
||||
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("DubboSampleGui"));
|
||||
sampler.setProperty("MS-ID", this.getId());
|
||||
|
||||
sampler.addTestElement(configCenter(this.getConfigCenter()));
|
||||
sampler.addTestElement(registryCenter(this.getRegistryCenter()));
|
||||
|
|
|
@ -106,6 +106,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
|
||||
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui"));
|
||||
sampler.setProperty("MS-ID", this.getId());
|
||||
sampler.setMethod(this.getMethod());
|
||||
sampler.setContentEncoding("UTF-8");
|
||||
sampler.setConnectTimeout(this.getConnectTimeout() == null ? "6000" : this.getConnectTimeout());
|
||||
|
|
|
@ -119,6 +119,8 @@ public class MsJDBCSampler extends MsTestElement {
|
|||
}
|
||||
sampler.setProperty(TestElement.TEST_CLASS, JDBCSampler.class.getName());
|
||||
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
|
||||
sampler.setProperty("MS-ID", this.getId());
|
||||
|
||||
// request.getDataSource() 是ID,需要转换为Name
|
||||
sampler.setProperty("dataSource", this.dataSource.getName());
|
||||
sampler.setProperty("query", this.getQuery());
|
||||
|
|
|
@ -117,6 +117,7 @@ public class MsTCPSampler extends MsTestElement {
|
|||
if (StringUtils.isNotEmpty(name) && !config.isOperating()) {
|
||||
tcpSampler.setName(this.getName() + "<->" + name);
|
||||
}
|
||||
tcpSampler.setProperty("MS-ID", this.getId());
|
||||
|
||||
tcpSampler.setProperty(TestElement.TEST_CLASS, TCPSampler.class.getName());
|
||||
tcpSampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TCPSamplerGui"));
|
||||
|
|
|
@ -239,7 +239,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
sendTask(report, reportUrl, testResult);
|
||||
sendTask(report, reportUrl, testResult);
|
||||
}
|
||||
|
||||
private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) {
|
||||
|
@ -303,6 +303,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
|
||||
private RequestResult getRequestResult(SampleResult result) {
|
||||
RequestResult requestResult = new RequestResult();
|
||||
requestResult.setId(result.getSamplerId());
|
||||
requestResult.setName(result.getSampleLabel());
|
||||
requestResult.setUrl(result.getUrlAsString());
|
||||
requestResult.setMethod(getMethod(result));
|
||||
|
|
|
@ -7,6 +7,8 @@ import java.util.List;
|
|||
|
||||
@Data
|
||||
public class RequestResult {
|
||||
// 请求ID
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
package io.metersphere.commons.user;
|
||||
|
||||
import io.metersphere.commons.utils.CodingUtil;
|
||||
import io.metersphere.dto.UserDTO;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class SessionUser extends UserDTO implements Serializable {
|
||||
public static final String secret = "9a9rdqPlTqhpZzkq";
|
||||
public static final String iv = "1Av7hf9PgHusUHRm";
|
||||
|
||||
private static final long serialVersionUID = -7149638440406959033L;
|
||||
private String csrfToken;
|
||||
|
||||
private SessionUser() {
|
||||
}
|
||||
|
||||
public static SessionUser fromUser(UserDTO user) {
|
||||
SessionUser sessionUser = new SessionUser();
|
||||
BeanUtils.copyProperties(user, sessionUser);
|
||||
|
||||
List<String> infos = Arrays.asList(user.getId(), RandomStringUtils.random(6), "" + System.currentTimeMillis());
|
||||
sessionUser.csrfToken = CodingUtil.aesEncrypt(StringUtils.join(infos, "|"), secret, iv);
|
||||
return sessionUser;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,14 +62,14 @@ public class SessionUtils {
|
|||
}
|
||||
|
||||
public static String getCurrentWorkspaceId() {
|
||||
return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastWorkspaceId();
|
||||
return getUser().getLastWorkspaceId();
|
||||
}
|
||||
|
||||
public static String getCurrentOrganizationId() {
|
||||
return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastOrganizationId();
|
||||
return getUser().getLastOrganizationId();
|
||||
}
|
||||
|
||||
public static String getCurrentProjectId() {
|
||||
return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastProjectId();
|
||||
return getUser().getLastProjectId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@ public class ShiroUtils {
|
|||
// filterChainDefinitionMap.put("/document/**", "anon");
|
||||
}
|
||||
|
||||
public static void ignoreCsrfFilter(Map<String, String> filterChainDefinitionMap) {
|
||||
filterChainDefinitionMap.put("/", "apikey, authc"); // 跳转到 / 不用校验 csrf
|
||||
}
|
||||
|
||||
public static Cookie getSessionIdCookie(){
|
||||
SimpleCookie sessionIdCookie = new SimpleCookie();
|
||||
sessionIdCookie.setPath("/");
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.config;
|
|||
|
||||
import io.metersphere.commons.utils.ShiroUtils;
|
||||
import io.metersphere.security.ApiKeyFilter;
|
||||
import io.metersphere.security.CsrfFilter;
|
||||
import io.metersphere.security.UserModularRealmAuthenticator;
|
||||
import io.metersphere.security.realm.LdapRealm;
|
||||
import io.metersphere.security.realm.ShiroDBRealm;
|
||||
|
@ -44,10 +45,14 @@ public class ShiroConfig implements EnvironmentAware {
|
|||
shiroFilterFactoryBean.setSuccessUrl("/");
|
||||
|
||||
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
|
||||
shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
|
||||
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
|
||||
|
||||
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
|
||||
|
||||
filterChainDefinitionMap.put("/**", "apikey, authc");
|
||||
ShiroUtils.ignoreCsrfFilter(filterChainDefinitionMap);
|
||||
|
||||
filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.metersphere.controller;
|
||||
|
||||
import io.metersphere.commons.constants.SsoMode;
|
||||
import io.metersphere.commons.constants.UserSource;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
|
@ -10,7 +9,6 @@ import io.metersphere.service.UserService;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
@ -24,8 +22,6 @@ public class LoginController {
|
|||
@Resource
|
||||
private UserService userService;
|
||||
@Resource
|
||||
private Environment env;
|
||||
@Resource
|
||||
private BaseDisplayService baseDisplayService;
|
||||
|
||||
@GetMapping(value = "/isLogin")
|
||||
|
@ -37,10 +33,6 @@ public class LoginController {
|
|||
}
|
||||
return ResultHolder.success(user);
|
||||
}
|
||||
String ssoMode = env.getProperty("sso.mode");
|
||||
if (ssoMode != null && StringUtils.equalsIgnoreCase(SsoMode.CAS.name(), ssoMode)) {
|
||||
return ResultHolder.error("sso");
|
||||
}
|
||||
return ResultHolder.error("");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package io.metersphere.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.Organization;
|
||||
import io.metersphere.base.domain.User;
|
||||
import io.metersphere.base.domain.Workspace;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
|
@ -29,7 +26,6 @@ import io.metersphere.service.WorkspaceService;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
|
|
@ -14,8 +14,7 @@ public class EngineContext {
|
|||
private String reportId;
|
||||
private Integer resourceIndex;
|
||||
private Map<String, Object> properties = new HashMap<>();
|
||||
private Map<String, String> testData = new HashMap<>();
|
||||
private Map<String, byte[]> testJars = new HashMap<>();
|
||||
private Map<String, byte[]> testResourceFiles = new HashMap<>();
|
||||
|
||||
public String getTestId() {
|
||||
return testId;
|
||||
|
@ -69,14 +68,6 @@ public class EngineContext {
|
|||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
public Map<String, String> getTestData() {
|
||||
return testData;
|
||||
}
|
||||
|
||||
public void setTestData(Map<String, String> testData) {
|
||||
this.testData = testData;
|
||||
}
|
||||
|
||||
public String getResourcePoolId() {
|
||||
return resourcePoolId;
|
||||
}
|
||||
|
@ -111,11 +102,11 @@ public class EngineContext {
|
|||
}
|
||||
|
||||
|
||||
public Map<String, byte[]> getTestJars() {
|
||||
return testJars;
|
||||
public Map<String, byte[]> getTestResourceFiles() {
|
||||
return testResourceFiles;
|
||||
}
|
||||
|
||||
public void setTestJars(Map<String, byte[]> testJars) {
|
||||
this.testJars = testJars;
|
||||
public void setTestResourceFiles(Map<String, byte[]> testResourceFiles) {
|
||||
this.testResourceFiles = testResourceFiles;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,8 +92,7 @@ public class EngineFactory {
|
|||
}
|
||||
|
||||
List<FileMetadata> jmxFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name())).collect(Collectors.toList());
|
||||
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
|
||||
List<FileMetadata> jarFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JAR.name())).collect(Collectors.toList());
|
||||
List<FileMetadata> resourceFiles = fileMetadataList.stream().filter(f -> !StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name())).collect(Collectors.toList());
|
||||
// 合并上传的jmx
|
||||
byte[] jmxBytes = mergeJmx(jmxFiles);
|
||||
final EngineContext engineContext = new EngineContext();
|
||||
|
@ -156,22 +155,13 @@ public class EngineFactory {
|
|||
MSException.throwException(e);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(csvFiles)) {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
csvFiles.forEach(cf -> {
|
||||
FileContent csvContent = fileService.getFileContent(cf.getId());
|
||||
data.put(cf.getName(), new String(csvContent.getFile()));
|
||||
});
|
||||
engineContext.setTestData(data);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(jarFiles)) {
|
||||
if (CollectionUtils.isNotEmpty(resourceFiles)) {
|
||||
Map<String, byte[]> data = new HashMap<>();
|
||||
jarFiles.forEach(jf -> {
|
||||
FileContent content = fileService.getFileContent(jf.getId());
|
||||
data.put(jf.getName(), content.getFile());
|
||||
resourceFiles.forEach(cf -> {
|
||||
FileContent csvContent = fileService.getFileContent(cf.getId());
|
||||
data.put(cf.getName(), csvContent.getFile());
|
||||
});
|
||||
engineContext.setTestJars(data);
|
||||
engineContext.setTestResourceFiles(data);
|
||||
}
|
||||
|
||||
return engineContext;
|
||||
|
|
|
@ -51,17 +51,9 @@ public class JmeterFileService {
|
|||
|
||||
// 每个测试生成一个文件夹
|
||||
files.put(fileName, context.getContent().getBytes(StandardCharsets.UTF_8));
|
||||
// 保存测试数据文件
|
||||
Map<String, String> testData = context.getTestData();
|
||||
if (!CollectionUtils.isEmpty(testData)) {
|
||||
for (String k : testData.keySet()) {
|
||||
String v = testData.get(k);
|
||||
files.put(k, v.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
// 保存 byte[] jar
|
||||
Map<String, byte[]> jarFiles = context.getTestJars();
|
||||
// 保存 byte[]
|
||||
Map<String, byte[]> jarFiles = context.getTestResourceFiles();
|
||||
if (!CollectionUtils.isEmpty(jarFiles)) {
|
||||
for (String k : jarFiles.keySet()) {
|
||||
byte[] v = jarFiles.get(k);
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package io.metersphere.security;
|
||||
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.CodingUtil;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.web.filter.authc.AnonymousFilter;
|
||||
import org.apache.shiro.web.util.WebUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class CsrfFilter extends AnonymousFilter {
|
||||
private static final String TOKEN_NAME = "CSRF-TOKEN";
|
||||
|
||||
@Override
|
||||
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
|
||||
|
||||
if (!SecurityUtils.getSubject().isAuthenticated()) {
|
||||
((HttpServletResponse) response).setHeader("Authentication-Status", "invalid");
|
||||
return true;
|
||||
}
|
||||
// api 过来的请求
|
||||
if (ApiKeyHandler.isApiKeyCall(WebUtils.toHttp(request))) {
|
||||
return true;
|
||||
}
|
||||
// websocket 不需要csrf
|
||||
String websocketKey = httpServletRequest.getHeader("Sec-WebSocket-Key");
|
||||
if (StringUtils.isNotBlank(websocketKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 请求头取出的token value
|
||||
String csrfToken = httpServletRequest.getHeader(TOKEN_NAME);
|
||||
// 校验 token
|
||||
validateToken(csrfToken);
|
||||
// 校验 referer
|
||||
validateReferer(httpServletRequest);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void validateReferer(HttpServletRequest request) {
|
||||
Environment env = CommonBeanFactory.getBean(Environment.class);
|
||||
String domains = env.getProperty("referer.urls");
|
||||
if (StringUtils.isBlank(domains)) {
|
||||
// 没有配置不校验
|
||||
return;
|
||||
}
|
||||
|
||||
String[] split = StringUtils.split(domains, ",");
|
||||
String referer = request.getHeader(HttpHeaders.REFERER);
|
||||
if (split != null) {
|
||||
if (!ArrayUtils.contains(split, referer)) {
|
||||
throw new RuntimeException("csrf error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateToken(String csrfToken) {
|
||||
if (StringUtils.isBlank(csrfToken)) {
|
||||
throw new RuntimeException("csrf token is empty");
|
||||
}
|
||||
csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv);
|
||||
|
||||
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|");
|
||||
if (signatureArray.length != 3) {
|
||||
throw new RuntimeException("invalid token");
|
||||
}
|
||||
|
||||
long signatureTime;
|
||||
try {
|
||||
signatureTime = Long.parseLong(signatureArray[2]);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Environment env = CommonBeanFactory.getBean(Environment.class);
|
||||
long timeout = env.getProperty("session.timeout", Long.class, 43200L);
|
||||
if (Math.abs(System.currentTimeMillis() - signatureTime) > timeout * 1000) {
|
||||
throw new RuntimeException("expired token");
|
||||
}
|
||||
if (!StringUtils.equals(SessionUtils.getUserId(), signatureArray[0])) {
|
||||
throw new RuntimeException("Please check csrf token.");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -30,6 +30,7 @@ import MsUser from "./components/common/head/HeaderUser";
|
|||
import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs";
|
||||
import MsLanguageSwitch from "./components/common/head/LanguageSwitch";
|
||||
import {saveLocalStorage} from "@/common/js/utils";
|
||||
import {registerRequestHeaders} from "@/common/js/ajax";
|
||||
|
||||
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
|
||||
const header = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : {};
|
||||
|
@ -53,6 +54,7 @@ export default {
|
|||
window.addEventListener("beforeunload", () => {
|
||||
localStorage.setItem("store", JSON.stringify(this.$store.state))
|
||||
})
|
||||
registerRequestHeaders();
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$get("/isLogin").then(response => {
|
||||
|
|
|
@ -100,6 +100,19 @@
|
|||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
formatResult(res) {
|
||||
let resMap = new Map;
|
||||
if (res && res.scenarios) {
|
||||
res.scenarios.forEach(item => {
|
||||
if (item && item.requestResults) {
|
||||
item.requestResults.forEach(req => {
|
||||
resMap.set(req.id, req);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.$emit('refresh', resMap);
|
||||
},
|
||||
getReport() {
|
||||
this.init();
|
||||
if (this.reportId) {
|
||||
|
@ -113,7 +126,7 @@
|
|||
if (!this.content) {
|
||||
this.content = {scenarios: []};
|
||||
}
|
||||
this.$emit('refresh');
|
||||
this.formatResult(this.content);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@
|
|||
<!-- 调试结果 -->
|
||||
<el-drawer v-if="type!=='detail'" :visible.sync="debugVisible" :destroy-on-close="true" direction="ltr"
|
||||
:withHeader="true" :modal="false" size="90%">
|
||||
<ms-api-report-detail :report-id="reportId" :debug="true" :currentProjectId="projectId"/>
|
||||
<ms-api-report-detail :report-id="reportId" :debug="true" :currentProjectId="projectId" @refresh="detailRefresh"/>
|
||||
</el-drawer>
|
||||
|
||||
<!--场景公共参数-->
|
||||
|
@ -291,7 +291,8 @@
|
|||
response: {},
|
||||
projectIds: new Set,
|
||||
projectEnvMap: new Map,
|
||||
projectList: []
|
||||
projectList: [],
|
||||
debugResult: new Map,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -548,21 +549,32 @@
|
|||
if (arr[i].hashTree != undefined && arr[i].hashTree.length > 0) {
|
||||
this.recursiveSorting(arr[i].hashTree);
|
||||
}
|
||||
// 添加debug结果
|
||||
if (this.debugResult && this.debugResult.get(arr[i].id)) {
|
||||
arr[i].requestResult = this.debugResult.get(arr[i].id);
|
||||
}
|
||||
}
|
||||
},
|
||||
sort() {
|
||||
for (let i in this.scenarioDefinition) {
|
||||
// 排序
|
||||
this.scenarioDefinition[i].index = Number(i) + 1;
|
||||
// 设置循环控制
|
||||
if (this.scenarioDefinition[i].type === ELEMENT_TYPE.LoopController && this.scenarioDefinition[i].hashTree
|
||||
&& this.scenarioDefinition[i].hashTree.length > 1) {
|
||||
this.scenarioDefinition[i].countController.proceed = true;
|
||||
}
|
||||
// 设置项目ID
|
||||
if (!this.scenarioDefinition[i].projectId) {
|
||||
this.scenarioDefinition[i].projectId = getCurrentProjectID();
|
||||
}
|
||||
if (this.scenarioDefinition[i].hashTree != undefined && this.scenarioDefinition[i].hashTree.length > 0) {
|
||||
this.recursiveSorting(this.scenarioDefinition[i].hashTree);
|
||||
}
|
||||
// 添加debug结果
|
||||
if (this.debugResult && this.debugResult.get(this.scenarioDefinition[i].id)) {
|
||||
this.scenarioDefinition[i].requestResult = this.debugResult.get(this.scenarioDefinition[i].id);
|
||||
}
|
||||
}
|
||||
},
|
||||
addCustomizeApi(request) {
|
||||
|
@ -576,6 +588,7 @@
|
|||
this.customizeRequest = {};
|
||||
this.sort();
|
||||
this.reload();
|
||||
this.initProjectIds();
|
||||
},
|
||||
addScenario(arr) {
|
||||
if (arr && arr.length > 0) {
|
||||
|
@ -1025,6 +1038,11 @@
|
|||
arr.forEach(a => this.projectIds.add(a));
|
||||
})
|
||||
})
|
||||
},
|
||||
detailRefresh(result) {
|
||||
// 把执行结果分发给各个请求
|
||||
this.debugResult = result;
|
||||
this.sort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,7 +112,17 @@ export default {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
sign = false;
|
||||
// 如果有环境,检查环境
|
||||
if (this.envMap && this.envMap.size > 0) {
|
||||
this.projectIds.forEach(id => {
|
||||
if (!this.envMap.get(id)) {
|
||||
sign = false;
|
||||
return false;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
sign = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sign) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -117,6 +117,36 @@
|
|||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div>
|
||||
{{ $t('load_test.granularity') }}
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
width="400"
|
||||
trigger="hover">
|
||||
<el-table :data="granularityData">
|
||||
<el-table-column property="start" :label="$t('load_test.duration')">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.start }} - {{ scope.row.end }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="granularity" :label="$t('load_test.granularity')"/>
|
||||
</el-table>
|
||||
<i slot="reference" class="el-icon-info pointer"/>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="granularity" :placeholder="$t('commons.please_select')" size="mini" clearable>
|
||||
<el-option v-for="op in granularityData" :key="op.granularity" :label="op.granularity" :value="op.granularity"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -134,6 +164,18 @@ export default {
|
|||
domains: [],
|
||||
params: [],
|
||||
statusCodeStr: '',
|
||||
granularity: undefined,
|
||||
granularityData: [
|
||||
{start: 0, end: 100, granularity: 1},
|
||||
{start: 101, end: 500, granularity: 5},
|
||||
{start: 501, end: 1000, granularity: 10},
|
||||
{start: 1001, end: 3000, granularity: 30},
|
||||
{start: 3001, end: 6000, granularity: 60},
|
||||
{start: 6001, end: 30000, granularity: 300},
|
||||
{start: 30001, end: 60000, granularity: 600},
|
||||
{start: 60001, end: 180000, granularity: 1800},
|
||||
{start: 180001, end: 360000, granularity: 3600},
|
||||
]
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -166,6 +208,7 @@ export default {
|
|||
this.statusCodeStr = this.statusCode.join(',');
|
||||
this.domains = data.domains || [];
|
||||
this.params = data.params || [];
|
||||
this.granularity = data.granularity;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -252,6 +295,7 @@ export default {
|
|||
statusCode: statusCode,
|
||||
params: this.params,
|
||||
domains: this.domains,
|
||||
granularity: this.granularity,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
@ -287,4 +331,8 @@ export default {
|
|||
align: center;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<el-row type="flex" justify="start" align="middle">
|
||||
<el-upload
|
||||
style="padding-right: 10px;"
|
||||
accept=".jar,.csv"
|
||||
accept=".jar,.csv,.json,.pdf,.jpg,.png,.jpeg,.doc,.docx,.xlsx"
|
||||
action=""
|
||||
:limit="fileNumLimit"
|
||||
multiple
|
||||
|
@ -170,7 +170,7 @@ export default {
|
|||
fileList: [],
|
||||
tableData: [],
|
||||
uploadList: [],
|
||||
metadataIdList:[],
|
||||
metadataIdList: [],
|
||||
fileNumLimit: 10,
|
||||
threadGroups: [],
|
||||
loadFileVisible: false,
|
||||
|
@ -276,7 +276,10 @@ export default {
|
|||
let self = this;
|
||||
let file = uploadResources.file;
|
||||
self.uploadList.push(file);
|
||||
|
||||
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
|
||||
if (type.toLowerCase() !== 'jmx') {
|
||||
return;
|
||||
}
|
||||
let jmxReader = new FileReader();
|
||||
jmxReader.onload = (event) => {
|
||||
self.threadGroups = self.threadGroups.concat(findThreadGroup(event.target.result, file.name));
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 360d7214d15951ae11b3973add795305a5c3d035
|
||||
Subproject commit 290ffd9eb52b1a42243adb35ac3ee61f8295bb8f
|
|
@ -22,7 +22,6 @@ import {left2RightDrag, bottom2TopDrag, right2LeftDrag} from "../common/js/direc
|
|||
import JsonSchemaEditor from './components/common/json-schema/schema/index';
|
||||
import JSONPathPicker from 'vue-jsonpath-picker';
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import 'default-passive-events'
|
||||
Vue.use(JsonSchemaEditor);
|
||||
import VuePapaParse from 'vue-papa-parse'
|
||||
Vue.use(VuePapaParse)
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import {Message, MessageBox} from 'element-ui';
|
||||
import axios from "axios";
|
||||
import i18n from '../../i18n/i18n'
|
||||
import {TokenKey} from "@/common/js/constants";
|
||||
|
||||
export function registerRequestHeaders() {
|
||||
axios.interceptors.request.use(config => {
|
||||
let user = JSON.parse(localStorage.getItem(TokenKey));
|
||||
if (user && user.csrfToken) {
|
||||
config.headers['CSRF-TOKEN'] = user.csrfToken;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
|
||||
// 登入请求不重定向
|
||||
let unRedirectUrls = new Set(['signin','ldap/signin','/signin', '/ldap/signin']);
|
||||
let unRedirectUrls = new Set(['signin', 'ldap/signin', '/signin', '/ldap/signin']);
|
||||
|
||||
if (!axios) {
|
||||
window.console.error('You have to install axios');
|
||||
|
|
|
@ -478,7 +478,8 @@ export default {
|
|||
delete_file: "The file already exists, please delete the file with the same name first!",
|
||||
thread_num: 'Concurrent users:',
|
||||
input_thread_num: 'Please enter the number of threads',
|
||||
duration: 'Duration time (seconds):',
|
||||
duration: 'Duration time (seconds)',
|
||||
granularity: 'Aggregation time (seconds)',
|
||||
input_duration: 'Please enter a duration',
|
||||
rps_limit: 'RPS Limit:',
|
||||
input_rps_limit: 'Please enter a limit',
|
||||
|
|
|
@ -475,7 +475,8 @@ export default {
|
|||
delete_file: "文件已存在,请先删除同名文件!",
|
||||
thread_num: '并发用户数:',
|
||||
input_thread_num: '请输入线程数',
|
||||
duration: '压测时长(秒):',
|
||||
duration: '压测时长(秒)',
|
||||
granularity: '聚合时间(秒)',
|
||||
input_duration: '请输入时长',
|
||||
rps_limit: 'RPS上限:',
|
||||
input_rps_limit: '请输入限制',
|
||||
|
|
|
@ -475,7 +475,8 @@ export default {
|
|||
delete_file: "文件已存在,請先刪除同名文件!",
|
||||
thread_num: '並發用戶數:',
|
||||
input_thread_num: '請輸入線程數',
|
||||
duration: '壓測時長(秒):',
|
||||
duration: '壓測時長(秒)',
|
||||
granularity: '聚合時間(秒)',
|
||||
input_duration: '請輸入時長',
|
||||
rps_limit: 'RPS上限:',
|
||||
input_rps_limit: '請輸入限制',
|
||||
|
|
Loading…
Reference in New Issue