This commit is contained in:
wenyann 2021-03-11 14:10:33 +08:00
commit 8fd1ddbf2b
34 changed files with 1890 additions and 124495 deletions

View File

@ -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");

View File

@ -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()));

View File

@ -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());

View File

@ -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());

View File

@ -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"));

View File

@ -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));

View File

@ -7,6 +7,8 @@ import java.util.List;
@Data
public class RequestResult {
// 请求ID
private String id;
private String name;

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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("/");

View File

@ -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;
}

View File

@ -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("");
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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 => {

View File

@ -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;
}

View File

@ -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()
}
}
}

View File

@ -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 one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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>

View File

@ -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

View File

@ -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)

View File

@ -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');

View File

@ -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',

View File

@ -475,7 +475,8 @@ export default {
delete_file: "文件已存在,请先删除同名文件!",
thread_num: '并发用户数:',
input_thread_num: '请输入线程数',
duration: '压测时长(秒):',
duration: '压测时长(秒)',
granularity: '聚合时间(秒)',
input_duration: '请输入时长',
rps_limit: 'RPS上限',
input_rps_limit: '请输入限制',

View File

@ -475,7 +475,8 @@ export default {
delete_file: "文件已存在,請先刪除同名文件!",
thread_num: '並發用戶數:',
input_thread_num: '請輸入線程數',
duration: '壓測時長(秒):',
duration: '壓測時長(秒)',
granularity: '聚合時間(秒)',
input_duration: '請輸入時長',
rps_limit: 'RPS上限',
input_rps_limit: '請輸入限制',