Conflicts:
	frontend/src/i18n/en-US.js
	frontend/src/i18n/zh-CN.js
	frontend/src/i18n/zh-TW.js
This commit is contained in:
q4speed 2020-07-10 10:47:56 +08:00
commit 5a377843d9
89 changed files with 2900 additions and 1470 deletions

View File

@ -161,6 +161,17 @@
<version>2.1.7</version>
</dependency>
<!--
该依赖是私有仓库的依赖,现已经发布到 Github Packages下载请在 settings 文件中配置自己的 GITHUB_TOKEN
示例:
<servers>
<server>
<id>github</id>
<username>USERNAME</username>
<password>TOKEN</password>
</server>
</servers>
-->
<dependency>
<groupId>com.fit2cloud</groupId>
<artifactId>quartz-spring-boot-starter</artifactId>
@ -296,39 +307,9 @@
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter-snapshots</id>
<name>jcenter</name>
<url>https://jcenter.bintray.com/</url>
</repository>
<repository>
<id>fit2cloud-enterprise-release</id>
<name>Fit2Cloud Enterprise Release</name>
<url>http://repository.fit2cloud.com/content/repositories/fit2cloud-enterprise-release/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>fit2cloud</id>
<id>github</id>
<name>fit2cloud</name>
<url>http://repository.fit2cloud.com/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>https://maven.pkg.github.com/fit2cloud/quartz-spring-boot-starter</url>
</repository>
</repositories>

View File

@ -50,8 +50,8 @@ public class APITestController {
return apiTestService.getApiTestByProjectId(projectId);
}
/*查询某个api测试状态*/
@GetMapping("/list/all/{testId}")
@GetMapping("/state/get/{testId}")
public ApiTest apiState(@PathVariable String testId) {
return apiTestService.getApiTestByTestId(testId);
}
@ -96,4 +96,10 @@ public class APITestController {
return apiTestService.run(request);
}
@PostMapping("/import/{platform}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public ApiTest testCaseImport(MultipartFile file, @PathVariable String platform) {
return apiTestService.apiTestImport(file, platform);
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.parse;
import io.metersphere.api.dto.scenario.Scenario;
import lombok.Data;
import java.util.List;
@Data
public class ApiImport {
private List<Scenario> scenarios;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.parse.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanCollection {
private PostmanCollectionInfo info;
private List<PostmanItem> item;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.parse.postman;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
@Data
public class PostmanCollectionInfo {
@JSONField(name = "_postman_id")
private String postmanId;
private String name;
private String schema;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.parse.postman;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.List;
@Data
public class PostmanItem {
private String name;
private PostmanRequest request;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.parse.postman;
import lombok.Data;
@Data
public class PostmanKeyValue {
private String key;
private String value;
private String type;
public PostmanKeyValue() {
}
public PostmanKeyValue(String key, String value) {
this.key = key;
this.value = value;
}
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.parse.postman;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class PostmanRequest {
private String method;
private String schema;
private List<PostmanKeyValue> header;
private JSONObject body;
private JSONObject auth;
private PostmanUrl url;
private String description;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.api.dto.parse.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanUrl {
private String raw;
private String protocol;
private String port;
private List<PostmanKeyValue> query;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.dto.parse.swagger;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
@Data
public class SwaggerApi {
private String swagger;
private SwaggerInfo info;
private String host;
private String basePath;
private List<String> schemes;
private List<SwaggerTag> tags;
private JSONObject paths;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.parse.swagger;
import lombok.Data;
@Data
public class SwaggerInfo {
private String version;
private String title;
private String description;
private String termsOfService;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.parse.swagger;
import lombok.Data;
@Data
public class SwaggerParameter {
private String name;
private String in;
private String description;
private Boolean required;
private String type;
private String format;
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.parse.swagger;
import lombok.Data;
import java.util.List;
@Data
public class SwaggerRequest {
private List<String> tags;
private String summary;
private String description;
private String operationId;
private List<String> consumes;
private List<String> produces;
private SwaggerParameter parameters;
}

View File

@ -0,0 +1,9 @@
package io.metersphere.api.dto.parse.swagger;
import lombok.Data;
@Data
public class SwaggerTag {
private String name;
private String description;
}

View File

@ -6,4 +6,12 @@ import lombok.Data;
public class KeyValue {
private String name;
private String value;
public KeyValue() {
}
public KeyValue(String name, String value) {
this.name = name;
this.value = value;
}
}

View File

@ -11,6 +11,8 @@ public class Request {
private String name;
private String url;
private String method;
private Boolean useEnvironment;
private String path;
private List<KeyValue> parameters;
private List<KeyValue> headers;
private Body body;

View File

@ -8,6 +8,7 @@ import java.util.List;
public class Scenario {
private String name;
private String url;
private String environmentId;
private List<KeyValue> variables;
private List<KeyValue> headers;
private List<Request> requests;

View File

@ -0,0 +1,37 @@
package io.metersphere.api.parse;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public abstract class ApiImportAbstractParser implements ApiImportParser {
protected String getApiTestStr(InputStream source) {
BufferedReader bufferedReader = null;
StringBuilder testStr = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(source, "UTF-8"));
testStr = new StringBuilder();
String inputStr = null;
while ((inputStr = bufferedReader.readLine()) != null) {
testStr.append(inputStr);
}
} catch (Exception e) {
MSException.throwException(e.getMessage());
LogUtil.error(e.getMessage(), e);
} finally {
try {
source.close();
} catch (IOException e) {
MSException.throwException(e.getMessage());
LogUtil.error(e.getMessage(), e);
}
}
return testStr.toString();
}
}

View File

@ -0,0 +1,9 @@
package io.metersphere.api.parse;
import io.metersphere.api.dto.parse.ApiImport;
import java.io.InputStream;
public interface ApiImportParser {
ApiImport parse(InputStream source);
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.parse;
import io.metersphere.commons.constants.ApiImportPlatform;
import io.metersphere.commons.constants.FileType;
import io.metersphere.performance.parse.EngineSourceParser;
import io.metersphere.performance.parse.xml.XmlEngineSourceParse;
import org.apache.commons.lang3.StringUtils;
public class ApiImportParserFactory {
public static ApiImportParser getApiImportParser(String platform) {
if (StringUtils.equals(ApiImportPlatform.Metersphere.name(), platform)) {
return new MsParser();
} else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) {
return new PostmanParser();
}
return null;
}
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import java.io.*;
public class MsParser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
return JSON.parseObject(testStr.toString(), ApiImport.class);
}
}

View File

@ -0,0 +1,95 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.parse.postman.*;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.Request;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.commons.constants.MsRequestBodyType;
import io.metersphere.commons.constants.PostmanRequestBodyMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class PostmanParser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
PostmanCollection postmanCollection = JSON.parseObject(testStr.toString(), PostmanCollection.class);
PostmanCollectionInfo info = postmanCollection.getInfo();
List<Request> requests = parseRequests(postmanCollection);
ApiImport apiImport = new ApiImport();
List<Scenario> scenarios = new ArrayList<>();
Scenario scenario = new Scenario();
scenario.setRequests(requests);
scenario.setName(info.getName());
scenarios.add(scenario);
apiImport.setScenarios(scenarios);
return apiImport;
}
private List<KeyValue> parseKeyValue(List<PostmanKeyValue> postmanKeyValues) {
if (postmanKeyValues == null) {
return null;
}
List<KeyValue> keyValues = new ArrayList<>();
postmanKeyValues.forEach(item -> {
keyValues.add(new KeyValue(item.getKey(), item.getValue()));
});
return keyValues;
}
private List<Request> parseRequests(PostmanCollection postmanCollection) {
List<PostmanItem> item = postmanCollection.getItem();
List<Request> requests = new ArrayList<>();
for (PostmanItem requestItem : item) {
Request request = new Request();
PostmanRequest requestDesc = requestItem.getRequest();
PostmanUrl url = requestDesc.getUrl();
request.setName(requestItem.getName());
request.setUrl(url.getRaw());
request.setUseEnvironment(false);
request.setMethod(requestDesc.getMethod());
request.setHeaders(parseKeyValue(requestDesc.getHeader()));
request.setParameters(parseKeyValue(url.getQuery()));
Body body = new Body();
JSONObject postmanBody = requestDesc.getBody();
String bodyMode = postmanBody.getString("mode");
if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) {
body.setRaw(postmanBody.getString(bodyMode));
body.setType(MsRequestBodyType.RAW.value());
String contentType = postmanBody.getJSONObject("options").getJSONObject("raw").getString("language");
List<KeyValue> headers = request.getHeaders();
boolean hasContentType = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase(header.getName(), "Content-Type")) {
hasContentType = true;
}
}
if (!hasContentType) {
headers.add(new KeyValue("Content-Type", contentType));
}
} else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value()) || StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) {
List<PostmanKeyValue> postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class);
body.setType(MsRequestBodyType.KV.value());
body.setKvs(parseKeyValue(postmanKeyValues));
}
request.setBody(body);
requests.add(request);
}
return requests;
}
}

View File

@ -0,0 +1,106 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.parse.postman.PostmanItem;
import io.metersphere.api.dto.parse.postman.PostmanRequest;
import io.metersphere.api.dto.parse.postman.PostmanUrl;
import io.metersphere.api.dto.parse.swagger.SwaggerApi;
import io.metersphere.api.dto.parse.swagger.SwaggerInfo;
import io.metersphere.api.dto.parse.swagger.SwaggerRequest;
import io.metersphere.api.dto.scenario.Request;
import io.metersphere.api.dto.scenario.Scenario;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class SwaggerParser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
SwaggerApi swaggerApi = JSON.parseObject(testStr.toString(), SwaggerApi.class);
SwaggerInfo info = swaggerApi.getInfo();
String title = info.getTitle();
// List<Request> requests = parseRequests(swaggerApi);
// ApiImport apiImport = new ApiImport();
// List<Scenario> scenarios = new ArrayList<>();
// Scenario scenario = new Scenario();
// scenario.setRequests(requests);
// scenario.setName(info.getName());
// scenarios.add(scenario);
// apiImport.setScenarios(scenarios);
// return apiImport;
return null;
}
// private List<Request> parseRequests(SwaggerApi swaggerApi) {
// JSONObject paths = swaggerApi.getPaths();
//
// Set<String> pathNames = paths.keySet();
//
// for (String path : pathNames) {
// JSONObject pathObject = paths.getJSONObject(path);
// Set<String> methods = pathObject.keySet();
// for (String method : methods) {
// SwaggerRequest swaggerRequest = JSON.parseObject(pathObject.getJSONObject(method).toJSONString(), SwaggerRequest.class);
// Request request = new Request();
// request.setName(swaggerRequest.getOperationId());
// request.setUrl(url.getRaw());
// request.setPath(.getRaw());
// request.setUseEnvironment(false);
// request.setMethod(requestDesc.getMethod());
// request.setHeaders(parseKeyValue(requestDesc.getHeader()));
// request.setParameters(parseKeyValue(url.getQuery()));
//
// }
// }
//
// List<PostmanItem> item = postmanCollection.getItem();
// List<Request> requests = new ArrayList<>();
// for (PostmanItem requestItem : item) {
// Request request = new Request();
// PostmanRequest requestDesc = requestItem.getRequest();
// PostmanUrl url = requestDesc.getUrl();
// request.setName(requestItem.getName());
// request.setUrl(url.getRaw());
// request.setUseEnvironment(false);
// request.setMethod(requestDesc.getMethod());
// request.setHeaders(parseKeyValue(requestDesc.getHeader()));
// request.setParameters(parseKeyValue(url.getQuery()));
// Body body = new Body();
// JSONObject postmanBody = requestDesc.getBody();
// String bodyMode = postmanBody.getString("mode");
// if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) {
// body.setRaw(postmanBody.getString(bodyMode));
// body.setType(MsRequestBodyType.RAW.value());
// String contentType = postmanBody.getJSONObject("options").getJSONObject("raw").getString("language");
// List<KeyValue> headers = request.getHeaders();
// boolean hasContentType = false;
// for (KeyValue header : headers) {
// if (StringUtils.equalsIgnoreCase(header.getName(), "Content-Type")) {
// hasContentType = true;
// }
// }
// if (!hasContentType) {
// headers.add(new KeyValue("Content-Type", contentType));
// }
// } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value()) || StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) {
// List<PostmanKeyValue> postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class);
// body.setType(MsRequestBodyType.KV.value());
// body.setKvs(parseKeyValue(postmanKeyValues));
// }
// request.setBody(body);
// requests.add(request);
// }
// return requests;
// }
}

View File

@ -2,18 +2,25 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.api.dto.SaveAPITestRequest;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.api.parse.ApiImportParserFactory;
import io.metersphere.api.parse.MsParser;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiTestFileMapper;
import io.metersphere.base.mapper.ApiTestMapper;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.ext.ExtApiTestMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
@ -26,8 +33,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.*;
import java.util.List;
import java.util.Objects;
import java.util.Random;
@ -52,6 +58,8 @@ public class APITestService {
private APIReportService apiReportService;
@Resource
private ScheduleService scheduleService;
@Resource
private TestCaseMapper testCaseMapper;
public List<APITestResult> list(QueryAPITestRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
@ -124,6 +132,20 @@ public class APITestService {
}
public void delete(String testId) {
// 是否关联测试用例
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andTestIdEqualTo(testId);
List<TestCase> testCases = testCaseMapper.selectByExample(testCaseExample);
if (testCases.size() > 0) {
String caseName = "";
for (int i = 0; i < testCases.size(); i++) {
caseName = caseName + testCases.get(i).getName() + ",";
}
caseName = caseName.substring(0, caseName.length() - 1);
MSException.throwException(Translator.get("related_case_del_fail_prefix") + caseName + Translator.get("related_case_del_fail_suffix"));
}
deleteFileByTestId(testId);
apiReportService.deleteByTestId(testId);
apiTestMapper.deleteByPrimaryKey(testId);
@ -163,6 +185,15 @@ public class APITestService {
}
}
private Boolean isNameExist(SaveAPITestRequest request) {
ApiTestExample example = new ApiTestExample();
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId());
if (apiTestMapper.countByExample(example) > 0) {
return true;
}
return false;
}
private ApiTest updateTest(SaveAPITestRequest request) {
checkNameExist(request);
final ApiTest test = new ApiTest();
@ -246,4 +277,37 @@ public class APITestService {
private void addOrUpdateApiTestCronJob(Schedule request) {
scheduleService.addOrUpdateCronJob(request, ApiTestJob.getJobKey(request.getResourceId()), ApiTestJob.getTriggerKey(request.getResourceId()), ApiTestJob.class);
}
public ApiTest apiTestImport(MultipartFile file, String platform) {
ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(platform);
ApiImport apiImport = null;
try {
apiImport = apiImportParser.parse(file.getInputStream());
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("parse_data_error"));
}
SaveAPITestRequest request = getImportApiTest(file, apiImport);
return createTest(request);
}
private SaveAPITestRequest getImportApiTest(MultipartFile file, ApiImport apiImport) {
SaveAPITestRequest request = new SaveAPITestRequest();
request.setName(file.getOriginalFilename());
request.setProjectId("");
request.setScenarioDefinition(apiImport.getScenarios());
request.setUserId(SessionUtils.getUser().getId());
request.setId(UUID.randomUUID().toString());
for (FileType fileType : FileType.values()) {
String suffix = fileType.suffix();
String name = request.getName();
if (name.endsWith(suffix)) {
request.setName(name.substring(0, name.length() - suffix.length()));
}
};
if (isNameExist(request)) {
request.setName(request.getName() + "_" + request.getId().substring(0, 5));
}
return request;
}
}

View File

@ -3,6 +3,8 @@ package io.metersphere.api.service;
import io.metersphere.base.domain.ApiTestEnvironmentExample;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.base.mapper.ApiTestEnvironmentMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.i18n.Translator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -37,7 +39,20 @@ public class ApiTestEnvironmentService {
public String add(ApiTestEnvironmentWithBLOBs apiTestEnvironmentWithBLOBs) {
apiTestEnvironmentWithBLOBs.setId(UUID.randomUUID().toString());
checkEnvironmentExist(apiTestEnvironmentWithBLOBs);
apiTestEnvironmentMapper.insert(apiTestEnvironmentWithBLOBs);
return apiTestEnvironmentWithBLOBs.getId();
}
private void checkEnvironmentExist (ApiTestEnvironmentWithBLOBs environment) {
if (environment.getName() != null) {
ApiTestEnvironmentExample example = new ApiTestEnvironmentExample();
example.createCriteria()
.andNameEqualTo(environment.getName())
.andProjectIdEqualTo(environment.getProjectId());
if (apiTestEnvironmentMapper.selectByExample(example).size() > 0) {
MSException.throwException(Translator.get("api_test_environment_already_exists"));
}
}
}
}

View File

@ -3,9 +3,12 @@
<mapper namespace="io.metersphere.base.mapper.ext.ExtTestCaseMapper">
<select id="getTestCaseNames" resultType="io.metersphere.base.domain.TestCase">
select test_case.id, test_case.name
select test_case.id, test_case.name, test_case.priority, test_case.type
from test_case
<where>
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.projectId != null">
AND test_case.project_id = #{request.projectId}
</if>
@ -14,7 +17,16 @@
<foreach collection="request.nodeIds" open="(" close=")" separator="," item="nodeId">
#{nodeId}
</foreach>
</if>
<if test="request.filters != null and request.filters.size() > 0">
<foreach collection="request.filters.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
and test_case.${key} in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</if>
</foreach>
</if>
</where>
ORDER BY test_case.update_time DESC
@ -54,14 +66,14 @@
</if>
</select>
<select id="listByMethod" resultType="io.metersphere.track.dto.TestCaseDTO">
SELECT id,name,status,project_id,type from api_test
SELECT id,name,status,project_id,"api" as type from api_test
<where>
<if test="request.projectId!=null">
and project_id=#{request.projectId}
</if>
</where>
UNION ALL
select id,name,status,project_id,type from load_test
select id,name,status,project_id,"perform" as type from load_test
<where>
<if test="request.projectId!=null">
and project_id= #{request.projectId}

View File

@ -71,6 +71,9 @@
<if test="request.planId != null">
and test_plan_test_case.plan_id = #{request.planId}
</if>
<if test="request.method != null">
and test_case.method = #{request.method}
</if>
<if test="request.nodePaths != null and request.nodePaths.size() > 0">
and test_case.node_path in

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum ApiImportPlatform {
Metersphere, Postman
}

View File

@ -1,7 +1,7 @@
package io.metersphere.commons.constants;
public enum FileType {
JMX(".jmx"), CSV(".csv");
JMX(".jmx"), CSV(".csv"), JSON(".json");
// 保存后缀
private String suffix;

View File

@ -0,0 +1,17 @@
package io.metersphere.commons.constants;
public enum MsRequestBodyType {
KV("KeyValue"), FORM_DATA("Form Data"), RAW("Raw");
private String value;
MsRequestBodyType(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.commons.constants;
public enum PostmanRequestBodyMode {
RAW("raw"), FORM_DATA("formdata"), URLENCODED("urlencoded"), FILE("file");
private String value;
PostmanRequestBodyMode(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}

View File

@ -56,7 +56,6 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/swagger-ui/**", "anon");
filterChainDefinitionMap.put("/v3/api-docs/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/403", "anon");
filterChainDefinitionMap.put("/anonymous/**", "anon");
filterChainDefinitionMap.put("/**", "apikey, authc");

View File

@ -6,6 +6,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.ResultHolder;
import io.metersphere.controller.request.LoginRequest;
import io.metersphere.i18n.Translator;
import io.metersphere.ldap.domain.Person;
import io.metersphere.ldap.service.LdapService;
import io.metersphere.ldap.domain.LdapInfo;
import io.metersphere.service.SystemParameterService;
@ -34,20 +35,25 @@ public class LdapController {
MSException.throwException(Translator.get("ldap_authentication_not_enabled"));
}
ldapService.authenticate(request);
Person person = ldapService.authenticate(request);
SecurityUtils.getSubject().getSession().setAttribute("authenticate", "ldap");
String username = request.getUsername();
String password = request.getPassword();
String email = person.getEmail();
if (StringUtils.isBlank(email)) {
MSException.throwException(Translator.get("login_fail_email_null"));
}
User u = userService.selectUser(request.getUsername());
if (u == null) {
User user = new User();
user.setId(username);
user.setName(username);
// todo user email ?
user.setEmail(username + "@fit2cloud.com");
user.setEmail(email);
user.setPassword(password);
userService.createUser(user);
} else {
@ -60,7 +66,7 @@ public class LdapController {
@PostMapping("/test/connect")
public void testConnect(@RequestBody LdapInfo ldapInfo) {
ldapService.testConnect(ldapInfo);
ldapService.testConnect();
}
@PostMapping("/test/login")

View File

@ -1,11 +1,9 @@
package io.metersphere.ldap.dao;
import java.util.List;
import io.metersphere.ldap.domain.Person;
public interface PersonRepo {
List findByName(String name);
String getDnForUser(String name);
Person getDnForUser(String name);
}

View File

@ -4,23 +4,28 @@ package io.metersphere.ldap.dao;
import io.metersphere.commons.constants.ParamConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.i18n.Translator;
import io.metersphere.ldap.domain.Person;
import io.metersphere.service.SystemParameterService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.InvalidNameException;
import org.springframework.ldap.InvalidSearchFilterException;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.*;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.SearchScope;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapContext;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
@ -32,59 +37,66 @@ public class PersonRepoImpl implements PersonRepo {
public boolean authenticate(String dn, String credentials) {
LdapTemplate ldapTemplate = getConnection();
return authenticate(dn, credentials, ldapTemplate);
}
private boolean authenticate(String dn, String credentials, LdapTemplate ldapTemplate) throws AuthenticationException {
DirContext ctx = null;
try {
ctx = ldapTemplate.getContextSource().getContext(dn, credentials);
// ldapTemplate.authenticate(dn, credentials);
// Take care here - if a base was specified on the ContextSource
// that needs to be removed from the user DN for the lookup to succeed.
// ctx.lookup(userDn);
return true;
} catch (AuthenticationException e) {
LogUtil.error("ldap authenticate failed..." + e);
System.out.println("Login failed: " + e);
MSException.throwException(Translator.get("authentication_failed"));
return false;
} catch (Exception e) {
// Context creation failed - authentication did not succeed
LogUtil.error("ldap authenticate failed..." + e);
System.out.println("Login failed: " + e);
MSException.throwException(Translator.get("ldap_connect_fail"));
return false;
} finally {
// It is imperative that the created DirContext instance is always closed
LdapUtils.closeContext((LdapContext) ctx);
}
}
@Override
public List findByName(String name) {
LdapTemplate ldapTemplate = getConnection();
ldapTemplate.setIgnorePartialResultException(true);
LdapQuery query = query().where("cn").is(name);
return ldapTemplate.search(query, getContextMapper());
}
@Override
public String getDnForUser(String uid) {
public Person getDnForUser(String username) {
LdapTemplate ldapTemplate = getConnection();
ldapTemplate.setIgnorePartialResultException(true);
List<String> result = ldapTemplate.search(
query().where("cn").is(uid),
new AbstractContextMapper() {
@Override
protected String doMapFromContext(DirContextOperations ctx) {
return ctx.getNameInNamespace();
}
});
String filter = getUserFilter();
String ou = getUserOu();
List<Person> result = null;
try {
result = ldapTemplate.search(query().base(ou).filter(filter, username), getContextMapper());
} catch (NameNotFoundException e) {
MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidNameException e) {
MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidSearchFilterException e) {
MSException.throwException(Translator.get("login_fail_filter_error"));
}
if (result.size() != 1) {
throw new RuntimeException(Translator.get("user_not_found_or_not_unique"));
MSException.throwException(Translator.get("user_not_found_or_not_unique"));
}
return result.get(0);
}
private String getUserFilter() {
String filter = service.getValue(ParamConstants.LDAP.FILTER.getValue());
if (StringUtils.isBlank(filter)) {
MSException.throwException(Translator.get("ldap_user_filter_is_null"));
}
return filter;
}
private String getUserOu() {
String ou = service.getValue(ParamConstants.LDAP.OU.getValue());
if (StringUtils.isBlank(ou)) {
MSException.throwException(Translator.get("ldap_ou_is_null"));
}
return ou;
}
protected ContextMapper getContextMapper() {
return new PersonContextMapper();
}
@ -93,6 +105,8 @@ public class PersonRepoImpl implements PersonRepo {
@Override
public Person doMapFromContext(DirContextOperations context) {
Person person = new Person();
person.setDn(context.getNameInNamespace());
person.setUid(context.getStringAttribute("uid"));
person.setCommonName(context.getStringAttribute("cn"));
person.setSurName(context.getStringAttribute("sn"));
person.setUsername(context.getStringAttribute("sAMAccountName"));
@ -105,26 +119,39 @@ public class PersonRepoImpl implements PersonRepo {
String url = service.getValue(ParamConstants.LDAP.URL.getValue());
String dn = service.getValue(ParamConstants.LDAP.DN.getValue());
String ou = service.getValue(ParamConstants.LDAP.OU.getValue());
String password = service.getValue(ParamConstants.LDAP.PASSWORD.getValue());
preConnect(url, dn, ou, password);
preConnect(url, dn, password);
String credentials = EncryptUtils.aesDecrypt(password).toString();
LdapContextSource sourceLdapCtx = new LdapContextSource();
sourceLdapCtx.setUrl(url);
sourceLdapCtx.setUserDn(dn);
sourceLdapCtx.setPassword(credentials);
sourceLdapCtx.setBase(ou);
sourceLdapCtx.setDirObjectFactory(DefaultDirObjectFactory.class);
sourceLdapCtx.afterPropertiesSet();
LdapTemplate ldapTemplate = new LdapTemplate(sourceLdapCtx);
ldapTemplate.setIgnorePartialResultException(true);
Map<String, Object> baseEnv = new Hashtable<>();
baseEnv.put("com.sun.jndi.ldap.connect.timeout", "3000");
baseEnv.put("com.sun.jndi.ldap.read.timeout", "3000");
sourceLdapCtx.setBaseEnvironmentProperties(baseEnv);
ldapTemplate.setDefaultSearchScope(SearchScope.SUBTREE.getId());
return new LdapTemplate(sourceLdapCtx);
// ldapTemplate 是否可用
try {
authenticate(dn, credentials, ldapTemplate);
} catch (AuthenticationException e) {
MSException.throwException(Translator.get("ldap_connect_fail_user"));
} catch (Exception e) {
MSException.throwException(Translator.get("ldap_connect_fail"));
}
return ldapTemplate;
}
private void preConnect(String url, String dn, String ou, String password) {
private void preConnect(String url, String dn, String password) {
if (StringUtils.isBlank(url)) {
MSException.throwException(Translator.get("ldap_url_is_null"));
@ -134,10 +161,6 @@ public class PersonRepoImpl implements PersonRepo {
MSException.throwException(Translator.get("ldap_dn_is_null"));
}
if (StringUtils.isBlank(ou)) {
MSException.throwException(Translator.get("ldap_ou_is_null"));
}
if (StringUtils.isBlank(password)) {
MSException.throwException(Translator.get("ldap_password_is_null"));
}

View File

@ -14,7 +14,7 @@ public class Person {
@Id
private Name id;
@DnAttribute(value="uid",index = 3)
@DnAttribute(value="uid",index = 0)
private String uid;
@Attribute(name = "cn")
private String commonName;
@ -24,5 +24,6 @@ public class Person {
private String username;
@Attribute(name = "mail")
private String email;
private String dn;
}

View File

@ -4,12 +4,11 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.LoginRequest;
import io.metersphere.i18n.Translator;
import io.metersphere.ldap.dao.PersonRepoImpl;
import io.metersphere.ldap.domain.LdapInfo;
import org.springframework.ldap.CommunicationException;
import io.metersphere.ldap.domain.Person;
import org.springframework.ldap.AuthenticationException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class LdapService {
@ -18,30 +17,23 @@ public class LdapService {
private PersonRepoImpl personRepo;
public void authenticate(LoginRequest request) {
String dn = null;
public Person authenticate(LoginRequest request) {
String username = request.getUsername();
String credentials = request.getPassword();
Person person = null;
try {
// select user by sAMAccountName
List user = personRepo.findByName(username);
if (user.size() == 1) {
dn = personRepo.getDnForUser(username);
} else if (user.size() == 0) {
MSException.throwException(Translator.get("user_not_exist") + username);
} else {
MSException.throwException(Translator.get("find_more_user"));
}
} catch (CommunicationException e) {
MSException.throwException(Translator.get("ldap_connect_fail"));
person = personRepo.getDnForUser(username);
personRepo.authenticate(person.getDn(), credentials);
} catch (AuthenticationException e) {
MSException.throwException(Translator.get("authentication_failed"));
}
personRepo.authenticate(dn, credentials);
return person;
}
public void testConnect(LdapInfo ldap) {
personRepo.authenticate(ldap.getDn(), ldap.getPassword());
public void testConnect() {
personRepo.getConnection();
}
}

View File

@ -55,8 +55,8 @@ public class PerformanceTestController {
return performanceTestService.getLoadTestByProjectId(projectId);
}
/*查询某个测试状态*/
@GetMapping("/list/all/{testId}")
@GetMapping("/state/get/{testId}")
public LoadTest listByTestId(@PathVariable String testId) {
return performanceTestService.getLoadTestBytestId(testId);
}

View File

@ -102,16 +102,8 @@ public class PerformanceTestService {
if (!loadTestReports.isEmpty()) {
List<String> reportIdList = loadTestReports.stream().map(LoadTestReport::getId).collect(Collectors.toList());
// delete load_test_report_result
LoadTestReportResultExample loadTestReportResultExample = new LoadTestReportResultExample();
loadTestReportResultExample.createCriteria().andReportIdIn(reportIdList);
loadTestReportResultMapper.deleteByExample(loadTestReportResultExample);
// delete load_test_report, delete load_test_report_detail
// delete load_test_report
reportIdList.forEach(reportId -> {
LoadTestReportDetailExample example = new LoadTestReportDetailExample();
example.createCriteria().andReportIdEqualTo(reportId);
loadTestReportDetailMapper.deleteByExample(example);
reportService.deleteReport(reportId);
});
}

View File

@ -3,10 +3,7 @@ package io.metersphere.performance.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.LoadTestReportLogMapper;
import io.metersphere.base.mapper.LoadTestReportMapper;
import io.metersphere.base.mapper.LoadTestReportResultMapper;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.constants.ReportKeys;
@ -45,6 +42,8 @@ public class ReportService {
private LoadTestReportLogMapper loadTestReportLogMapper;
@Resource
private TestResourceService testResourceService;
@Resource
private LoadTestReportDetailMapper loadTestReportDetailMapper;
public List<ReportDTO> getRecentReportList(ReportRequest request) {
List<OrderRequest> orders = new ArrayList<>();
@ -85,6 +84,16 @@ public class ReportService {
stopEngine(loadTest, engine);
}
// delete load_test_report_result
LoadTestReportResultExample loadTestReportResultExample = new LoadTestReportResultExample();
loadTestReportResultExample.createCriteria().andReportIdEqualTo(reportId);
loadTestReportResultMapper.deleteByExample(loadTestReportResultExample);
// delete load_test_report_detail
LoadTestReportDetailExample example = new LoadTestReportDetailExample();
example.createCriteria().andReportIdEqualTo(reportId);
loadTestReportDetailMapper.deleteByExample(example);
loadTestReportMapper.deleteByPrimaryKey(reportId);
}

View File

@ -40,17 +40,24 @@ public class SystemParameterService {
public void editMail(List<SystemParameter> parameters) {
List<SystemParameter> paramList = this.getParamList(ParamConstants.Classify.MAIL.getValue());
boolean empty = paramList.size() < 2;
boolean empty = paramList.size() <= 0;
parameters.forEach(parameter -> {
SystemParameterExample example = new SystemParameterExample();
if (parameter.getParamKey().equals(ParamConstants.MAIL.PASSWORD.getKey())) {
String string = EncryptUtils.aesEncrypt(parameter.getParamValue()).toString();
parameter.setParamValue(string);
if (!StringUtils.isBlank(parameter.getParamValue())) {
String string = EncryptUtils.aesEncrypt(parameter.getParamValue()).toString();
parameter.setParamValue(string);
}
}
if (empty) {
systemParameterMapper.insert(parameter);
} else {
example.createCriteria().andParamKeyEqualTo(parameter.getParamKey());
if (systemParameterMapper.countByExample(example) > 0) {
systemParameterMapper.updateByPrimaryKey(parameter);
} else {
systemParameterMapper.insert(parameter);
}
example.clear();
});
}
@ -68,6 +75,8 @@ public class SystemParameterService {
javaMailSender.setUsername(hashMap.get(ParamConstants.MAIL.ACCOUNT.getKey()));
javaMailSender.setPassword(hashMap.get(ParamConstants.MAIL.PASSWORD.getKey()));
Properties props = new Properties();
props.put("mail.smtp.timeout", "5000");
props.put("mail.smtp.connectiontimeout", "5000");
props.put("mail.smtp.auth", "true");
if (BooleanUtils.toBoolean(hashMap.get(ParamConstants.MAIL.SSL.getKey()))) {
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
@ -105,8 +114,11 @@ public class SystemParameterService {
}
} else {
paramList.stream().filter(param -> param.getParamKey().equals(ParamConstants.MAIL.PASSWORD.getKey())).forEach(param -> {
String string = EncryptUtils.aesDecrypt(param.getParamValue()).toString();
param.setParamValue(string);
if (!StringUtils.isBlank(param.getParamValue())) {
String string = EncryptUtils.aesDecrypt(param.getParamValue()).toString();
param.setParamValue(string);
}
});
}
paramList.sort(Comparator.comparingInt(SystemParameter::getSort));

View File

@ -42,6 +42,7 @@ public class TestPlanTestCaseController {
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setPlanId(planId);
request.setNodePaths(list);
request.setMethod("auto");
return testPlanTestCaseService.listByNode(request);
}

View File

@ -12,6 +12,8 @@ import java.util.Map;
@Setter
public class QueryTestCaseRequest extends TestCase {
private String name;
private List<String> nodeIds;
private List<OrderRequest> orders;

View File

@ -66,6 +66,7 @@ organization_id_is_null=Organization ID cannot be null
#api
api_load_script_error=Load script error
api_report_is_null="Report is null, can't update"
api_test_environment_already_exists="Api test environment already exists"
#test case
test_case_node_level=level
test_case_node_level_tip=The node tree maximum depth is
@ -119,8 +120,13 @@ ldap_url_is_null=LDAP address is empty
ldap_dn_is_null=LDAP binding DN is empty
ldap_ou_is_null=LDAP parameter OU is empty
ldap_password_is_null=LDAP password is empty
ldap_connect_fail=Connection failed
authentication_failed=User authentication failed
ldap_connect_fail=Connection LDAP failed
ldap_connect_fail_user=Connection LDAP failed, wrong DN or password bound
ldap_user_filter_is_null=LDAP user filter is empty
authentication_failed=User authentication failed,wrong user name or password
user_not_found_or_not_unique=User does not exist or is not unique
find_more_user=Multiple users found
ldap_authentication_not_enabled=LDAP authentication is not enabled
ldap_authentication_not_enabled=LDAP authentication is not enabled
login_fail_email_null=Login failed, user mailbox is empty
login_fail_ou_error=Login failed, please check the user OU
login_fail_filter_error=Login failed, please check the user filter

View File

@ -19,7 +19,7 @@ cannot_delete_current_user=无法删除当前登录用户
connection_failed=连接失败
user_already_exists=该用户已存在于当前成员列表中
cannot_remove_current=无法移除当前登录用户
login_fail=失败
login_fail=失败
password_is_incorrect=密码不正确
user_not_exist=用户不存在:
user_has_been_disabled=用户已被禁用
@ -66,6 +66,7 @@ organization_id_is_null=组织 ID 不能为空
#api
api_load_script_error=读取脚本失败
api_report_is_null="测试报告是未生成,无法更新"
api_test_environment_already_exists="已存在该名称的环境配置"
#test case
test_case_node_level=
test_case_node_level_tip=模块树最大深度为
@ -119,11 +120,15 @@ ldap_url_is_null=LDAP地址为空
ldap_dn_is_null=LDAP绑定DN为空
ldap_ou_is_null=LDAP参数OU为空
ldap_password_is_null=LDAP密码为空
ldap_connect_fail=连接失败
authentication_failed=用户认证失败
ldap_connect_fail=连接LDAP失败
ldap_connect_fail_user=连接LDAP失败绑定的DN或密码错误
ldap_user_filter_is_null=LDAP用户过滤器为空
authentication_failed=用户认证失败,用户名或密码错误
user_not_found_or_not_unique=用户不存在或者不唯一
find_more_user=查找到多个用户
ldap_authentication_not_enabled=LDAP认证未启用
login_fail_email_null=登录失败,用户邮箱为空
login_fail_ou_error=登录失败请检查用户OU
login_fail_filter_error=登录失败,请检查用户过滤器

View File

@ -66,6 +66,7 @@ organization_id_is_null=組織 ID 不能為空
#api
api_load_script_error=讀取腳本失敗
api_report_is_null="測試報告是未生成,無法更新"
api_test_environment_already_exists="已存在該名稱的環境配置"
#test case
test_case_node_level=
test_case_node_level_tip=模塊樹最大深度為
@ -119,8 +120,13 @@ ldap_url_is_null=LDAP地址為空
ldap_dn_is_null=LDAP綁定DN為空
ldap_ou_is_null=LDAP參數OU為空
ldap_password_is_null=LDAP密碼為空
ldap_connect_fail=連接失敗
authentication_failed=用戶認證失敗
ldap_connect_fail=連接LDAP失敗
ldap_connect_fail_user=連接LDAP失敗綁定的DN或密碼錯誤
ldap_user_filter_is_null=LDAP用戶過濾器為空
authentication_failed=用戶認證失敗,用戶名或密碼錯誤
user_not_found_or_not_unique=用戶不存在或者不唯一
find_more_user=查找到多個用戶
ldap_authentication_not_enabled=LDAP認證未啟用
login_fail_email_null=登錄失敗,用戶郵箱為空
login_fail_ou_error=登錄失敗請檢查用戶OU
login_fail_filter_error=登錄失敗,請檢查用戶過濾器

View File

@ -39,18 +39,23 @@
<el-dropdown-item command="performance" :disabled="create || isReadOnly">
{{$t('api_test.create_performance_test')}}
</el-dropdown-item>
<el-dropdown-item command="export" :disabled="isDisabled || isReadOnly">
<el-dropdown-item command="export" :disabled="isReadOnly || create">
{{$t('api_test.export_config')}}
</el-dropdown-item>
<el-dropdown-item command="import" :disabled="isReadOnly">
{{$t('api_test.api_import.label')}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<api-import ref="apiImport"/>
<ms-api-report-dialog :test-id="id" ref="reportDialog"/>
<ms-schedule-config :schedule="test.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule" :check-open="checkScheduleEdit"/>
</el-row>
</el-header>
<ms-api-scenario-config :is-read-only="isReadOnly" :scenarios="test.scenarioDefinition" ref="config"/>
<ms-api-scenario-config :is-read-only="isReadOnly" :scenarios="test.scenarioDefinition" :project-id="test.projectId" ref="config"/>
</el-container>
</el-card>
</div>
@ -64,11 +69,12 @@
import MsApiReportDialog from "./ApiReportDialog";
import {checkoutTestManagerOrTestUser, downloadFile} from "../../../../common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import ApiImport from "./components/import/ApiImport";
export default {
name: "MsApiTestConfig",
components: {MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig},
components: {ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig},
props: ["id"],
@ -211,6 +217,9 @@
case "export":
downloadFile(this.test.name + ".json", this.test.export());
break;
case "import":
this.$refs.apiImport.open();
break;
}
},
saveCronExpression(cronExpression) {
@ -228,13 +237,13 @@
url = '/api/schedule/update';
}
this.$post(url, param, response => {
this.$success('保存成功');
this.$success(this.$t('commons.save_success'));
this.getTest(this.test.id);
});
},
checkScheduleEdit() {
if (this.create) {
this.$message('请先保存测试');
this.$message(this.$t('api_test.environment.please_save_test'));
return false;
}
return true;

View File

@ -1,7 +1,7 @@
<template>
<el-dialog :title="'环境配置'" :visible.sync="visible" class="environment-dialog">
<el-dialog :title="$t('api_test.environment.environment_config')" :visible.sync="visible" class="environment-dialog" @close="close">
<el-container v-loading="result.loading">
<ms-aside-item :title="'环境列表'" :data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"
<ms-aside-item :title="$t('api_test.environment.environment_list')" :data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"
:delete-fuc="deleteEnvironment" @itemSelected="environmentSelected" ref="environmentItems"/>
<environment-edit :environment="currentEnvironment" ref="environmentEdit"/>
</el-container>
@ -51,7 +51,7 @@
},
deleteEnvironment(environment) {
this.result = this.$get('/api/environment/delete/' + environment.id, response => {
this.$success('删除成功');
this.$success(this.$t('commons.delete_success'));
this.getEnvironments();
});
},
@ -93,6 +93,9 @@
},
getDefaultEnvironment() {
return {variables: [{}], headers: [{}], protocol: 'https', projectId: this.projectId};
},
close() {
this.$emit('close');
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div class="request-container">
<draggable :list="requests" group="Request" class="request-draggable" ghost-class="request-ghost">
<div class="request-item" v-for="(request, index) in requests" :key="index" @click="select(request)"
<draggable :list="this.scenario.requests" group="Request" class="request-draggable" ghost-class="request-ghost">
<div class="request-item" v-for="(request, index) in this.scenario.requests" :key="index" @click="select(request)"
:class="{'selected': isSelected(request)}">
<el-row type="flex">
<div class="request-method">
@ -40,7 +40,7 @@
components: {draggable},
props: {
requests: Array,
scenario: Object,
open: Function,
isReadOnly: {
type: Boolean,
@ -65,15 +65,15 @@
methods: {
createRequest: function () {
let request = new Request();
this.requests.push(request);
this.scenario.requests.push(request);
},
copyRequest: function (index) {
let request = this.requests[index];
this.requests.push(new Request(request));
let request = this.scenario.requests[index];
this.scenario.requests.push(new Request(request));
},
deleteRequest: function (index) {
this.requests.splice(index, 1);
if (this.requests.length === 0) {
this.scenario.requests.splice(index, 1);
if (this.scenario.requests.length === 0) {
this.createRequest();
}
},
@ -88,13 +88,17 @@
}
},
select: function (request) {
request.environment = this.scenario.environment;
if (!request.useEnvironment) {
request.useEnvironment = false;
}
this.selected = request;
this.open(request);
}
},
created() {
this.select(this.requests[0]);
this.select(this.scenario.requests[0]);
}
}
</script>

View File

@ -1,30 +1,47 @@
<template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="request.name" maxlength="100" show-word-limit/>
</el-form-item>
<el-form-item :label="$t('api_test.request.url')" prop="url">
<el-form-item v-if="!request.useEnvironment" :label="$t('api_test.request.url')" prop="url" class="adjust-margin-bottom">
<el-input :disabled="isReadOnly" v-model="request.url" maxlength="500"
:placeholder="$t('api_test.request.url_description')" @change="urlChange" clearable>
<el-select :disabled="isReadOnly" v-model="request.method" slot="prepend" class="request-method-select"
@change="methodChange">
<el-option label="GET" value="GET"/>
<el-option label="POST" value="POST"/>
<el-option label="PUT" value="PUT"/>
<el-option label="PATCH" value="PATCH"/>
<el-option label="DELETE" value="DELETE"/>
<el-option label="OPTIONS" value="OPTIONS"/>
<el-option label="HEAD" value="HEAD"/>
<el-option label="CONNECT" value="CONNECT"/>
</el-select>
<template v-slot:prepend>
<ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="request.useEnvironment" :label="$t('api_test.request.path')" prop="path">
<el-input :disabled="isReadOnly" v-model="request.path" maxlength="500"
:placeholder="$t('api_test.request.path_description')" @change="pathChange" clearable>
<template v-slot:prepend>
<ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="request.useEnvironment" :label="$t('api_test.request.address')" class="adjust-margin-bottom">
<el-tag class="environment-display">
<span class="environment-name">{{request.environment ? request.environment.name + ': ' : ''}}</span>
<span class="environment-url">{{displayUrl}}</span>
<span v-if="!displayUrl" class="environment-url-tip">{{$t('api_test.request.please_configure_environment_in_scenario')}}</span>
</el-tag>
</el-form-item>
<el-form-item>
<el-switch
v-model="request.useEnvironment"
:active-text="$t('api_test.request.refer_to_environment')" @change="useEnvironmentChange">
</el-switch>
</el-form-item>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">
<ms-api-key-value :is-read-only="isReadOnly" :items="request.parameters"
:description="$t('api_test.request.parameters_desc')" @change="parametersChange"/>
:description="$t('api_test.request.parameters_desc')"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
<ms-api-key-value :is-read-only="isReadOnly" :items="request.headers"/>
@ -48,10 +65,11 @@
import MsApiAssertions from "./assertion/ApiAssertions";
import {KeyValue, Request} from "../model/ScenarioModel";
import MsApiExtract from "./extract/ApiExtract";
import ApiRequestMethodSelect from "./collapse/ApiRequestMethodSelect";
export default {
name: "MsApiRequestForm",
components: {MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
components: {ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
props: {
request: Request,
isReadOnly: {
@ -77,6 +95,9 @@
url: [
{max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
{validator: validateURL, trigger: 'blur'}
],
path: [
{max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
]
}
}
@ -85,39 +106,43 @@
methods: {
urlChange() {
if (!this.request.url) return;
let parameters = [];
let url = this.getURL(this.addProtocol(this.request.url));
if (url) {
this.request.url = decodeURIComponent(url.origin + url.pathname);
}
},
pathChange() {
if (!this.request.path) return;
if (!this.request.path.startsWith('/')) {
this.request.path = '/' + this.request.path;
}
let url = this.getURL(this.displayUrl);
this.request.path = decodeURIComponent(url.pathname);
this.request.urlWirhEnv = decodeURIComponent(url.origin + url.pathname);
},
getURL(urlStr) {
try {
let url = new URL(this.addProtocol(this.request.url));
let url = new URL(urlStr);
url.searchParams.forEach((value, key) => {
if (key && value) {
parameters.push(new KeyValue(key, value));
this.request.parameters.splice(0, 0, new KeyValue(key, value));
}
});
//
parameters.push(new KeyValue());
this.request.parameters = parameters;
this.request.url = this.getURL(url);
return url;
} catch (e) {
this.$error(this.$t('api_test.request.url_invalid'), 2000)
this.$error(this.$t('api_test.request.url_invalid'), 2000);
}
},
methodChange(value) {
if (value === 'GET' && this.activeName === 'body') {
this.activeName = 'parameters';
}
},
parametersChange(parameters) {
if (!this.request.url) return;
let url = new URL(this.addProtocol(this.request.url));
url.search = "";
parameters.forEach(function (parameter) {
if (parameter.name && parameter.value) {
url.searchParams.append(parameter.name, parameter.value);
}
})
this.request.url = this.getURL(url);
useEnvironmentChange(value) {
if (value && !this.request.environment) {
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
this.request.useEnvironment = false;
}
},
addProtocol(url) {
if (url) {
@ -126,15 +151,15 @@
}
}
return url;
},
getURL(url) {
return decodeURIComponent(url.origin + url.pathname) + "?" + url.searchParams.toString();
}
},
computed: {
isNotGet() {
return this.request.method !== "GET";
},
displayUrl() {
return this.request.environment ? this.request.environment.protocol + '://' + this.request.environment.socket + (this.request.path ? this.request.path : '') : '';
}
}
}
@ -144,4 +169,28 @@
.request-method-select {
width: 110px;
}
.el-tag {
width: 100%;
height: 40px;
line-height: 40px;
}
.environment-display {
font-size: 14px;
}
.environment-name {
font-weight: bold;
font-style: italic;
}
.adjust-margin-bottom {
margin-bottom: 10px;
}
.environment-url-tip {
color: #F56C6C;
}
</style>

View File

@ -25,7 +25,7 @@
</el-dropdown-menu>
</el-dropdown>
</template>
<ms-api-request-config :is-read-only="isReadOnly" :requests="scenario.requests" :open="select"/>
<ms-api-request-config :is-read-only="isReadOnly" :scenario="scenario" :open="select"/>
</ms-api-collapse-item>
</draggable>
</ms-api-collapse>
@ -35,8 +35,8 @@
<el-main class="scenario-main">
<div class="scenario-form">
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" v-if="isScenario"/>
<ms-api-request-form :is-read-only="isReadOnly" :request="selected" v-if="isRequest"/>
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/>
<ms-api-request-form :is-read-only="isReadOnly" :request="selected" :project-id="projectId" v-if="isRequest"/>
</div>
</el-main>
</el-container>
@ -66,6 +66,7 @@
props: {
scenarios: Array,
projectId: String,
isReadOnly: {
type: Boolean,
default: false

View File

@ -1,12 +1,19 @@
<template>
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px">
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" v-loading="result.loading">
<el-form-item :label="$t('api_test.scenario.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="scenario.name" maxlength="100" show-word-limit/>
</el-form-item>
<!-- <el-form-item :label="$t('api_test.scenario.base_url')" prop="url">-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="100"/>-->
<!-- </el-form-item>-->
<el-form-item :label="$t('api_test.environment.environment')">
<el-select :disabled="isReadOnly" v-model="scenario.environmentId" class="environment-select" @change="environmentChange" clearable>
<el-option v-for="(environment, index) in environments" :key="index" :label="environment.name + ': ' + environment.protocol + '://' + environment.socket" :value="environment.id"/>
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
</el-select>
</el-form-item>
<!-- <el-form-item :label="$t('api_test.scenario.base_url')" prop="url">-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="200"/>-->
<!-- </el-form-item>-->
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
@ -16,28 +23,38 @@
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :description="$t('api_test.scenario.kv_description')"/>
</el-tab-pane>
</el-tabs>
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</el-form>
</template>
<script>
import MsApiKeyValue from "./ApiKeyValue";
import {Scenario} from "../model/ScenarioModel";
import MsApiScenarioVariables from "./ApiScenarioVariables";
import ApiEnvironmentConfig from "./ApiEnvironmentConfig";
export default {
name: "MsApiScenarioForm",
components: {MsApiScenarioVariables, MsApiKeyValue},
components: {ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue},
props: {
scenario: Scenario,
projectId: String,
isReadOnly: {
type: Boolean,
default: false
}
},
created() {
this.getEnvironments();
},
data() {
return {
result: {},
activeName: "parameters",
environments: [],
rules: {
name: [
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
@ -47,10 +64,57 @@
]
}
}
},
methods: {
getEnvironments() {
if (this.projectId) {
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
this.environments = response.data;
for (let i in this.environments) {
if (this.environments[i].id === this.scenario.environmentId) {
this.scenario.environment = this.environments[i];
break;
}
}
});
}
},
environmentChange(value) {
for (let i in this.environments) {
if (this.environments[i].id === value) {
this.scenario.environment = this.environments[i];
this.scenario.requests.forEach(request => {
request.environment = this.environments[i];
});
break;
}
}
if (!value) {
this.scenario.environment = undefined;
this.scenario.requests.forEach(request => {
request.environment = undefined;
});
}
},
openEnvironmentConfig() {
this.$refs.environmentConfig.open(this.projectId);
},
environmentConfigClose() {
this.getEnvironments();
}
}
}
</script>
<style scoped>
.environment-select {
width: 100%;
}
.environment-button {
margin-left: 20px;
padding: 7px;
}
</style>

View File

@ -7,7 +7,7 @@
</el-col>
<el-col class="assertion-btn">
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
<el-button :disabled="isReadOnly" type="primary" size="small" icon="el-icon-plus" plain @click="add" v-else/>
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add" v-else>Add</el-button>
</el-col>
</el-row>
</div>

View File

@ -15,7 +15,7 @@
</el-col>
<el-col class="assertion-btn">
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
<el-button :disabled="isReadOnly" type="primary" size="small" icon="el-icon-plus" plain @click="add" v-else/>
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add" v-else>Add</el-button>
</el-col>
</el-row>
</div>

View File

@ -24,7 +24,7 @@
:placeholder="$t('api_test.request.assertions.value')"/>
</el-col>
<el-col class="assertion-btn">
<el-button :disabled="isReadOnly" type="primary" size="small" icon="el-icon-plus" plain @click="add"/>
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add">Add</el-button>
</el-col>
</el-row>
</div>

View File

@ -1,21 +1,24 @@
<template>
<div>
<el-row :gutter="10">
<el-col :span="4">
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/>
</el-select>
</el-col>
<el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.DURATION" :callback="after"/>
</el-col>
</el-row>
<div class="assertion-add">
<el-row :gutter="10">
<el-col :span="4">
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/>
</el-select>
</el-col>
<el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.DURATION" :callback="after"/>
<el-button v-if="!type" :disabled="true" type="primary" size="small">Add</el-button>
</el-col>
</el-row>
</div>
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
</div>
@ -62,4 +65,11 @@
.assertion-item {
width: 100%;
}
.assertion-add {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<el-select :disabled="isReadOnly" v-model="request.method" class="request-method-select" @change="change">
<el-option label="GET" value="GET"/>
<el-option label="POST" value="POST"/>
<el-option label="PUT" value="PUT"/>
<el-option label="PATCH" value="PATCH"/>
<el-option label="DELETE" value="DELETE"/>
<el-option label="OPTIONS" value="OPTIONS"/>
<el-option label="HEAD" value="HEAD"/>
<el-option label="CONNECT" value="CONNECT"/>
</el-select>
</template>
<script>
export default {
name: "ApiRequestMethodSelect",
props: ['isReadOnly', 'request'],
methods: {
change(value) {
this.$emit('change', value);
}
}
}
</script>
<style scoped>
</style>

View File

@ -2,13 +2,13 @@
<el-main v-loading="result.loading">
<el-form :model="environment" :rules="rules" ref="from">
<span>环境名称</span>
<span>{{$t('api_test.environment.name')}}</span>
<el-form-item
prop="name">
<el-input v-model="environment.name" :placeholder="'请填写名称'" clearable></el-input>
<el-input v-model="environment.name" :placeholder="this.$t('commons.input_name')" clearable></el-input>
</el-form-item>
<span>环境域名</span>
<span>{{$t('api_test.environment.socket')}}</span>
<el-form-item
prop="socket">
<el-input v-model="environment.socket" :placeholder="$t('api_test.request.url_description')" clearable>
@ -21,14 +21,14 @@
</el-input>
</el-form-item>
<span>全局变量</span>
<span>{{$t('api_test.environment.globalVariable')}}</span>
<ms-api-scenario-variables :items="environment.variables"/>
<span>请求头</span>
<span>{{$t('api_test.request.headers')}}</span>
<ms-api-key-value :items="environment.headers"/>
<div class="environment-footer">
<el-button type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="save">{{this.$t('commons.save')}}</el-button>
</div>
</el-form>
@ -53,7 +53,7 @@
data() {
let socketValidator = (rule, value, callback) => {
if (!this.validateSocket(value)) {
callback(new Error('格式错误'));
callback(new Error(this.$t('commons.formatErr')));
} else {
callback();
}
@ -61,7 +61,7 @@
return {
result: {},
rules: {
name :[{required: true, message: '请填写名称', trigger: 'blur'}],
name :[{required: true, message: this.$t('commons.input_name'), trigger: 'blur'}],
socket :[{required: true, validator: socketValidator, trigger: 'blur'}],
},
}
@ -84,7 +84,7 @@
}
this.result = this.$post(url, param, response => {
this.environment.id = response.data;
this.$success("保存成功");
this.$success(this.$t('commons.save_success'));
});
},
buildParam() {

View File

@ -3,19 +3,23 @@
<div class="extract-description">
{{$t('api_test.request.extract.description')}}
</div>
<el-row :gutter="10">
<el-col :span="2">
<el-select :disabled="isReadOnly" class="extract-item" v-model="type" :placeholder="$t('api_test.request.extract.select_type')"
size="small">
<el-option :label="$t('api_test.request.extract.regex')" :value="options.REGEX"/>
<el-option label="JSONPath" :value="options.JSON_PATH"/>
<el-option label="XPath" :value="options.XPATH"/>
</el-select>
</el-col>
<el-col :span="22">
<ms-api-extract-common :is-read-only="isReadOnly" :extract-type="type" :list="list" v-if="type" :callback="after"/>
</el-col>
</el-row>
<div class="extract-add">
<el-row :gutter="10">
<el-col :span="2">
<el-select :disabled="isReadOnly" class="extract-item" v-model="type" :placeholder="$t('api_test.request.extract.select_type')"
size="small">
<el-option :label="$t('api_test.request.extract.regex')" :value="options.REGEX"/>
<el-option label="JSONPath" :value="options.JSON_PATH"/>
<el-option label="XPath" :value="options.XPATH"/>
</el-select>
</el-col>
<el-col :span="22">
<ms-api-extract-common :is-read-only="isReadOnly" :extract-type="type" :list="list" v-if="type" :callback="after"/>
</el-col>
<el-button v-if="!type" :disabled="true" type="primary" size="small">Add</el-button>
</el-row>
</div>
<ms-api-extract-edit :is-read-only="isReadOnly" :extract="extract"/>
</div>
@ -81,4 +85,11 @@
.extract-item {
width: 100%;
}
.extract-add {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -17,7 +17,7 @@
<el-col class="extract-btn">
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove"
v-if="edit"/>
<el-button :disabled="isReadOnly" type="primary" size="small" icon="el-icon-plus" plain @click="add" v-else/>
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add" v-else>Add</el-button>
</el-col>
</el-row>
</div>

View File

@ -0,0 +1,138 @@
<template>
<el-dialog :title="$t('api_test.api_import.title')" :visible.sync="visible" class="api-import" v-loading="result.loading">
<div class="data-format">
<div>{{$t('api_test.api_import.data_format')}}</div>
<el-radio-group v-model="selectedPlatformValue">
<el-radio v-for="(item, index) in platforms" :key="index" :label="item.value">{{item.name}}</el-radio>
</el-radio-group>
</div>
<div class="api-upload">
<el-upload
drag
action=""
:http-request="upload"
:limit="1"
:beforeUpload="uploadValidate"
:show-file-list="false"
:file-list="fileList"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{$t('api_test.api_import.file_size_limit')}}</div>
</el-upload>
</div>
<div class="format-tip">
<div>
<span>{{$t('api_test.api_import.tip')}}{{selectedPlatform.tip}}</span>
</div>
<div>
<span>{{$t('api_test.api_import.export_tip')}}{{selectedPlatform.exportTip}}</span>
</div>
</div>
</el-dialog>
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
export default {
name: "ApiImport",
components: {MsDialogFooter},
data() {
return {
visible: false,
platforms: [
{
name: 'Metersphere',
value: 'Metersphere',
tip: this.$t('api_test.api_import.ms_tip'),
exportTip: this.$t('api_test.api_import.ms_export_tip'),
suffixes: new Set(['json'])
},
{
name: 'Postman',
value: 'Postman',
tip: this.$t('api_test.api_import.postman_tip'),
exportTip: this.$t('api_test.api_import.post_man_export_tip'),
suffixes: new Set(['json'])
}
],
selectedPlatform: {},
selectedPlatformValue: 'Metersphere',
fileList: [],
result: {},
}
},
created() {
this.selectedPlatform = this.platforms[0];
},
watch: {
selectedPlatformValue() {
for (let i in this.platforms) {
if (this.platforms[i].value === this.selectedPlatformValue) {
this.selectedPlatform = this.platforms[i];
break;
}
}
}
},
methods: {
open() {
this.visible = true;
},
upload(file) {
this.fileList.push(file.file);
this.result = this.$fileUpload('/api/import/' + this.selectedPlatformValue, this.fileList, response => {
let res = response.data;
this.$success(this.$t('test_track.case.import.success'));
this.visible = false;
this.$router.push({path: '/api/test/edit', query: {id: res.id}});
});
this.fileList = [];
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (!this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
return false;
}
return true;
}
}
}
</script>
<style scoped>
.format-tip {
background: #EDEDED;
}
.api-upload {
text-align: center;
}
.el-radio-group {
margin: 10px 0;
}
.data-format,.format-tip,.api-upload {
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.api-import >>> .el-dialog__body {
padding: 15px 25px;
}
</style>

View File

@ -225,9 +225,15 @@ export class HTTPSamplerProxy extends DefaultTestElement {
super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName);
this.request = request || {};
this.stringProp("HTTPSampler.domain", this.request.hostname);
this.stringProp("HTTPSampler.protocol", this.request.protocol.split(":")[0]);
this.stringProp("HTTPSampler.path", this.request.pathname);
if (request.useEnvironment) {
this.stringProp("HTTPSampler.domain", this.request.environment.domain);
this.stringProp("HTTPSampler.protocol", this.request.environment.protocol);
this.stringProp("HTTPSampler.path", this.request.path);
} else {
this.stringProp("HTTPSampler.domain", this.request.hostname);
this.stringProp("HTTPSampler.protocol", this.request.protocol.split(":")[0]);
this.stringProp("HTTPSampler.path", this.request.pathname);
}
this.stringProp("HTTPSampler.method", this.request.method);
this.stringProp("HTTPSampler.contentEncoding", this.request.encoding, "UTF-8");
if (!this.request.port) {

View File

@ -147,6 +147,7 @@ export class Scenario extends BaseConfig {
this.variables = [];
this.headers = [];
this.requests = [];
this.environmentId = undefined;
this.set(options);
this.sets({variables: KeyValue, headers: KeyValue, requests: Request}, options);
@ -164,11 +165,14 @@ export class Scenario extends BaseConfig {
isValid() {
for (let i = 0; i < this.requests.length; i++) {
if (this.requests[i].isValid()) {
return true;
if (!this.requests[i].isValid()) {
return false;
}
}
return false;
if (!this.name) {
return false;
}
return true;
}
}
@ -177,12 +181,15 @@ export class Request extends BaseConfig {
super();
this.name = undefined;
this.url = undefined;
this.path = undefined;
this.method = undefined;
this.parameters = [];
this.headers = [];
this.body = undefined;
this.assertions = undefined;
this.extract = undefined;
this.environment = undefined;
this.useEnvironment = undefined;
this.set(options);
this.sets({parameters: KeyValue, headers: KeyValue}, options);
@ -198,7 +205,7 @@ export class Request extends BaseConfig {
}
isValid() {
return !!this.url && !!this.method
return ((!this.useEnvironment && !!this.url) || (this.useEnvironment && !!this.path && this.environment)) && !!this.method
}
}
@ -392,10 +399,19 @@ class JMXRequest {
this.method = request.method;
this.hostname = decodeURIComponent(url.hostname);
this.pathname = decodeURIComponent(url.pathname);
this.path = decodeURIComponent(request.path);
this.useEnvironment = request.useEnvironment;
this.environment = request.environment;
this.port = url.port;
this.protocol = url.protocol.split(":")[0];
if (this.method.toUpperCase() !== "GET") {
this.pathname += url.search.replace('&', '&amp;');
// this.pathname += url.search.replace('&', '&amp;');
this.pathname += '?';
request.parameters.forEach(parameter => {
if (parameter.name) {
this.pathname += (parameter.name + '=' + parameter.value + '&');
}
});
}
}
}
@ -463,6 +479,22 @@ class JMXGenerator {
}
addScenarioVariables(threadGroup, scenario) {
let scenarioVariableKeys = new Set();
scenario.variables.forEach(item => {
scenarioVariableKeys.add(item.name);
});
let environment = scenario.environment;
if (environment) {
let envVariables = environment.variables;
if (!(envVariables instanceof Array)) {
envVariables = JSON.parse(environment.variables);
envVariables.forEach(item => {
if (item.name && !scenarioVariableKeys.has(item.name)) {
scenario.variables.push(new KeyValue(item.name, item.value));
}
})
}
}
let args = this.filterKV(scenario.variables);
if (args.length > 0) {
let name = scenario.name + " Variables"
@ -471,6 +503,22 @@ class JMXGenerator {
}
addScenarioHeaders(threadGroup, scenario) {
let scenarioHeaderKeys = new Set();
scenario.headers.forEach(item => {
scenarioHeaderKeys.add(item.name);
});
let environment = scenario.environment;
if (environment) {
let envHeaders = environment.headers;
if (!(envHeaders instanceof Array)) {
envHeaders = JSON.parse(environment.headers);
envHeaders.forEach(item => {
if (item.name && !scenarioHeaderKeys.has(item.name)) {
scenario.headers.push(new KeyValue(item.name, item.value));
}
})
}
}
let headers = this.filterKV(scenario.headers);
if (headers.length > 0) {
let name = scenario.name + " Headers"

View File

@ -10,7 +10,7 @@
<crontab-result v-show="false" :ex="schedule.value" ref="crontabResult" @resultListChange="resultListChange"/>
</div>
<div>
<span :class="{'disable-character': !schedule.enable}"> 下次执行时间{{this.recentList.length > 0 ? this.recentList[0] : '未设置'}} </span>
<span :class="{'disable-character': !schedule.enable}"> {{$t('schedule.next_execution_time')}}{{this.recentList.length > 0 ? this.recentList[0] : $t('schedule.not_set')}} </span>
</div>
</div>
</template>

View File

@ -1,17 +1,17 @@
<template>
<el-dialog width="30%" class="schedule-edit" :title="'编辑定时任务'" :visible.sync="dialogVisible" @close="close">
<el-dialog width="30%" class="schedule-edit" :title="$t('schedule.edit_timer_task')" :visible.sync="dialogVisible" @close="close">
<div id="app">
<el-form :model="form" :rules="rules" ref="from">
<el-form-item
:placeholder="'请输入 Cron 表达式'"
:placeholder="$t('schedule.please_input_cron_expression')"
prop="cronValue">
<el-input v-model="form.cronValue" placeholder class="inp"/>
<el-button type="primary" @click="showCronDialog">生成 Cron</el-button>
<el-button type="primary" @click="saveCron">保存</el-button>
<el-button type="primary" @click="showCronDialog">{{$t('schedule.generate_expression')}}</el-button>
<el-button type="primary" @click="saveCron">{{$t('commons.save')}}</el-button>
</el-form-item>
<crontab-result :ex="form.cronValue" ref="crontabResult" />
</el-form>
<el-dialog title="生成 cron" :visible.sync="showCron" :modal="false">
<el-dialog :title="$t('schedule.generate_expression')" :visible.sync="showCron" :modal="false">
<crontab @hide="showCron=false" @fill="crontabFill" :expression="schedule.value" ref="crontab"/>
</el-dialog>
</div>
@ -46,9 +46,9 @@
const validateCron = (rule, cronValue, callback) => {
let customValidate = this.customValidate(this.getIntervalTime());
if (!cronValidate(cronValue)) {
callback(new Error('Cron 表达式格式错误'));
callback(new Error(this.$t('schedule.cron_expression_format_error')));
} else if(!this.intervalShortValidate()) {
callback(new Error('间隔时间请大于 5 分钟'));
callback(new Error(this.$t('schedule.cron_expression_interval_short_error')));
} else if (!customValidate.pass){
callback(new Error(customValidate.info));
} else {

View File

@ -1,7 +1,7 @@
<template>
<div>
<el-tabs type="border-card">
<el-tab-pane label="秒" v-if="shouldHide('second')">
<el-tab-pane :label="$t('schedule.cron.seconds')" v-if="shouldHide('second')">
<crontab-second
@update="updateContabValue"
:check="checkNumber"
@ -9,7 +9,7 @@
/>
</el-tab-pane>
<el-tab-pane label="分钟" v-if="shouldHide('min')">
<el-tab-pane :label="$t('schedule.cron.minutes')" v-if="shouldHide('min')">
<crontab-min
@update="updateContabValue"
:check="checkNumber"
@ -18,7 +18,7 @@
/>
</el-tab-pane>
<el-tab-pane label="小时" v-if="shouldHide('hour')">
<el-tab-pane :label="$t('schedule.cron.hours')" v-if="shouldHide('hour')">
<crontab-hour
@update="updateContabValue"
:check="checkNumber"
@ -27,7 +27,7 @@
/>
</el-tab-pane>
<el-tab-pane label="日" v-if="shouldHide('day')">
<el-tab-pane :label="$t('schedule.cron.day')" v-if="shouldHide('day')">
<crontab-day
@update="updateContabValue"
:check="checkNumber"
@ -36,7 +36,7 @@
/>
</el-tab-pane>
<el-tab-pane label="月" v-if="shouldHide('mouth')">
<el-tab-pane :label="$t('schedule.cron.month')" v-if="shouldHide('mouth')">
<crontab-mouth
@update="updateContabValue"
:check="checkNumber"
@ -45,7 +45,7 @@
/>
</el-tab-pane>
<el-tab-pane label="周" v-if="shouldHide('week')">
<el-tab-pane :label="$t('schedule.cron.weeks')" v-if="shouldHide('week')">
<crontab-week
@update="updateContabValue"
:check="checkNumber"
@ -54,7 +54,7 @@
/>
</el-tab-pane>
<el-tab-pane label="年" v-if="shouldHide('year')">
<el-tab-pane :label="$t('schedule.cron.years')" v-if="shouldHide('year')">
<crontab-year @update="updateContabValue"
:check="checkNumber"
:cron="contabValueObj"
@ -64,11 +64,11 @@
<div class="popup-main">
<div class="popup-result-container">
<p class="title">时间表达式</p>
<p class="title">{{$t('schedule.cron.time_expression')}}</p>
<table>
<thead>
<th v-for="item of tabTitles" width="40" :key="item">{{item}}</th>
<th>crontab完整表达式</th>
<th>{{$t('schedule.cron.complete_expression')}}</th>
</thead>
<tbody>
<td>
@ -101,9 +101,9 @@
<crontab-result :ex="contabValueString" ref="crontabResult"/>
<div class="pop_btn">
<el-button size="small" type="primary" @click="submitFill">确定</el-button>
<el-button size="small" type="warning" @click="clearCron">重置</el-button>
<el-button size="small" @click="hidePopup">取消</el-button>
<el-button size="small" type="primary" @click="submitFill">{{$t('commons.confirm')}}</el-button>
<el-button size="small" type="warning" @click="clearCron">{{$t('api_test.reset')}}</el-button>
<el-button size="small" @click="hidePopup">{{$t('commons.cancel')}}</el-button>
</div>
</div>
</div>
@ -123,7 +123,14 @@
name: "Crontab",
data() {
return {
tabTitles: ["秒", "分钟", "小时", "日", "月", "周", "年"],
tabTitles: [
this.$t('schedule.cron.seconds'),
this.$t('schedule.cron.minutes'),
this.$t('schedule.cron.hours'),
this.$t('schedule.cron.day'),
this.$t('schedule.cron.month'),
this.$t('schedule.cron.weeks'),
this.$t('schedule.cron.years')],
tabActive: 0,
myindex: 0,
contabValueObj: {
@ -179,7 +186,7 @@
"updateContabValue", name, value, from;
this.contabValueObj[name] = value;
if (from && from !== name) {
console.log(`来自组件 ${from} 改变了 ${name} ${value}`);
// console.log(` ${from} ${name} ${value}`);
this.changeRadio(name, value);
}
},
@ -310,7 +317,7 @@
},
clearCron() {
//
("准备还原");
// ("");
this.contabValueObj = {
second: "*",
min: "*",

View File

@ -2,49 +2,49 @@
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * / L M]
{{$t('schedule.cron.day')}}{{$t('schedule.cron.day_allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
不指定
{{$t('schedule.cron.not_specify')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
周期从
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}
<el-input-number v-model='cycle01' :min="0" :max="31" /> -
<el-input-number v-model='cycle02' :min="0" :max="31" />
<el-input-number v-model='cycle02' :min="0" :max="31" /> {{$t('schedule.cron.day')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="0" :max="31" /> 号开始
<el-input-number v-model='average02' :min="0" :max="31" /> 日执行一次
{{$t('schedule.cron.from')}}
<el-input-number v-model='average01' :min="0" :max="31" /> {{$t('schedule.cron.day_unit')}}{{$t('schedule.cron.start')}}{{$t('schedule.cron.every')}}
<el-input-number v-model='average02' :min="0" :max="31" /> {{$t('schedule.cron.day')}}{{$t('schedule.cron.execute_once')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
每月
<el-input-number v-model='workday' :min="0" :max="31" /> 号最近的那个工作日
{{$t('schedule.cron.every')}}{{$t('schedule.cron.month')}}
<el-input-number v-model='workday' :min="0" :max="31" /> {{$t('schedule.cron.day_unit')}}{{$t('schedule.cron.last_working_day')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
本月最后一天
{{$t('schedule.cron.last_working_day')}}{{$t('schedule.cron.last_day_of_the_month')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="7">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
{{$t('schedule.cron.specify')}}
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple style="width:100%">
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option>
</el-select>
</el-radio>

View File

@ -2,13 +2,13 @@
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
小时允许的通配符[, - * /]
</el-radio>
{{$t('schedule.cron.hours')}}{{$t('schedule.cron.allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}
<el-input-number v-model='cycle01' :min="0" :max="60" /> -
<el-input-number v-model='cycle02' :min="0" :max="60" /> 小时
</el-radio>
@ -16,16 +16,16 @@
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="60" /> 小时开始
<el-input-number v-model='average02' :min="0" :max="60" /> 小时执行一次
{{$t('schedule.cron.from')}}
<el-input-number v-model='average01' :min="0" :max="60" /> {{$t('schedule.cron.hours')}}{{$t('schedule.cron.start')}}{{$t('schedule.cron.every')}}
<el-input-number v-model='average02' :min="0" :max="60" /> {{$t('schedule.cron.hours')}}{{$t('schedule.cron.execute_once')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
{{$t('schedule.cron.specify')}}
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>

View File

@ -2,30 +2,30 @@
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
分钟允许的通配符[, - * /]
</el-radio>
{{$t('schedule.cron.minutes')}}{{$t('schedule.cron.allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}
<el-input-number v-model='cycle01' :min="0" :max="60" /> -
<el-input-number v-model='cycle02' :min="0" :max="60" /> 分钟
<el-input-number v-model='cycle02' :min="0" :max="60" /> {{$t('schedule.cron.minutes')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="60" /> 分钟开始
<el-input-number v-model='average02' :min="0" :max="60" /> 分钟执行一次
{{$t('schedule.cron.from')}}
<el-input-number v-model='average01' :min="0" :max="60" /> {{$t('schedule.cron.minutes')}}{{$t('schedule.cron.start')}}{{$t('schedule.cron.every')}}
<el-input-number v-model='average02' :min="0" :max="60" /> {{$t('schedule.cron.minutes')}}{{$t('schedule.cron.execute_once')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
{{$t('schedule.cron.specify')}}
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>

View File

@ -2,30 +2,30 @@
<el-form size='small'>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /]
{{$t('schedule.cron.month')}}{{$t('schedule.cron.allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}
<el-input-number v-model='cycle01' :min="1" :max="12" /> -
<el-input-number v-model='cycle02' :min="1" :max="12" />
<el-input-number v-model='cycle02' :min="1" :max="12" /> {{$t('schedule.cron.month')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="1" :max="12" /> 月开始
<el-input-number v-model='average02' :min="1" :max="12" /> 月月执行一次
{{$t('schedule.cron.from')}}
<el-input-number v-model='average01' :min="1" :max="12" /> {{$t('schedule.cron.month')}}{{$t('schedule.cron.start')}}{{$t('schedule.cron.every')}}
<el-input-number v-model='average02' :min="1" :max="12" /> {{$t('schedule.cron.month')}}{{$t('schedule.cron.execute_once')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
{{$t('schedule.cron.specify')}}
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple style="width:100%">
<el-option v-for="item in 12" :key="item" :value="item">{{item}}</el-option>
</el-select>
</el-radio>

View File

@ -1,6 +1,6 @@
<template>
<div class="popup-result">
<p class="title">最近5次运行时间</p>
<p class="title">{{$t('schedule.cron.recent_run_time')}}</p>
<ul class="popup-result-scroll">
<template>
<li v-for='item in resultList' :key="item">{{item}}</li>
@ -340,12 +340,12 @@ export default {
}
// 100
if (resultArr.length == 0) {
this.resultList = ['没有达到条件的结果!'];
this.resultList = [this.$t('schedule.cron.no_qualifying_results')];
} else {
this.resultList = resultArr;
if (resultArr.length !== 5) {
this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!')
}
// if (resultArr.length !== 5) {
// this.resultList.push('100' + resultArr.length + '')
// }
}
this.$emit("resultListChange", this.resultList);

View File

@ -2,30 +2,30 @@
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /]
{{$t('schedule.cron.seconds')}}{{$t('schedule.cron.allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}
<el-input-number v-model='cycle01' :min="0" :max="60" /> -
<el-input-number v-model='cycle02' :min="0" :max="60" />
<el-input-number v-model='cycle02' :min="0" :max="60" /> {{$t('schedule.cron.seconds')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="60" /> 秒开始
<el-input-number v-model='average02' :min="0" :max="60" /> 秒执行一次
{{$t('schedule.cron.from')}}
<el-input-number v-model='average01' :min="0" :max="60" /> {{$t('schedule.cron.seconds')}}{{$t('schedule.cron.start')}}{{$t('schedule.cron.every')}}
<el-input-number v-model='average02' :min="0" :max="60" /> {{$t('schedule.cron.seconds')}}{{$t('schedule.cron.execute_once')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
{{$t('schedule.cron.specify')}}
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>

View File

@ -2,19 +2,19 @@
<el-form size='small'>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * / L #]
{{$t('schedule.cron.weeks')}}{{$t('schedule.cron.weeks_allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
不指定
{{$t('schedule.cron.not_specify')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
周期从星期
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}{{$t('schedule.cron.week')}}
<el-input-number v-model='cycle01' :min="1" :max="7" /> -
<el-input-number v-model='cycle02' :min="1" :max="7" />
</el-radio>
@ -22,23 +22,23 @@
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="4" /> 周的星期
{{$t('schedule.cron.num')}}
<el-input-number v-model='average01' :min="1" :max="4" /> {{$t('schedule.cron.week_of_weeks')}}
<el-input-number v-model='average02' :min="1" :max="7" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
本月最后一个星期
{{$t('schedule.cron.last_week_of_the_month')}}
<el-input-number v-model='weekday' :min="1" :max="7" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
{{$t('schedule.cron.specify')}}
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple style="width:100%">
<el-option v-for="(item,index) of weekList" :key="index" :value="index+1">{{item}}</el-option>
</el-select>
</el-radio>
@ -58,7 +58,15 @@ export default {
average01: 1,
average02: 1,
checkboxList: [],
weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
weekList: [
this.$t('commons.weeks_1'),
this.$t('commons.weeks_2'),
this.$t('commons.weeks_3'),
this.$t('commons.weeks_4'),
this.$t('commons.weeks_5'),
this.$t('commons.weeks_6'),
this.$t('commons.weeks_0'),
],
checkNum: this.$options.propsData.check
}
},

View File

@ -2,29 +2,29 @@
<el-form size="small">
<el-form-item>
<el-radio :label="1" v-model='radioValue'>
不填允许的通配符[, - * /]
</el-radio>
{{$t('schedule.cron.not_fill')}}{{$t('schedule.cron.allowed_wildcards')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="2" v-model='radioValue'>
每年
{{$t('schedule.cron.every')}}{{$t('schedule.cron.years')}}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="3" v-model='radioValue'>
周期从
<el-input-number v-model='cycle01' :min='fullYear' /> -
{{$t('schedule.cron.period')}} {{$t('schedule.cron.from')}}
<el-input-number v-model='cycle01' :min='fullYear' /> -
<el-input-number v-model='cycle02' :min='fullYear' />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="4" v-model='radioValue'>
<el-input-number v-model='average01' :min='fullYear' /> 年开始
<el-input-number v-model='average02' :min='fullYear' /> 年执行一次
{{$t('schedule.cron.from')}}
<el-input-number v-model='average01' :min='fullYear' /> {{$t('schedule.cron.years')}}{{$t('schedule.cron.start')}}{{$t('schedule.cron.every')}}
<el-input-number v-model='average02' :min='fullYear' /> {{$t('schedule.cron.years')}}{{$t('schedule.cron.execute_once')}}
</el-radio>
</el-form-item>
@ -32,7 +32,7 @@
<el-form-item>
<el-radio :label="5" v-model='radioValue'>
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-select clearable v-model="checkboxList" :placeholder="$t('schedule.cron.multi_select')" multiple>
<el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" />
</el-select>
</el-radio>

View File

@ -139,7 +139,11 @@
}
},
initWebSocket() {
const uri = "ws://" + window.location.host + "/performance/report/" + this.reportId;
let protocol = "ws://";
if (window.location.protocol === 'https:') {
protocol = "wss://";
}
const uri = protocol + window.location.host + "/performance/report/" + this.reportId;
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;

View File

@ -131,12 +131,15 @@
let transactions = data.reduce(function (total, currentValue) {
return total + parseFloat(currentValue.transactions);
}, 0);
transactions = transactions.toFixed(2);
let received = data.reduce(function (total, currentValue) {
return total + parseFloat(currentValue.received);
}, 0);
received = received.toFixed(2);
let sent = data.reduce(function (total, currentValue) {
return total + parseFloat(currentValue.sent);
}, 0);
sent = sent.toFixed(2);
let error = (Math.round(failSize / allSamples * 10000) / 100) + '%';
let averageTime = (averageTimeTotal / allSamples).toFixed(2);

View File

@ -268,7 +268,7 @@
url = '/performance/schedule/update';
}
this.$post(url, param, response => {
this.$success('保存成功');
this.$success(this.$t('commons.save_success'));
this.getTest(this.testPlan.id);
});
},

View File

@ -44,7 +44,7 @@
</el-card>
</ms-main-container>
<el-dialog :title="title" :visible.sync="createVisible">
<el-dialog :title="title" :visible.sync="createVisible" destroy-on-close>
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"></el-input>

View File

@ -57,7 +57,7 @@
</el-dialog>
<!--Change personal password-->
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="35%" left>
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="35%" :before-close='closeDialog' left >
<el-form :model="ruleForm" :rules="rules" ref="editPasswordForm" label-width="120px" class="demo-ruleForm">
<el-form-item :label="$t('member.old_password')" prop="password" style="margin-bottom: 29px">
<el-input v-model="ruleForm.password" autocomplete="off" show-password/>
@ -68,7 +68,7 @@
</el-form>
<span slot="footer" class="dialog-footer">
<ms-dialog-footer
@cancel="editPasswordVisible = false"
@cancel="cancel()"
@confirm="updatePassword('editPasswordForm')"/>
</span>
</el-dialog>
@ -156,6 +156,16 @@
editPassword(row) {
this.editPasswordVisible = true;
},
cancel(){
this.editPasswordVisible = false;
this.ruleForm.password="";
this.ruleForm.newpassword="";
},
closeDialog(){
this.editPasswordVisible = false;
this.ruleForm.password="";
this.ruleForm.newpassword="";
},
updateUser(updateUserForm) {
this.$refs[updateUserForm].validate(valid => {
if (valid) {

View File

@ -18,9 +18,9 @@
<el-form-item :label="$t('ldap.filter')" prop="filter">
<el-input v-model="form.filter" :placeholder="$t('ldap.input_filter_placeholder')"></el-input>
</el-form-item>
<el-form-item :label="$t('ldap.mapping')" prop="mapping">
<el-input v-model="form.mapping" :placeholder="$t('ldap.input_mapping')"></el-input>
</el-form-item>
<!-- <el-form-item :label="$t('ldap.mapping')" prop="mapping">-->
<!-- <el-input v-model="form.mapping" :placeholder="$t('ldap.input_mapping')"></el-input>-->
<!-- </el-form-item>-->
<el-form-item :label="$t('ldap.open')" prop="open">
<el-checkbox v-model="form.open"></el-checkbox>
</el-form-item>
@ -29,7 +29,7 @@
<div>
<el-button type="primary" size="small" :disabled="!show" @click="testConnection">{{$t('ldap.test_connect')}}
</el-button>
<el-button type="primary" size="small" :disabled="!show" @click="testLogin">{{$t('ldap.test_login')}}
<el-button type="primary" size="small" :disabled="!showLogin || !show" @click="testLogin">{{$t('ldap.test_login')}}
</el-button>
<el-button v-if="showEdit" size="small" @click="edit">{{$t('ldap.edit')}}</el-button>
<el-button type="success" v-if="showSave" size="small" @click="save('form')">{{$t('commons.save')}}</el-button>
@ -75,12 +75,14 @@
showEdit: true,
showSave: false,
showCancel: false,
showLogin: false,
loginVisible: false,
rules: {
url: {required: true, message: this.$t('ldap.input_url'), trigger: ['change', 'blur']},
dn: {required: true, message: this.$t('ldap.input_dn'), trigger: ['change', 'blur']},
password: {required: true, message: this.$t('ldap.input_password'), trigger: ['change', 'blur']},
ou: {required: true, message: this.$t('ldap.input_ou'), trigger: ['change', 'blur']},
filter: {required: true, message: this.$t('ldap.input_filter'), trigger: ['change', 'blur']}
},
loginFormRules: {
username: {required: true, message: this.$t('ldap.input_username'), trigger: 'blur'},
@ -120,6 +122,9 @@
}
this.result = this.$post("/ldap/test/connect", this.form, response => {
this.$success(this.$t('commons.connection_successful'));
this.showLogin = true;
}, () => {
this.showLogin = false;
})
},
testLogin() {
@ -132,6 +137,11 @@
return false;
}
if (!this.form.filter) {
this.$warning(this.$t('ldap.filter_cannot_be_empty'));
return false;
}
this.loginForm = {};
this.loginVisible = true;
},
@ -172,6 +182,7 @@
this.showEdit = true;
this.showSave = false;
this.showCancel = false;
this.showLogin = false;
this.$success(this.$t('commons.save_success'));
this.init();
});

View File

@ -21,7 +21,144 @@
},
data() {
return {
activeName: 'email'
formInline: {
/*host: 'smtp.163.com',
port: '465',
account: 'xjj0608@163.com',
password: '2345678',*/
},
input: '',
visible: true,
result: {},
showEdit: true,
showSave: false,
showCancel: false,
show: true,
disabledConnection: false,
disabledSave: false,
loading: false,
activeName: 'email',
rules: {
host: [
{
required: true,
message: ' '
},
],
port: [
{
required: true,
message: ' '
}
],
account: [
{
required: true,
message: ' '
}]
}
}
},
activated() {
this.query()
this.change()
},
methods: {
changeType() {
this.$refs.input = 'password'
},
query() {
this.result = this.$get("/system/mail/info", response => {
this.$set(this.formInline, "host", response.data[0].paramValue);
this.$set(this.formInline, "port", response.data[1].paramValue);
this.$set(this.formInline, "account", response.data[2].paramValue);
this.$set(this.formInline, "password", response.data[3].paramValue);
if(response.data[4].paramValue!=""){
this.$set(this.formInline, "SSL", JSON.parse(response.data[4].paramValue));
}
if(response.data[5].paramValue!=""){
this.$set(this.formInline, "TLS", JSON.parse(response.data[5].paramValue));
}
if(response.data[6].paramValue!=""){
this.$set(this.formInline, "SMTP", JSON.parse(response.data[6].paramValue));
}
})
},
change() {
if (!this.formInline.host || !this.formInline.port || !this.formInline.account) {
this.disabledConnection = true;
this.disabledSave = true;
} else {
this.disabledConnection = false;
this.disabledSave = false;
}
},
testConnection(formInline) {
let param = {
"smtp.server": this.formInline.host,
"smtp.port": this.formInline.port,
"smtp.account": this.formInline.account,
"smtp.password": this.formInline.password,
"smtp.ssl": this.formInline.SSL,
"smtp.tls": this.formInline.TLS,
"smtp.smtp": this.formInline.SMTP,
};
this.$refs[formInline].validate((valid) => {
if (valid) {
this.result = this.$post("/system/testConnection", param, response => {
this.$success(this.$t('commons.connection_successful'));
})
} else {
return false;
}
})
},
edit() {
this.change()
this.showEdit = false;
this.showSave = true;
this.showCancel = true;
this.show = false;
},
save(formInline) {
this.showEdit = true;
this.showCancel = false;
this.showSave = false;
this.show = true;
let param = [
{paramKey: "smtp.host", paramValue: this.formInline.host, type: "text", sort: 1},
{paramKey: "smtp.port", paramValue: this.formInline.port, type: "text", sort: 2},
{paramKey: "smtp.account", paramValue: this.formInline.account, type: "text", sort: 3},
{paramKey: "smtp.password", paramValue: this.formInline.password, type: "password", sort: 4},
{paramKey: "smtp.ssl", paramValue: this.formInline.SSL, type: "text", sort: 5},
{paramKey: "smtp.tls", paramValue: this.formInline.TLS, type: "text", sort: 6},
{paramKey: "smtp.smtp", paramValue: this.formInline.SMTP, type: "text", sort: 7}
]
this.$refs[formInline].validate(valid => {
if (valid) {
this.result = this.$post("/system/edit/email", param, response => {
let flag = response.success;
if (flag) {
this.$success(this.$t('commons.save_success'));
} else {
this.$message.error(this.$t('commons.save_failed'));
}
});
} else {
return false;
}
})
},
cancel() {
this.query();
this.showEdit = true;
this.showCancel = false;
this.showSave = false;
this.show = true;
this.change()
}
}
}

View File

@ -9,7 +9,7 @@
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="ID"/>
<el-table-column prop="name" :label="$t('commons.username')" width="200"/>
<el-table-column prop="name" :label="$t('commons.name')" width="200"/>
<el-table-column :label="$t('commons.role')" width="120">
<template v-slot:default="scope">
<ms-roles-tag :roles="scope.row.roles"/>
@ -162,7 +162,7 @@
</el-dialog>
<!--Modify user information in system settings-->
<el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true"
<el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="35%" :destroy-on-close="true"
@close="handleClose">
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule" ref="updateUserForm">
<el-form-item label="ID" prop="id">

View File

@ -355,6 +355,13 @@
this.$success(this.$t('commons.save_success'));
if (this.operationType == 'add' && this.isCreateContinue) {
this.form.name = '';
this.form.prerequisite = '';
this.form.steps = [{
num: 1,
desc: '',
result: ''
}];
this.form.remark = '';
this.$emit("refresh");
return;
}

View File

@ -16,7 +16,7 @@
:limit="1"
action=""
:on-exceed="handleExceed"
:beforeUpload="UploadValidate"
:beforeUpload="uploadValidate"
:on-error="handleError"
:show-file-list="false"
:http-request="upload"
@ -66,8 +66,8 @@
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
UploadValidate(file) {
var suffix =file.name.substring(file.name.lastIndexOf('.') + 1);
uploadValidate(file) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (suffix != 'xls' && suffix != 'xlsx') {
this.$warning(this.$t('test_track.case.import.upload_limit_format'));
return false;

View File

@ -19,26 +19,52 @@
<el-container>
<el-main class="case-content" v-loading="result.loading">
<el-table
:data="testCases"
row-key="id"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="70vh"
ref="table">
<el-row>
<el-col :offset="16" :span="8">
<ms-table-search-bar :condition.sync="condition" @change="initData"/>
</el-col>
</el-row>
<el-table
:data="testCases"
@filter-change="filter"
row-key="id"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="70vh"
ref="table">
<el-table-column
type="selection"></el-table-column>
<el-table-column
type="selection"></el-table-column>
<el-table-column
prop="name"
:label="$t('test_track.case.name')"
style="width: 100%">
<template v-slot:default="scope">
{{scope.row.name}}
</template>
</el-table-column>
</el-table>
<el-table-column
prop="name"
:label="$t('test_track.case.name')"
style="width: 100%">
<template v-slot:default="scope">
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column
prop="priority"
:filters="priorityFilters"
column-key="priority"
:label="$t('test_track.case.priority')"
show-overflow-tooltip>
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority"/>
</template>
</el-table-column>
<el-table-column
prop="type"
:filters="typeFilters"
column-key="type"
:label="$t('test_track.case.type')"
show-overflow-tooltip>
<template v-slot:default="scope">
<type-table-item :value="scope.row.type"/>
</template>
</el-table-column>
</el-table>
</el-main>
</el-container>
</el-container>
@ -56,107 +82,129 @@
import NodeTree from '../../../common/NodeTree';
import MsDialogFooter from '../../../../common/components/MsDialogFooter'
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
import {_filter} from "../../../../../../common/js/utils";
import MsTableSearchBar from "../../../../common/components/MsTableSearchBar";
export default {
name: "TestCaseRelevance",
components: {NodeTree, MsDialogFooter},
data() {
return {
result: {},
dialogFormVisible: false,
isCheckAll: false,
testCases: [],
selectIds: new Set(),
treeNodes: [],
selectNodeIds: [],
selectNodeNames: []
};
},
props: {
planId: {
type: String
}
},
watch: {
planId() {
this.initData();
},
selectNodeIds() {
this.getCaseNames();
}
},
methods: {
openTestCaseRelevanceDialog() {
this.initData();
this.dialogFormVisible = true;
},
saveCaseRelevance(){
let param = {};
param.planId = this.planId;
param.testCaseIds = [...this.selectIds];
this.$post('/test/plan/relevance' , param, () => {
this.selectIds.clear();
this.$success("保存成功");
this.dialogFormVisible = false;
this.$emit('refresh');
});
},
getCaseNames() {
let param = {};
if (this.planId) {
param.planId = this.planId;
}
if (this.selectNodeIds && this.selectNodeIds.length > 0){
param.nodeIds = this.selectNodeIds;
}
this.result = this.$post('/test/case/name', param, response => {
this.testCases = response.data;
this.testCases.forEach(item => {
item.checked = false;
});
});
},
handleSelectAll(selection) {
if(selection.length > 0){
this.testCases.forEach(item => {
this.selectIds.add(item.id);
});
} else {
this.selectIds.clear();
}
},
handleSelectionChange(selection, row) {
if(this.selectIds.has(row.id)){
this.selectIds.delete(row.id);
} else {
this.selectIds.add(row.id);
}
},
nodeChange(nodeIds, nodeNames) {
this.selectNodeIds = nodeIds;
this.selectNodeNames = nodeNames;
},
initData() {
this.getCaseNames();
this.getAllNodeTreeByPlanId();
},
refresh() {
this.close();
},
getAllNodeTreeByPlanId() {
if (this.planId) {
this.result = this.$get("/case/node/list/all/plan/" + this.planId, response => {
this.treeNodes = response.data;
});
}
},
close() {
this.selectIds.clear();
this.selectNodeIds = [];
this.selectNodeNames = [];
}
name: "TestCaseRelevance",
components: {NodeTree, MsDialogFooter, PriorityTableItem, TypeTableItem, MsTableSearchBar},
data() {
return {
result: {},
dialogFormVisible: false,
isCheckAll: false,
testCases: [],
selectIds: new Set(),
treeNodes: [],
selectNodeIds: [],
selectNodeNames: [],
condition: {},
priorityFilters: [
{text: 'P0', value: 'P0'},
{text: 'P1', value: 'P1'},
{text: 'P2', value: 'P2'},
{text: 'P3', value: 'P3'}
],
typeFilters: [
{text: this.$t('commons.functional'), value: 'functional'},
{text: this.$t('commons.performance'), value: 'performance'},
{text: this.$t('commons.api'), value: 'api'}
]
};
},
props: {
planId: {
type: String
}
},
watch: {
planId() {
this.initData();
},
selectNodeIds() {
this.getCaseNames();
}
},
methods: {
openTestCaseRelevanceDialog() {
this.initData();
this.dialogFormVisible = true;
},
saveCaseRelevance() {
let param = {};
param.planId = this.planId;
param.testCaseIds = [...this.selectIds];
this.$post('/test/plan/relevance', param, () => {
this.selectIds.clear();
this.$success(this.$t('commons.save_success'));
this.dialogFormVisible = false;
this.$emit('refresh');
});
},
getCaseNames() {
let param = {};
if (this.planId) {
// param.planId = this.planId;
this.condition.planId = this.planId;
}
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
// param.nodeIds = this.selectNodeIds;
this.condition.nodeIds = this.selectNodeIds;
}
this.result = this.$post('/test/case/name', this.condition, response => {
this.testCases = response.data;
this.testCases.forEach(item => {
item.checked = false;
});
});
},
handleSelectAll(selection) {
if (selection.length > 0) {
this.testCases.forEach(item => {
this.selectIds.add(item.id);
});
} else {
this.selectIds.clear();
}
},
handleSelectionChange(selection, row) {
if (this.selectIds.has(row.id)) {
this.selectIds.delete(row.id);
} else {
this.selectIds.add(row.id);
}
},
nodeChange(nodeIds, nodeNames) {
this.selectNodeIds = nodeIds;
this.selectNodeNames = nodeNames;
},
initData() {
this.getCaseNames();
this.getAllNodeTreeByPlanId();
},
refresh() {
this.close();
},
getAllNodeTreeByPlanId() {
if (this.planId) {
this.result = this.$get("/case/node/list/all/plan/" + this.planId, response => {
this.treeNodes = response.data;
});
}
},
close() {
this.selectIds.clear();
this.selectNodeIds = [];
this.selectNodeNames = [];
},
filter(filters) {
_filter(filters, this.condition);
this.initData();
},
}
}
</script>
<style scoped>
@ -165,16 +213,18 @@
display: none;
color: black;
}
.tb-edit .current-row .el-input {
display: block;
}
.tb-edit .current-row .el-input+span {
.tb-edit .current-row .el-input + span {
display: none;
}
.node-tree{
.node-tree {
margin-right: 10px;
}
@ -189,10 +239,12 @@
height: 100%;
/*border: 1px solid #EBEEF5;*/
}
.tree-aside {
min-height: 300px;
max-height: 100%;
}
.main-content {
min-height: 300px;
height: 100%;

View File

@ -1,283 +1,285 @@
export default {
commons: {
delete_cancelled: 'Delete cancelled',
workspace: 'Workspace',
organization: 'Organization',
setting: 'Setting',
project: 'Project',
about_us: 'About Us',
'delete_cancelled': 'Delete cancelled',
'workspace': 'Workspace',
'organization': 'Organization',
'setting': 'Setting',
'project': 'Project',
'about_us': 'About Us',
current_project: 'Current Project',
name: 'Name',
description: 'Description',
clear: 'Clear',
save: 'Save',
save_success: 'Saved successfully',
delete_success: 'Deleted successfully',
modify_success: 'Modify Success',
copy_success: 'Copy Success',
delete_cancel: 'Deleted Cancel',
confirm: 'Confirm',
cancel: 'Cancel',
prompt: 'Prompt',
operating: 'Operating',
input_limit: 'Within {0} and {1} characters',
login: 'Sign In',
welcome: 'Welcome back, please enter username and password to log in to MeterSphere',
username: 'Username',
password: 'Password',
input_username: 'Please enter username',
input_password: 'Please enter password',
test: 'Test',
create_time: 'Created Time',
update_time: 'Updated Time',
add: 'Add',
member: 'Member',
email: 'Email',
phone: 'Phone',
role: 'Role',
personal_info: 'Personal Info',
status: 'Status',
show_all: 'Show All',
show: 'Show',
report: 'Report',
user: 'User',
system: 'System',
personal_setting: 'Personal Setting',
test_resource_pool: 'Resource Pool',
system_setting: 'Settings',
api: 'API',
performance: 'Performance',
functional: 'Functional test',
input_content: 'Please enter content',
create: 'Create',
edit: 'Edit',
copy: 'Copy',
refresh: 'Refresh',
remark: 'Remark',
delete: 'Delete',
not_filled: 'Not filled',
please_select: 'Please select',
search_by_name: 'Search by name',
personal_information: 'Personal Information',
exit_system: 'Exit System',
verification: 'Verification',
system_parameter_setting: 'System Parameter Setting',
connection_successful: 'Connection successful',
connection_failed: 'Connection failed',
save_failed: 'Saved failed',
host_cannot_be_empty: 'Host cannot be empty',
port_cannot_be_empty: 'Port cannot be empty',
account_cannot_be_empty: 'Account cannot be empty',
title: 'Title',
custom: 'Custom',
select_date: 'Select date',
calendar_heatmap: 'Calendar Heatmap',
months_1: 'Jan',
months_2: 'Feb',
months_3: 'Mar',
months_4: 'Apr',
months_5: 'May',
months_6: 'Jun',
months_7: 'Jul',
months_8: 'Aug',
months_9: 'Sep',
months_10: 'Oct',
months_11: 'Nov',
months_12: 'Dec',
weeks_0: 'Sun',
weeks_1: 'Mon',
weeks_2: 'Tues',
weeks_3: 'Wed',
weeks_4: 'Thur',
weeks_5: 'Fri',
weeks_6: 'Sat',
test_unit: 'tests',
remove: 'Remove',
remove_cancel: 'Remove Cancel',
remove_success: 'Remove Success',
tips: 'The authentication information has expired, please login again',
not_performed_yet: 'Not performed yet',
incorrect_input: 'Incorrect input',
delete_confirm: 'Please enter the following to confirm deletion:',
login_username: 'ID or email',
input_login_username: 'Please input the user ID or email',
'name': 'Name',
'description': 'Description',
'clear': 'Clear',
'save': 'Save',
'save_success': 'Saved successfully',
'delete_success': 'Deleted successfully',
'modify_success': 'Modify Success',
'copy_success': 'Copy Success',
'delete_cancel': 'Deleted Cancel',
'confirm': 'Confirm',
'cancel': 'Cancel',
'prompt': 'Prompt',
'operating': 'Operating',
'input_limit': 'Within {0} and {1} characters',
'login': 'Sign In',
'welcome': 'Welcome back, please enter username and password to log in to MeterSphere',
'username': 'Username',
'password': 'Password',
'input_username': 'Please enter username',
'input_password': 'Please enter password',
'test': 'Test',
'create_time': 'Created Time',
'update_time': 'Updated Time',
'add': 'Add',
'member': 'Member',
'email': 'Email',
'phone': 'Phone',
'role': 'Role',
'personal_info': 'Personal Info',
'status': 'Status',
'show_all': 'Show All',
'show': 'Show',
'report': 'Report',
'user': 'User',
'system': 'System',
'personal_setting': 'Personal Setting',
'test_resource_pool': 'Resource Pool',
'system_setting': 'Settings',
'api': 'API',
'performance': 'Performance',
'functional': 'Functional test',
'input_content': 'Please enter content',
'create': 'Create',
'edit': 'Edit',
'copy': 'Copy',
'refresh': 'Refresh',
'remark': 'Remark',
'delete': 'Delete',
'not_filled': 'Not filled',
'please_select': 'Please select',
'search_by_name': 'Search by name',
'personal_information': 'Personal Information',
'exit_system': 'Exit System',
'verification': 'Verification',
'system_parameter_setting': 'System Parameter Setting',
'connection_successful': 'Connection successful',
'connection_failed': 'Connection failed',
'save_failed': 'Saved failed',
'host_cannot_be_empty': 'Host cannot be empty',
'port_cannot_be_empty': 'Port cannot be empty',
'account_cannot_be_empty': 'Account cannot be empty',
'title': 'Title',
'custom': 'Custom',
'select_date': 'Select date',
'calendar_heatmap': 'Calendar Heatmap',
'months_1': 'Jan',
'months_2': 'Feb',
'months_3': 'Mar',
'months_4': 'Apr',
'months_5': 'May',
'months_6': 'Jun',
'months_7': 'Jul',
'months_8': 'Aug',
'months_9': 'Sep',
'months_10': 'Oct',
'months_11': 'Nov',
'months_12': 'Dec',
'weeks_0': 'Sun',
'weeks_1': 'Mon',
'weeks_2': 'Tues',
'weeks_3': 'Wed',
'weeks_4': 'Thur',
'weeks_5': 'Fri',
'weeks_6': 'Sat',
'test_unit': 'tests',
'remove': 'Remove',
'remove_cancel': 'Remove Cancel',
'remove_success': 'Remove Success',
'tips': 'The authentication information has expired, please login again',
'not_performed_yet': 'Not performed yet',
'incorrect_input': 'Incorrect input',
'delete_confirm': 'Please enter the following to confirm deletion:',
'login_username': 'ID or email',
'input_login_username': 'Please input the user ID or email',
'input_name': 'Please enter name',
'formatErr': 'Format Error'
},
workspace: {
create: 'Create Workspace',
update: 'Update Workspace',
delete: 'Delete Workspace',
delete_confirm: 'Deleting the workspace will delete all resources (such as related projects, test cases, etc.) under the workspace. Are you sure you want to delete?',
add: 'Add Workspace',
input_name: 'Please enter a workspace name',
search_by_name: 'Search by name',
organization_name: 'Organization Name',
please_choose_organization: 'Please Choose Organization',
please_select_a_workspace_first: 'Please select a workspace first!',
none: 'None Workspace',
select: 'Select Workspace',
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
'create': 'Create Workspace',
'update': 'Update Workspace',
'delete': 'Delete Workspace',
'delete_confirm': 'Deleting the workspace will delete all resources (such as related projects, test cases, etc.) under the workspace. Are you sure you want to delete?',
'add': 'Add Workspace',
'input_name': 'Please enter a workspace name',
'search_by_name': 'Search by name',
'organization_name': 'Organization Name',
'please_choose_organization': 'Please Choose Organization',
'please_select_a_workspace_first': 'Please select a workspace first!',
'none': 'None Workspace',
'select': 'Select Workspace',
'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
},
organization: {
create: 'Create Organization',
modify: 'Modify',
delete: 'Delete Organization',
delete_confirm: 'Deleting this organization will delete all resources (such as related workspaces, projects, test cases, etc.) under this organization. Are you sure you want to delete?',
input_name: 'Please enter a organization name',
select_organization: 'Please select organization',
search_by_name: 'Search by name',
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
none: 'None Organization',
select: 'Select Organization',
'create': 'Create Organization',
'modify': 'Modify',
'delete': 'Delete Organization',
'delete_confirm': 'Deleting this organization will delete all resources (such as related workspaces, projects, test cases, etc.) under this organization. Are you sure you want to delete?',
'input_name': 'Please enter a organization name',
'select_organization': 'Please select organization',
'search_by_name': 'Search by name',
'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
'none': 'None Organization',
'select': 'Select Organization',
},
project: {
name: 'Project name',
recent: 'Recent Projects',
create: 'Create Project',
edit: 'Edit Project',
delete: 'Delete project',
delete_confirm: 'Deleting this project will delete all test resources under this project. Are you sure you want to delete?',
delete_tip: 'Deleting this project will delete all test resources under this project. Are you sure you want to delete?',
search_by_name: 'Search by name',
input_name: 'Please enter a workspace name',
owning_workspace: 'Owning Workspace',
please_choose_workspace: 'Please select Workspace',
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
'name': 'Project name',
'recent': 'Recent Projects',
'create': 'Create Project',
'edit': 'Edit Project',
'delete': 'Delete project',
'delete_confirm': 'Deleting this project will delete all test resources under this project. Are you sure you want to delete?',
'delete_tip': 'Deleting this project will delete all test resources under this project. Are you sure you want to delete?',
'search_by_name': 'Search by name',
'input_name': 'Please enter a workspace name',
'owning_workspace': 'Owning Workspace',
'please_choose_workspace': 'Please select Workspace',
'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
},
member: {
create: 'Create',
modify: 'Modify',
delete_confirm: 'Are you sure you want to delete this Member?',
please_choose_member: 'Please Choose Member',
search_by_name: 'Search by name',
modify_personal_info: 'Modify Personal Information',
edit_password: 'Edit Password',
edit_information: 'Edit Information',
input_name: 'Please enter a user name',
input_email: 'Please enter a email',
special_characters_are_not_supported: 'Special characters are not supported',
mobile_number_format_is_incorrect: 'Mobile number format is incorrect',
email_format_is_incorrect: 'Email format is incorrect',
password_format_is_incorrect: 'Valid password: 8-16 digits, English upper and lower case letters + numbers + special characters (optional)',
old_password: 'Old Password',
new_password: 'New Password',
remove_member: 'Are you sure you want to remove this member',
input_id_or_email: 'Please enter user ID, or user Email',
no_such_user: 'Without this user information, please enter the correct user ID or user Email!',
'create': 'Create',
'modify': 'Modify',
'delete_confirm': 'Are you sure you want to delete this Member?',
'please_choose_member': 'Please Choose Member',
'search_by_name': 'Search by name',
'modify_personal_info': 'Modify Personal Information',
'edit_password': 'Edit Password',
'edit_information': 'Edit Information',
'input_name': 'Please enter a user name',
'input_email': 'Please enter a email',
'special_characters_are_not_supported': 'Special characters are not supported',
'mobile_number_format_is_incorrect': 'Mobile number format is incorrect',
'email_format_is_incorrect': 'Email format is incorrect',
'password_format_is_incorrect': 'Valid password: 8-16 digits, English upper and lower case letters + numbers + special characters (optional)',
'old_password': 'Old Password',
'new_password': 'New Password',
'remove_member': 'Are you sure you want to remove this member',
'input_id_or_email': 'Please enter user ID, or user Email',
'no_such_user': 'Without this user information, please enter the correct user ID or user Email!',
},
user: {
create: 'Create',
modify: 'Modify',
input_name: 'Please enter a user name',
input_id: 'Please enter a ID',
input_email: 'Please enter a email',
input_password: 'Please enter a password',
input_phone: 'Please enter phone number',
special_characters_are_not_supported: 'Special characters are not supported',
mobile_number_format_is_incorrect: 'Mobile number format is incorrect',
email_format_is_incorrect: 'Email format is incorrect',
delete_confirm: 'Are you sure you want to delete this User?',
apikey_delete_confirm: 'Are you sure you want to delete this API Key?',
input_id_placeholder: 'Please enter ID (only supports numbers and English letters)'
'create': 'Create',
'modify': 'Modify',
'input_name': 'Please enter a user name',
'input_id': 'Please enter a ID',
'input_email': 'Please enter a email',
'input_password': 'Please enter a password',
'input_phone': 'Please enter phone number',
'special_characters_are_not_supported': 'Special characters are not supported',
'mobile_number_format_is_incorrect': 'Mobile number format is incorrect',
'email_format_is_incorrect': 'Email format is incorrect',
'delete_confirm': 'Are you sure you want to delete this User?',
'apikey_delete_confirm': 'Are you sure you want to delete this API Key?',
'input_id_placeholder': 'Please enter ID (only supports numbers and English letters)'
},
role: {
please_choose_role: 'Please Choose Role',
admin: 'Admin',
org_admin: 'Org_Admin',
test_manager: 'Test Manager',
test_user: 'Test User',
test_viewer: 'Test Viewer',
add: 'Add Role',
'please_choose_role': 'Please Choose Role',
'admin': 'Admin',
'org_admin': 'Org_Admin',
'test_manager': 'Test Manager',
'test_user': 'Test User',
'test_viewer': 'Test Viewer',
'add': 'Add Role',
},
report: {
recent: 'Recent Report',
search_by_name: 'Search by Name',
test_name: 'Test',
test_overview: 'Test Overview',
test_request_statistics: 'Test Request Statistics',
test_error_log: 'Test Error Log',
test_log_details: 'Test Log Details',
test_details: 'Test Details',
test_duration: 'Test Duration{0} minutes {1} seconds',
test_start_time: 'Test Start Time',
test_end_time: 'Test End Time',
test_stop_now: 'Test Stop Now',
test_stop_now_confirm: 'Are you sure you want to stop the current test immediately?',
test_rerun_confirm: 'Are you sure you want to rerun the current test immediately?',
test_stop_success: 'Test stop successfully',
test_execute_again: 'Test Execute Again',
export: 'Export',
compare: 'Compare',
generation_error: 'Report generation error, cannot be viewed!',
being_generated: 'Report is being generated...',
delete_confirm: 'Confirm delete: ',
start_status: 'The test is starting, please check the report later!',
run_status: 'The test is running, please check the report later',
user_name: 'Creator',
project_name: 'Project Name'
'recent': 'Recent Report',
'search_by_name': 'Search by Name',
'test_name': 'Test',
'test_overview': 'Test Overview',
'test_request_statistics': 'Test Request Statistics',
'test_error_log': 'Test Error Log',
'test_log_details': 'Test Log Details',
'test_details': 'Test Details',
'test_duration': 'Test Duration{0} minutes {1} seconds',
'test_start_time': 'Test Start Time',
'test_end_time': 'Test End Time',
'test_stop_now': 'Test Stop Now',
'test_stop_now_confirm': 'Are you sure you want to stop the current test immediately?',
'test_rerun_confirm': 'Are you sure you want to rerun the current test immediately?',
'test_stop_success': 'Test stop successfully',
'test_execute_again': 'Test Execute Again',
'export': 'Export',
'compare': 'Compare',
'generation_error': 'Report generation error, cannot be viewed!',
'being_generated': 'Report is being generated...',
'delete_confirm': 'Confirm delete: ',
'start_status': 'The test is starting, please check the report later!',
'run_status': 'The test is running, please check the report later',
'user_name': 'Creator',
'project_name': 'Project Name'
},
load_test: {
operating: 'Operating',
pressure_prediction_chart: 'Pressure Prediction Chart',
recent: 'Recent Tests',
search_by_name: 'Search by name',
project_name: 'Project',
delete_confirm: 'Are you sure want to delete test: ',
input_name: 'Please enter name',
select_project: 'Please select project',
save_and_run: 'Save and execute',
basic_config: 'Scene Configuration',
pressure_config: 'Pressure configuration',
advanced_config: 'Advanced Configuration',
runtime_config: 'Runtime Configuration',
is_running: 'Test is running! ',
test_name_is_null: 'Test name cannot be empty! ',
project_is_null: 'Project cannot be empty! ',
jmx_is_null: 'Must contain a JMX file, and can only contain a JMX file!',
file_name: 'File name',
file_size: 'File size',
file_type: 'File Type',
file_status: 'File Status',
last_modify_time: 'Modify time',
upload_tips: 'Drag files here, or <em> click to upload </em>',
upload_type: 'Only JMX/CSV files can be uploaded',
related_file_not_found: "No related test file found!",
delete_file_confirm: 'Confirm delete file:',
file_size_limit: "The number of files exceeds the limit",
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 (minutes):',
input_duration: 'Please enter a duration',
rps_limit: 'RPS Limit:',
input_rps_limit: 'Please enter a limit',
ramp_up_time_within: 'In',
ramp_up_time_minutes: 'minutes, separate',
ramp_up_time_times: 'add concurrent users',
advanced_config_error: 'Advanced configuration verification failed',
domain_bind: 'Domain bind',
domain: 'Domain',
enable: 'Enable',
ip: 'IP',
params: 'Parameters',
param_name: 'Name',
param_value: 'Value',
domain_is_duplicate: 'Domain is duplicated',
param_is_duplicate: 'Parameter name is duplicate',
domain_ip_is_empty: 'Domain and IP cannot be empty',
param_name_value_is_empty: 'Parameters cannot be empty',
connect_timeout: 'Timeout to establish a connection',
custom_http_code: 'Custom HTTP response success status code',
separated_by_commas: 'Separated by commas',
create: 'Create Test',
select_resource_pool: 'Please Select Resource Pool',
resource_pool_is_null: 'Resource Pool is empty',
download_log_file: 'Download',
user_name: 'Creator',
special_characters_are_not_supported: 'Test name does not support special characters',
pressure_config_params_is_empty: 'Pressure configuration parameters cannot be empty!'
'operating': 'Operating',
'pressure_prediction_chart': 'Pressure Prediction Chart',
'recent': 'Recent Tests',
'search_by_name': 'Search by name',
'project_name': 'Project',
'delete_confirm': 'Are you sure want to delete test: ',
'input_name': 'Please enter name',
'select_project': 'Please select project',
'save_and_run': 'Save and execute',
'basic_config': 'Scene Configuration',
'pressure_config': 'Pressure configuration',
'advanced_config': 'Advanced Configuration',
'runtime_config': 'Runtime Configuration',
'is_running': 'Test is running! ',
'test_name_is_null': 'Test name cannot be empty! ',
'project_is_null': 'Project cannot be empty! ',
'jmx_is_null': 'Must contain a JMX file, and can only contain a JMX file!',
'file_name': 'File name',
'file_size': 'File size',
'file_type': 'File Type',
'file_status': 'File Status',
'last_modify_time': 'Modify time',
'upload_tips': 'Drag files here, or <em> click to upload </em>',
'upload_type': 'Only JMX/CSV files can be uploaded',
'related_file_not_found': "No related test file found!",
'delete_file_confirm': 'Confirm delete file:',
'file_size_limit': "The number of files exceeds the limit",
'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 (minutes):',
'input_duration': 'Please enter a duration',
'rps_limit': 'RPS Limit:',
'input_rps_limit': 'Please enter a limit',
'ramp_up_time_within': 'In',
'ramp_up_time_minutes': 'minutes, separate',
'ramp_up_time_times': 'add concurrent users',
'advanced_config_error': 'Advanced configuration verification failed',
'domain_bind': 'Domain bind',
'domain': 'Domain',
'enable': 'Enable',
'ip': 'IP',
'params': 'Parameters',
'param_name': 'Name',
'param_value': 'Value',
'domain_is_duplicate': 'Domain is duplicated',
'param_is_duplicate': 'Parameter name is duplicate',
'domain_ip_is_empty': 'Domain and IP cannot be empty',
'param_name_value_is_empty': 'Parameters cannot be empty',
'connect_timeout': 'Timeout to establish a connection',
'custom_http_code': 'Custom HTTP response success status code',
'separated_by_commas': 'Separated by commas',
'create': 'Create Test',
'select_resource_pool': 'Please Select Resource Pool',
'resource_pool_is_null': 'Resource Pool is empty',
'download_log_file': 'Download',
'user_name': 'Creator',
'special_characters_are_not_supported': 'Test name does not support special characters',
'pressure_config_params_is_empty': 'Pressure configuration parameters cannot be empty!'
},
api_test: {
creator: "Creator",
@ -294,6 +296,15 @@ export default {
value: "Value",
create_performance_test: "Create Performance Test",
export_config: "Export Configuration",
environment: {
name: "Environment Name",
socket: "Socket",
globalVariable: "Global Variable",
environment_list: "Environment List",
environment_config: "Environment Config",
environment: "Environment",
please_save_test: "Please Save Test First",
},
scenario: {
config: "Scenario Config",
input_name: "Please enter the scenario name",
@ -314,7 +325,13 @@ export default {
name: "Name",
method: "Method",
url: "URL",
path: "Path",
address: "Address",
refer_to_environment: "Use Environment",
please_configure_environment_in_scenario: "Please Configure Environment In The Scenario",
please_add_environment_to_scenario: "Please Add The Environment Configuration To The Scenario First",
url_description: "etc: https://fit2cloud.com",
path_description: "etc/login",
parameters: "Query parameters",
parameters_desc: "Parameters will be appended to the URL e.g. https://fit2cloud.com?Name=Value&Name2=Value2",
headers: "Headers",
@ -347,6 +364,19 @@ export default {
json_path_expression: "JSONPath expression",
xpath_expression: "XPath expression",
}
},
api_import: {
label: "Import",
title: "API test import",
data_format: "Data format",
file_size_limit: "The file size does not exceed 20 M",
tip: "Instructions",
export_tip: "Export Tip",
ms_tip: "Support for Metersphere JSON format",
ms_export_tip: "Export jSON-formatted files via Metersphere website or browser plug-ins",
postman_tip: "Only Postman Collection V2.1 json files are supported",
post_man_export_tip: "Export the test collection by Postman",
suffixFormatErr: "The file format does not meet the requirements",
}
},
api_report: {
@ -532,64 +562,106 @@ export default {
}
},
test_resource_pool: {
type: 'type',
enable_disable: 'Enable / disable',
search_by_name: 'Search by name',
create_resource_pool: 'Create resource pool',
update_resource_pool: 'Create resource pool',
select_pool_type: 'Select resource type',
max_threads: 'Maximum concurrent number',
input_pool_name: 'Please enter the resource pool name',
pool_name_valid: 'Resource pool name does not support special characters',
cannot_remove_all_node: 'Cannot delete all independent nodes',
cannot_empty: 'Resource pool cannot be empty',
fill_the_data: 'Please complete the data',
delete_prompt: 'This operation will permanently delete the resource pool, continue?',
status_change_success: 'Successfully changed the status!',
status_change_failed: 'Failed to change the status, resource pool is invalid!',
check_in: 'Check in',
'type': 'type',
'enable_disable': 'Enable / disable',
'search_by_name': 'Search by name',
'create_resource_pool': 'Create resource pool',
'update_resource_pool': 'Create resource pool',
'select_pool_type': 'Select resource type',
'max_threads': 'Maximum concurrent number',
'input_pool_name': 'Please enter the resource pool name',
'pool_name_valid': 'Resource pool name does not support special characters',
'cannot_remove_all_node': 'Cannot delete all independent nodes',
'cannot_empty': 'Resource pool cannot be empty',
'fill_the_data': 'Please complete the data',
'delete_prompt': 'This operation will permanently delete the resource pool, continue?',
'status_change_success': 'Successfully changed the status!',
'status_change_failed': 'Failed to change the status, resource pool is invalid!',
'check_in': 'Check in',
},
system_parameter_setting: {
mailbox_service_settings: 'Mailbox Settings',
ldap_setting: 'LDAP Setting',
test_connection: 'Test connection',
SMTP_host: 'SMTP host',
SMTP_port: 'SMTP port',
SMTP_account: 'SMTP account',
SMTP_password: 'SMTP password',
SSL: 'Turn on SSL (if the SMTP port is 465, you usually need to enable SSL)',
TLS: 'Turn on TLS (if the SMTP port is 587, you usually need to enable TLS)',
SMTP: 'Anonymous SMTP or not',
'mailbox_service_settings': 'Mailbox Settings',
'ldap_setting': 'LDAP Setting',
'test_connection': 'Test connection',
'SMTP_host': 'SMTP host',
'SMTP_port': 'SMTP port',
'SMTP_account': 'SMTP account',
'SMTP_password': 'SMTP password',
'SSL': 'Turn on SSL (if the SMTP port is 465, you usually need to enable SSL)',
'TLS': 'Turn on TLS (if the SMTP port is 587, you usually need to enable TLS)',
'SMTP': 'Anonymous SMTP or not',
},
i18n: {
home: 'Home'
'home': 'Home'
},
ldap: {
url: 'LDAP URL',
dn: 'Bind DN',
password: 'Password',
ou: 'User OU',
filter: 'User Filter',
mapping: 'LDAP Mapping',
open: 'Enable LDAP Authentication',
input_url: 'Please enter LDAP url',
input_dn: 'Please enter DN',
input_password: 'Please enter the password',
input_ou: 'Please enter user OU',
input_filter: 'Please enter a user filter',
input_mapping: 'Please enter LDAP attribute mapping',
input_username: 'please enter user name',
input_url_placeholder: 'Please enter the LDAP address (eg ldap://localhost:389)',
input_ou_placeholder: 'Enter user OU (use | to separate each OU)',
input_filter_placeholder: 'Input filter [Possible options are cn or uid or sAMAccountName=%(user)s]',
test_connect: 'Test Connection',
test_login: 'Test Login',
edit: 'Edit',
login_success: 'login success',
url_cannot_be_empty: 'LDAP address cannot be empty',
dn_cannot_be_empty: 'LDAP DN cannot be empty',
ou_cannot_be_empty: 'LDAP OU cannot be empty',
password_cannot_be_empty: 'LDAP password cannot be empty',
}
'url': 'LDAP URL',
'dn': 'Bind DN',
'password': 'Password',
'ou': 'User OU',
'filter': 'User Filter',
'mapping': 'LDAP Mapping',
'open': 'Enable LDAP Authentication',
'input_url': 'Please enter LDAP url',
'input_dn': 'Please enter DN',
'input_password': 'Please enter the password',
'input_ou': 'Please enter user OU',
'input_filter': 'Please enter a user filter',
'input_mapping': 'Please enter LDAP attribute mapping',
'input_username': 'please enter user name',
'input_url_placeholder': 'Please enter the LDAP address (eg ldap://localhost:389)',
'input_ou_placeholder': 'Enter user OU',
'input_filter_placeholder': 'Input filter [Possible options are cn or uid or sAMAccountName={0}, eg: (uid={0})]',
'test_connect': 'Test Connection',
'test_login': 'Test Login',
'edit': 'Edit',
'login_success': 'login success',
'url_cannot_be_empty': 'LDAP address cannot be empty',
'dn_cannot_be_empty': 'LDAP DN cannot be empty',
'ou_cannot_be_empty': 'LDAP OU cannot be empty',
'filter_cannot_be_empty': 'LDAP user filter cannot be empty',
'password_cannot_be_empty': 'LDAP password cannot be empty',
},
schedule: {
not_set: "Not Set",
next_execution_time: "Next Execution Time",
edit_timer_task: "Edit Timer Task",
please_input_cron_expression: "Please Input Cron Expression",
generate_expression: "Generate Expression",
cron_expression_format_error: "Cron Expression Format Error",
cron_expression_interval_short_error: "Interval Time Should Longer than 5 Minutes",
cron: {
seconds: "Seconds",
minutes: "Minutes",
hours: "Hours",
day: "Day",
month: "Month",
weeks: "Weeks",
years: "Years",
week: "Week",
time_expression: "Time Expression",
complete_expression: "Complete Expression",
allowed_wildcards: "Allowed Wildcards[, - * /]",
day_allowed_wildcards: "Allowed Wildcards[, - * / L M]",
weeks_allowed_wildcards: "Allowed Wildcards[, - * / L M]",
not_specify: "Not Specify",
specify: "Specify",
period: "Period",
from: "From",
every: "Every",
day_unit: "Day Unit",
start: "Start",
execute_once: "Execute Once",
last_working_day: "The Last Working Day",
last_day_of_the_month: "The Last Day Of The Month",
multi_select: "Multi Select",
num: "Number",
week_of_weeks: "Week Of Weeks",
last_week_of_the_month: "The Last Week Of The Month",
not_fill: "Not Fill",
recent_run_time: "Recent 5th Runing Time",
no_qualifying_results: "No Qualifying Results",
}
},
};

View File

@ -1,310 +1,284 @@
export default {
commons: {
delete_cancelled: '已取消删除',
workspace: '工作空间',
organization: '组织',
setting: '设置',
project: '项目',
about_us: '关于',
'delete_cancelled': '已取消删除',
'workspace': '工作空间',
'organization': '组织',
'setting': '设置',
'project': '项目',
'about_us': '关于',
current_project: '当前项目',
name: '名称',
description: '描述',
clear: '清空',
save: '保存',
save_success: '保存成功',
delete_success: '删除成功',
copy_success: '复制成功',
modify_success: '修改成功',
delete_cancel: '已取消删除',
confirm: '确定',
cancel: '取消',
prompt: '提示',
operating: '操作',
input_limit: '长度在 {0} 到 {1} 个字符',
login: '登录',
welcome: '欢迎回来请输入用户名和密码登录MeterSphere',
username: '姓名',
password: '密码',
input_username: '请输入用户姓名',
input_password: '请输入密码',
test: '测试',
create_time: '创建时间',
update_time: '更新时间',
add: '添加',
member: '成员',
email: '邮箱',
phone: '电话',
role: '角色',
personal_info: '个人信息',
status: '状态',
show_all: '显示全部',
show: '显示',
report: '报告',
user: '用户',
system: '系统',
personal_setting: '个人设置',
test_resource_pool: '测试资源池',
system_setting: '系统设置',
api: '接口测试',
performance: '性能测试',
functional: '功能测试',
input_content: '请输入内容',
create: '新建',
edit: '编辑',
copy: '复制',
refresh: '刷新',
remark: '备注',
delete: '删除',
not_filled: '未填写',
please_select: '请选择',
search_by_name: '根据名称搜索',
personal_information: '个人信息',
exit_system: '退出系统',
verification: '验证',
title: '标题',
custom: '自定义',
select_date: '选择日期',
start_date: '开始日期',
end_date: '结束日期',
select_date_time: '选择日期时间',
start_date_time: '开始日期时间',
end_date_time: '结束日期时间',
range_separator: "至",
calendar_heatmap: '测试日历',
months_1: '一月',
months_2: '二月',
months_3: '三月',
months_4: '四月',
months_5: '五月',
months_6: '六月',
months_7: '七月',
months_8: '八月',
months_9: '九月',
months_10: '十月',
months_11: '十一月',
months_12: '十二月',
weeks_0: '周日',
weeks_1: '周一',
weeks_2: '周二',
weeks_3: '周三',
weeks_4: '周四',
weeks_5: '周五',
weeks_6: '周六',
test_unit: '测试',
system_parameter_setting: '系统参数设置',
connection_successful: '连接成功',
connection_failed: '连接失败',
save_failed: '保存失败',
host_cannot_be_empty: '主机不能为空',
port_cannot_be_empty: '端口号不能为空',
account_cannot_be_empty: '帐户不能为空',
remove: '移除',
remove_cancel: '移除取消',
remove_success: '移除成功',
tips: '认证信息已过期,请重新登录',
not_performed_yet: '尚未执行',
incorrect_input: '输入内容不正确',
delete_confirm: '请输入以下内容,确认删除:',
login_username: 'ID 或 邮箱',
input_login_username: '请输入用户 ID 或 邮箱',
search: "查询",
adv_search: {
title: '高级搜索',
combine: '组合查询',
and: '所有',
or: '任意一个',
operators: {
like: "包含",
not_like: "不包含",
in: "属于",
not_in: "不属于",
is: "是",
not_is: "不是",
gt: "大于",
ge: "大于等于",
lt: "小于",
le: "小于等于",
equals: "等于",
between: "之间",
current_user: "是当前登录用户"
}
}
'name': '名称',
'description': '描述',
'clear': '清空',
'save': '保存',
'save_success': '保存成功',
'delete_success': '删除成功',
'copy_success': '复制成功',
'modify_success': '修改成功',
'delete_cancel': '已取消删除',
'confirm': '确定',
'cancel': '取消',
'prompt': '提示',
'operating': '操作',
'input_limit': '长度在 {0} 到 {1} 个字符',
'login': '登录',
'welcome': '欢迎回来请输入用户名和密码登录MeterSphere',
'username': '姓名',
'password': '密码',
'input_username': '请输入用户姓名',
'input_password': '请输入密码',
'test': '测试',
'create_time': '创建时间',
'update_time': '更新时间',
'add': '添加',
'member': '成员',
'email': '邮箱',
'phone': '电话',
'role': '角色',
'personal_info': '个人信息',
'status': '状态',
'show_all': '显示全部',
'show': '显示',
'report': '报告',
'user': '用户',
'system': '系统',
'personal_setting': '个人设置',
'test_resource_pool': '测试资源池',
'system_setting': '系统设置',
'api': '接口测试',
'performance': '性能测试',
'functional': '功能测试',
'input_content': '请输入内容',
'create': '新建',
'edit': '编辑',
'copy': '复制',
'refresh': '刷新',
'remark': '备注',
'delete': '删除',
'not_filled': '未填写',
'please_select': '请选择',
'search_by_name': '根据名称搜索',
'personal_information': '个人信息',
'exit_system': '退出系统',
'verification': '验证',
'title': '标题',
'custom': '自定义',
'select_date': '选择日期',
'calendar_heatmap': '测试日历',
'months_1': '一月',
'months_2': '二月',
'months_3': '三月',
'months_4': '四月',
'months_5': '五月',
'months_6': '六月',
'months_7': '七月',
'months_8': '八月',
'months_9': '九月',
'months_10': '十月',
'months_11': '十一月',
'months_12': '十二月',
'weeks_0': '周日',
'weeks_1': '周一',
'weeks_2': '周二',
'weeks_3': '周三',
'weeks_4': '周四',
'weeks_5': '周五',
'weeks_6': '周六',
'test_unit': '测试',
'system_parameter_setting': '系统参数设置',
'connection_successful': '连接成功',
'connection_failed': '连接失败',
'save_failed': '保存失败',
'host_cannot_be_empty': '主机不能为空',
'port_cannot_be_empty': '端口号不能为空',
'account_cannot_be_empty': '帐户不能为空',
'remove': '移除',
'remove_cancel': '移除取消',
'remove_success': '移除成功',
'tips': '认证信息已过期,请重新登录',
'not_performed_yet': '尚未执行',
'incorrect_input': '输入内容不正确',
'delete_confirm': '请输入以下内容,确认删除:',
'login_username': 'ID 或 邮箱',
'input_login_username': '请输入用户 ID 或 邮箱',
'input_name': '请输入名称',
'formatErr': '格式错误'
},
workspace: {
create: '创建工作空间',
update: '修改工作空间',
delete: '删除工作空间',
delete_confirm: '删除该工作空间会关联删除该工作空间下的所有资源(如:相关项目,测试用例等),确定要删除吗?',
add: '添加工作空间',
input_name: '请输入工作空间名称',
search_by_name: '根据名称搜索',
organization_name: '所属组织',
please_choose_organization: '请选择组织',
please_select_a_workspace_first: '请先选择工作空间!',
none: '无工作空间',
select: '选择工作空间',
special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
delete_warning: '删除该工作空间将同步删除该工作空间下所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
'create': '创建工作空间',
'update': '修改工作空间',
'delete': '删除工作空间',
'delete_confirm': '删除该工作空间会关联删除该工作空间下的所有资源(如:相关项目,测试用例等),确定要删除吗?',
'add': '添加工作空间',
'input_name': '请输入工作空间名称',
'search_by_name': '根据名称搜索',
'organization_name': '所属组织',
'please_choose_organization': '请选择组织',
'please_select_a_workspace_first': '请先选择工作空间!',
'none': '无工作空间',
'select': '选择工作空间',
'special_characters_are_not_supported': '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
'delete_warning': '删除该工作空间将同步删除该工作空间下所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
},
organization: {
create: '创建组织',
modify: '修改组织',
delete: '删除组织',
delete_confirm: '删除该组织会关联删除该组织下的所有资源(如:相关工作空间,项目,测试用例等),确定要删除吗?',
input_name: '请输入组织名称',
select_organization: '请选择组织',
search_by_name: '根据名称搜索',
special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
none: '无组织',
select: '选择组织',
delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
'create': '创建组织',
'modify': '修改组织',
'delete': '删除组织',
'delete_confirm': '删除该组织会关联删除该组织下的所有资源(如:相关工作空间,项目,测试用例等),确定要删除吗?',
'input_name': '请输入组织名称',
'select_organization': '请选择组织',
'search_by_name': '根据名称搜索',
'special_characters_are_not_supported': '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
'none': '无组织',
'select': '选择组织',
'delete_warning': '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
},
project: {
recent: '最近的项目',
create: '创建项目',
edit: '编辑项目',
delete: '删除项目',
delete_confirm: '确定要删除这个项目吗?',
delete_tip: '删除该项目,会删除该项目下所有测试资源,确定要删除吗?',
search_by_name: '根据名称搜索',
input_name: '请输入项目名称',
owning_workspace: '所属工作空间',
please_choose_workspace: '请选择工作空间',
special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
'recent': '最近的项目',
'create': '创建项目',
'edit': '编辑项目',
'delete': '删除项目',
'delete_confirm': '确定要删除这个项目吗?',
'delete_tip': '删除该项目,会删除该项目下所有测试资源,确定要删除吗?',
'search_by_name': '根据名称搜索',
'input_name': '请输入项目名称',
'owning_workspace': '所属工作空间',
'please_choose_workspace': '请选择工作空间',
'special_characters_are_not_supported': '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
},
member: {
create: '添加成员',
modify: '修改成员',
delete_confirm: '这个用户确定要删除吗?',
please_choose_member: '请选择成员',
search_by_name: '根据名称搜索',
modify_personal_info: '修改个人信息',
edit_password: '修改密码',
edit_information: '编辑信息',
input_name: '请输入名称',
input_email: '请输入邮箱',
special_characters_are_not_supported: '不支持特殊字符',
mobile_number_format_is_incorrect: '手机号码格式不正确',
email_format_is_incorrect: '邮箱格式不正确',
password_format_is_incorrect: '有效密码8-16位英文大小写字母+数字+特殊字符(可选)',
old_password: '旧密码',
new_password: '新密码',
remove_member: '确定要移除该成员吗',
input_id_or_email: '请输入用户 ID, 或者 用户邮箱',
no_such_user: '无此用户信息, 请输入正确的用户 ID 或者 用户邮箱!',
'create': '添加成员',
'modify': '修改成员',
'delete_confirm': '这个用户确定要删除吗?',
'please_choose_member': '请选择成员',
'search_by_name': '根据名称搜索',
'modify_personal_info': '修改个人信息',
'edit_password': '修改密码',
'edit_information': '编辑信息',
'input_name': '请输入名称',
'input_email': '请输入邮箱',
'special_characters_are_not_supported': '不支持特殊字符',
'mobile_number_format_is_incorrect': '手机号码格式不正确',
'email_format_is_incorrect': '邮箱格式不正确',
'password_format_is_incorrect': '有效密码8-16位英文大小写字母+数字+特殊字符(可选)',
'old_password': '旧密码',
'new_password': '新密码',
'remove_member': '确定要移除该成员吗',
'input_id_or_email': '请输入用户 ID, 或者 用户邮箱',
'no_such_user': '无此用户信息, 请输入正确的用户 ID 或者 用户邮箱!',
},
user: {
create: '创建用户',
modify: '修改用户',
input_name: '请输入用户姓名',
input_id: '请输入ID',
input_email: '请输入邮箱',
input_password: '请输入密码',
input_phone: '请输入电话号码',
special_characters_are_not_supported: '不支持特殊字符',
mobile_number_format_is_incorrect: '手机号码格式不正确',
email_format_is_incorrect: '邮箱格式不正确',
delete_confirm: '这个用户确定要删除吗?',
apikey_delete_confirm: '这个 API Key 确定要删除吗?',
input_id_placeholder: '请输入ID (只支持数字、英文字母)'
'create': '创建用户',
'modify': '修改用户',
'input_name': '请输入用户姓名',
'input_id': '请输入ID',
'input_email': '请输入邮箱',
'input_password': '请输入密码',
'input_phone': '请输入电话号码',
'special_characters_are_not_supported': '不支持特殊字符',
'mobile_number_format_is_incorrect': '手机号码格式不正确',
'email_format_is_incorrect': '邮箱格式不正确',
'delete_confirm': '这个用户确定要删除吗?',
'apikey_delete_confirm': '这个 API Key 确定要删除吗?',
'input_id_placeholder': '请输入ID (只支持数字、英文字母)'
},
role: {
please_choose_role: '请选择角色',
admin: '系统管理员',
org_admin: '组织管理员',
test_manager: '测试经理',
test_user: '测试人员',
test_viewer: 'Viewer',
add: '添加角色',
'please_choose_role': '请选择角色',
'admin': '系统管理员',
'org_admin': '组织管理员',
'test_manager': '测试经理',
'test_user': '测试人员',
'test_viewer': 'Viewer',
'add': '添加角色',
},
report: {
recent: '最近的报告',
search_by_name: '根据名称搜索',
test_name: '所属测试',
test_overview: '测试概览',
test_request_statistics: '请求统计',
test_error_log: '错误记录',
test_log_details: '日志详情',
test_details: '测试详情',
test_duration: '持续时间:{0} 分钟 {1} 秒',
test_start_time: '开始时间',
test_end_time: '结束时间',
test_stop_now: '立即停止',
test_stop_now_confirm: '确定要立即停止当前测试吗?',
test_rerun_confirm: '确定要再次执行当前测试吗?',
test_stop_success: '停止成功',
test_execute_again: '再次执行',
export: '导出',
compare: '比较',
generation_error: '报告生成错误,无法查看!',
being_generated: '报告正在生成中...',
delete_confirm: '确认删除报告: ',
start_status: '测试处于开始状态,请稍后查看报告!',
run_status: '测试处于运行状态,请稍后查看报告!',
user_name: '创建人',
project_name: '所属项目',
'recent': '最近的报告',
'search_by_name': '根据名称搜索',
'test_name': '所属测试',
'test_overview': '测试概览',
'test_request_statistics': '请求统计',
'test_error_log': '错误记录',
'test_log_details': '日志详情',
'test_details': '测试详情',
'test_duration': '持续时间:{0} 分钟 {1} 秒',
'test_start_time': '开始时间',
'test_end_time': '结束时间',
'test_stop_now': '立即停止',
'test_stop_now_confirm': '确定要立即停止当前测试吗?',
'test_rerun_confirm': '确定要再次执行当前测试吗?',
'test_stop_success': '停止成功',
'test_execute_again': '再次执行',
'export': '导出',
'compare': '比较',
'generation_error': '报告生成错误,无法查看!',
'being_generated': '报告正在生成中...',
'delete_confirm': '确认删除报告: ',
'start_status': '测试处于开始状态,请稍后查看报告!',
'run_status': '测试处于运行状态,请稍后查看报告!',
'user_name': '创建人',
'project_name': '所属项目',
},
load_test: {
operating: '操作',
recent: '最近的测试',
search_by_name: '根据名称搜索',
project_name: '所属项目',
delete_confirm: '确认删除测试: ',
input_name: '请输入名称',
select_project: '请选择项目',
save_and_run: '保存并执行',
basic_config: '场景配置',
pressure_config: '压力配置',
advanced_config: '高级配置',
runtime_config: '运行配置',
is_running: '正在运行!',
test_name_is_null: '测试名称不能为空!',
project_is_null: '项目不能为空!',
jmx_is_null: '必需包含一个JMX文件且只能包含一个JMX文件',
file_name: '文件名',
file_size: '文件大小',
file_type: '文件类型',
file_status: '文件状态',
last_modify_time: '修改时间',
upload_tips: '将文件拖到此处,或<em>点击上传</em>',
upload_type: '只能上传JMX/CSV文件',
related_file_not_found: "未找到关联的测试文件!",
delete_file_confirm: '确认删除文件: ',
file_size_limit: "文件个数超出限制!",
delete_file: "文件已存在,请先删除同名文件!",
thread_num: '并发用户数:',
input_thread_num: '请输入线程数',
duration: '压测时长(分钟):',
input_duration: '请输入时长',
rps_limit: 'RPS上限',
input_rps_limit: '请输入限制',
ramp_up_time_within: '在',
ramp_up_time_minutes: '分钟内,分',
ramp_up_time_times: '次增加并发用户',
advanced_config_error: '高级配置校验失败',
domain_bind: '域名绑定',
domain: '域名',
enable: '是否启用',
ip: 'IP地址',
params: '自定义属性',
param_name: '属性名',
param_value: '属性值',
domain_is_duplicate: '域名不能重复',
param_is_duplicate: '参数名不能重复',
domain_ip_is_empty: '域名和IP不能为空',
param_name_value_is_empty: '参数名和参数值不能为空',
connect_timeout: '建立连接超时时间',
custom_http_code: '自定义 HTTP 响应成功状态码',
separated_by_commas: '按逗号分隔',
create: '创建测试',
select_resource_pool: '请选择资源池',
resource_pool_is_null: '资源池为空',
download_log_file: '下载完整日志文件',
pressure_prediction_chart: '压力预估图',
user_name: '创建人',
special_characters_are_not_supported: '测试名称不支持特殊字符',
pressure_config_params_is_empty: '压力配置参数不能为空!'
'operating': '操作',
'recent': '最近的测试',
'search_by_name': '根据名称搜索',
'project_name': '所属项目',
'delete_confirm': '确认删除测试: ',
'input_name': '请输入名称',
'select_project': '请选择项目',
'save_and_run': '保存并执行',
'basic_config': '场景配置',
'pressure_config': '压力配置',
'advanced_config': '高级配置',
'runtime_config': '运行配置',
'is_running': '正在运行!',
'test_name_is_null': '测试名称不能为空!',
'project_is_null': '项目不能为空!',
'jmx_is_null': '必需包含一个JMX文件且只能包含一个JMX文件',
'file_name': '文件名',
'file_size': '文件大小',
'file_type': '文件类型',
'file_status': '文件状态',
'last_modify_time': '修改时间',
'upload_tips': '将文件拖到此处,或<em>点击上传</em>',
'upload_type': '只能上传JMX/CSV文件',
'related_file_not_found': "未找到关联的测试文件!",
'delete_file_confirm': '确认删除文件: ',
'file_size_limit': "文件个数超出限制!",
'delete_file': "文件已存在,请先删除同名文件!",
'thread_num': '并发用户数:',
'input_thread_num': '请输入线程数',
'duration': '压测时长(分钟):',
'input_duration': '请输入时长',
'rps_limit': 'RPS上限',
'input_rps_limit': '请输入限制',
'ramp_up_time_within': '在',
'ramp_up_time_minutes': '分钟内,分',
'ramp_up_time_times': '次增加并发用户',
'advanced_config_error': '高级配置校验失败',
'domain_bind': '域名绑定',
'domain': '域名',
'enable': '是否启用',
'ip': 'IP地址',
'params': '自定义属性',
'param_name': '属性名',
'param_value': '属性值',
'domain_is_duplicate': '域名不能重复',
'param_is_duplicate': '参数名不能重复',
'domain_ip_is_empty': '域名和IP不能为空',
'param_name_value_is_empty': '参数名和参数值不能为空',
'connect_timeout': '建立连接超时时间',
'custom_http_code': '自定义 HTTP 响应成功状态码',
'separated_by_commas': '按逗号分隔',
'create': '创建测试',
'select_resource_pool': '请选择资源池',
'resource_pool_is_null': '资源池为空',
'download_log_file': '下载完整日志文件',
'pressure_prediction_chart': '压力预估图',
'user_name': '创建人',
'special_characters_are_not_supported': '测试名称不支持特殊字符',
'pressure_config_params_is_empty': '压力配置参数不能为空!'
},
api_test: {
creator: "创建人",
@ -320,6 +294,15 @@ export default {
value: "值",
create_performance_test: "创建性能测试",
export_config: "导出配置",
environment: {
name: "环境名称",
socket: "环境域名",
globalVariable: "全局变量",
environment_list: "环境列表",
environment_config: "环境配置",
environment: "环境",
please_save_test: "请先保存测试",
},
scenario: {
config: "场景配置",
input_name: "请输入场景名称",
@ -339,7 +322,13 @@ export default {
name: "请求名称",
method: "请求方法",
url: "请求URL",
url_description: "例如: https://fit2cloud.com",
path: "请求路径",
address: "请求地址",
refer_to_environment: "引用环境",
please_configure_environment_in_scenario: "请在场景中配置环境",
please_add_environment_to_scenario: "请先在场景中添加环境配置",
url_description: "例如https://fit2cloud.com",
path_description: "例如:/login",
url_invalid: "URL无效",
parameters: "请求参数",
parameters_desc: "参数追加到URL例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
@ -373,6 +362,19 @@ export default {
json_path_expression: "JSONPath表达式",
xpath_expression: "XPath表达式",
}
},
api_import: {
label: "导入",
title: "接口测试导入",
data_format: "数据格式",
file_size_limit: "文件大小不超过 20 M",
tip: "说明",
export_tip: "导出方法",
ms_tip: "支持 Metersphere json 格式",
ms_export_tip: "通过 Metersphere Api 测试页面或者浏览器插件导出 json 格式文件",
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
post_man_export_tip: "通过 Postman 导出测试集合",
suffixFormatErr: "文件格式不符合要求",
}
},
api_report: {
@ -558,63 +560,105 @@ export default {
}
},
test_resource_pool: {
type: '类型',
enable_disable: '启用/禁用',
search_by_name: '根据名称搜索',
create_resource_pool: '创建资源池',
update_resource_pool: '修改资源池',
select_pool_type: '选择资源类型',
max_threads: '最大并发数',
input_pool_name: '请输入资源池名称',
pool_name_valid: '资源池名称不支持特殊字符',
cannot_remove_all_node: '不能删除所有独立节点',
cannot_empty: '资源池不能为空',
fill_the_data: '请完善数据',
delete_prompt: '此操作将永久删除该资源池, 是否继续?',
status_change_success: '状态修改成功!',
status_change_failed: '状态修改失败, 校验不通过!',
check_in: '校验中',
'type': '类型',
'enable_disable': '启用/禁用',
'search_by_name': '根据名称搜索',
'create_resource_pool': '创建资源池',
'update_resource_pool': '修改资源池',
'select_pool_type': '选择资源类型',
'max_threads': '最大并发数',
'input_pool_name': '请输入资源池名称',
'pool_name_valid': '资源池名称不支持特殊字符',
'cannot_remove_all_node': '不能删除所有独立节点',
'cannot_empty': '资源池不能为空',
'fill_the_data': '请完善数据',
'delete_prompt': '此操作将永久删除该资源池, 是否继续?',
'status_change_success': '状态修改成功!',
'status_change_failed': '状态修改失败, 校验不通过!',
'check_in': '校验中',
},
system_parameter_setting: {
mailbox_service_settings: '邮件设置',
ldap_setting: 'LDAP设置',
test_connection: '测试连接',
SMTP_host: 'SMTP主机',
SMTP_port: 'SMTP端口',
SMTP_account: 'SMTP账户',
SMTP_password: 'SMTP密码',
SSL: '开启SSL(如果SMTP端口是465通常需要启用SSL)',
TLS: '开启TLS(如果SMTP端口是587通常需要启用TLS)',
SMTP: '是否匿名 SMTP',
'mailbox_service_settings': '邮件设置',
'ldap_setting': 'LDAP设置',
'test_connection': '测试连接',
'SMTP_host': 'SMTP主机',
'SMTP_port': 'SMTP端口',
'SMTP_account': 'SMTP账户',
'SMTP_password': 'SMTP密码',
'SSL': '开启SSL(如果SMTP端口是465通常需要启用SSL)',
'TLS': '开启TLS(如果SMTP端口是587通常需要启用TLS)',
'SMTP': '是否匿名 SMTP',
},
i18n: {
home: '首页'
'home': '首页'
},
ldap: {
url: 'LDAP地址',
dn: '绑定DN',
password: '密码',
ou: '用户OU',
filter: '用户过滤器',
mapping: 'LDAP属性映射',
open: '启用LDAP认证',
input_url: '请输入LDAP地址',
input_dn: '请输入DN',
input_password: '请输入密码',
input_ou: '请输入用户OU',
input_filter: '请输入用户过滤器',
input_mapping: '请输入LDAP属性映射',
input_username: '请输入用户名',
input_url_placeholder: '请输入LDAP地址 (如 ldap://localhost:389)',
input_ou_placeholder: '输入用户OU (使用|分隔各OU)',
input_filter_placeholder: '输入过滤器 [可能的选项是cn或uid或sAMAccountName=%(user)s]',
test_connect: '测试连接',
test_login: '测试登录',
edit: '编辑',
login_success: '登录成功',
url_cannot_be_empty: 'LDAP 地址不能为空',
dn_cannot_be_empty: 'LDAP DN不能为空',
ou_cannot_be_empty: 'LDAP OU不能为空',
password_cannot_be_empty: 'LDAP 密码不能为空',
}
'url': 'LDAP地址',
'dn': '绑定DN',
'password': '密码',
'ou': '用户OU',
'filter': '用户过滤器',
'mapping': 'LDAP属性映射',
'open': '启用LDAP认证',
'input_url': '请输入LDAP地址',
'input_dn': '请输入DN',
'input_password': '请输入密码',
'input_ou': '请输入用户OU',
'input_filter': '请输入用户过滤器',
'input_mapping': '请输入LDAP属性映射',
'input_username': '请输入用户名',
'input_url_placeholder': '请输入LDAP地址 (如 ldap://localhost:389)',
'input_ou_placeholder': '输入用户OU',
'input_filter_placeholder': '输入过滤器 [可能的选项是cn或uid或sAMAccountName={0}, 如:(uid={0})]',
'test_connect': '测试连接',
'test_login': '测试登录',
'edit': '编辑',
'login_success': '登录成功',
'url_cannot_be_empty': 'LDAP 地址不能为空',
'dn_cannot_be_empty': 'LDAP DN不能为空',
'ou_cannot_be_empty': 'LDAP OU不能为空',
'filter_cannot_be_empty': 'LDAP 用户过滤器不能为空',
'password_cannot_be_empty': 'LDAP 密码不能为空',
},
schedule: {
not_set: "未设置",
next_execution_time: "下次执行时间",
edit_timer_task: "编辑定时任务",
please_input_cron_expression: "请输入 Cron 表达式",
generate_expression: "生成表达式",
cron_expression_format_error: "Cron 表达式格式错误",
cron_expression_interval_short_error: "间隔时间请大于 5 分钟",
cron: {
seconds: "秒",
minutes: "分钟",
hours: "小时",
day: "日",
month: "月",
weeks: "周",
years: "年",
week: "星期",
time_expression: "时间表达式",
complete_expression: "完整表达式",
allowed_wildcards: "允许的通配符[, - * /]",
day_allowed_wildcards: "允许的通配符[, - * / L M]",
weeks_allowed_wildcards: "允许的通配符[, - * / L M]",
not_specify: "不指定",
specify: "指定",
period: "周期",
from: "从",
every: "每",
day_unit: "号",
start: "开始",
execute_once: "执行一次",
last_working_day: "最近的那个工作日",
last_day_of_the_month: "本月最后一天",
multi_select: "可多选",
num: "第",
week_of_weeks: "周的星期",
last_week_of_the_month: "本月最后一个星期",
not_fill: "不填",
recent_run_time: "最近5次运行时间",
no_qualifying_results: "没有达到条件的结果",
}
},
};

View File

@ -1,281 +1,283 @@
export default {
commons: {
delete_cancelled: '已取消删除',
workspace: '工作空間',
organization: '組織',
setting: '設置',
project: '項目',
about_us: '關於',
'delete_cancelled': '已取消删除',
'workspace': '工作空間',
'organization': '組織',
'setting': '設置',
'project': '項目',
'about_us': '關於',
current_project: '當前項目',
name: '名稱',
description: '描述',
clear: '清空',
save: '保存',
save_success: '保存成功',
delete_success: '刪除成功',
copy_success: '複製成功',
modify_success: '修改成功',
delete_cancel: '已取消刪除',
confirm: '確定',
cancel: '取消',
prompt: '提示',
operating: '操作',
input_limit: '長度在 {0} 到 {1} 個字符',
login: '登錄',
welcome: '歡迎回來,請輸入用戶名和密碼登錄MeterSphere',
username: '用戶名',
password: '密碼',
input_username: '請輸入用戶名',
input_password: '請輸入密碼',
test: '測試',
create_time: '創建時間',
update_time: '更新時間',
add: '添加',
member: '成員',
email: '郵箱',
phone: '電話',
role: '角色',
personal_info: '個人信息',
status: '狀態',
show_all: '顯示全部',
show: '顯示',
report: '報告',
user: '用戶',
system: '系統',
personal_setting: '個人設置',
test_resource_pool: '測試資源池',
system_setting: '系統設置',
api: '接口測試',
performance: '性能測試',
functional: '功能測試',
input_content: '請輸入內容',
create: '新建',
edit: '編輯',
copy: '複製',
refresh: '刷新',
remark: '備註',
delete: '刪除',
not_filled: '未填寫',
please_select: '請選擇',
search_by_name: '根據名稱搜索',
personal_information: '個人信息',
exit_system: '退出系統',
verification: '驗證',
title: '標題',
custom: '自定義',
select_date: '選擇日期',
calendar_heatmap: '測試日曆',
months_1: '一月',
months_2: '二月',
months_3: '三月',
months_4: '四月',
months_5: '五月',
months_6: '六月',
months_7: '七月',
months_8: '八月',
months_9: '九月',
months_10: '十月',
months_11: '十一月',
months_12: '十二月',
weeks_0: '周日',
weeks_1: '周一',
weeks_2: '周二',
weeks_3: '周三',
weeks_4: '周四',
weeks_5: '周五',
weeks_6: '周六',
test_unit: '測試',
system_parameter_setting: '系統參數設置',
connection_successful: '連接成功',
connection_failed: '連接失敗',
save_failed: '保存失敗',
host_cannot_be_empty: '主機不能為空',
port_cannot_be_empty: '埠號不能為空',
account_cannot_be_empty: '帳戶不能為空',
remove: '移除',
remove_cancel: '移除取消',
remove_success: '移除成功',
tips: '认認證資訊已過期,請重新登入',
not_performed_yet: '尚未執行',
incorrect_input: '輸入內容不正確',
delete_confirm: '請輸入以下內容,確認刪除:',
'name': '名稱',
'description': '描述',
'clear': '清空',
'save': '保存',
'save_success': '保存成功',
'delete_success': '刪除成功',
'copy_success': '複製成功',
'modify_success': '修改成功',
'delete_cancel': '已取消刪除',
'confirm': '確定',
'cancel': '取消',
'prompt': '提示',
'operating': '操作',
'input_limit': '長度在 {0} 到 {1} 個字符',
'login': '登錄',
'welcome': '歡迎回來,請輸入用戶名和密碼登錄MeterSphere',
'username': '用戶名',
'password': '密碼',
'input_username': '請輸入用戶名',
'input_password': '請輸入密碼',
'test': '測試',
'create_time': '創建時間',
'update_time': '更新時間',
'add': '添加',
'member': '成員',
'email': '郵箱',
'phone': '電話',
'role': '角色',
'personal_info': '個人信息',
'status': '狀態',
'show_all': '顯示全部',
'show': '顯示',
'report': '報告',
'user': '用戶',
'system': '系統',
'personal_setting': '個人設置',
'test_resource_pool': '測試資源池',
'system_setting': '系統設置',
'api': '接口測試',
'performance': '性能測試',
'functional': '功能測試',
'input_content': '請輸入內容',
'create': '新建',
'edit': '編輯',
'copy': '複製',
'refresh': '刷新',
'remark': '備註',
'delete': '刪除',
'not_filled': '未填寫',
'please_select': '請選擇',
'search_by_name': '根據名稱搜索',
'personal_information': '個人信息',
'exit_system': '退出系統',
'verification': '驗證',
'title': '標題',
'custom': '自定義',
'select_date': '選擇日期',
'calendar_heatmap': '測試日曆',
'months_1': '一月',
'months_2': '二月',
'months_3': '三月',
'months_4': '四月',
'months_5': '五月',
'months_6': '六月',
'months_7': '七月',
'months_8': '八月',
'months_9': '九月',
'months_10': '十月',
'months_11': '十一月',
'months_12': '十二月',
'weeks_0': '周日',
'weeks_1': '周一',
'weeks_2': '周二',
'weeks_3': '周三',
'weeks_4': '周四',
'weeks_5': '周五',
'weeks_6': '周六',
'test_unit': '測試',
'system_parameter_setting': '系統參數設置',
'connection_successful': '連接成功',
'connection_failed': '連接失敗',
'save_failed': '保存失敗',
'host_cannot_be_empty': '主機不能為空',
'port_cannot_be_empty': '埠號不能為空',
'account_cannot_be_empty': '帳戶不能為空',
'remove': '移除',
'remove_cancel': '移除取消',
'remove_success': '移除成功',
'tips': '认認證資訊已過期,請重新登入',
'not_performed_yet': '尚未執行',
'incorrect_input': '輸入內容不正確',
'delete_confirm': '請輸入以下內容,確認刪除:',
'input_name': '請輸入名稱',
'formatErr': '格式錯誤'
},
workspace: {
create: '創建工作空間',
update: '修改工作空間',
delete: '刪除工作空間',
delete_confirm: '删除該工作空間會關聯删除該工作空間下的所有資源(如:相關項目,測試用例等),確定要删除嗎?',
add: '添加工作空間',
input_name: '請輸入工作空間名稱',
search_by_name: '根據名稱搜索',
organization_name: '所屬組織',
please_choose_organization: '請選擇組織',
please_select_a_workspace_first: '請先選擇工作空間! ',
none: '無工作空間',
select: '選擇工作空間',
special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
delete_warning: '删除该工作空间将同步删除该工作空间下所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
'create': '創建工作空間',
'update': '修改工作空間',
'delete': '刪除工作空間',
'delete_confirm': '删除該工作空間會關聯删除該工作空間下的所有資源(如:相關項目,測試用例等),確定要删除嗎?',
'add': '添加工作空間',
'input_name': '請輸入工作空間名稱',
'search_by_name': '根據名稱搜索',
'organization_name': '所屬組織',
'please_choose_organization': '請選擇組織',
'please_select_a_workspace_first': '請先選擇工作空間! ',
'none': '無工作空間',
'select': '選擇工作空間',
'special_characters_are_not_supported': '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
'delete_warning': '删除该工作空间将同步删除该工作空间下所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
},
organization: {
create: '創建組織',
modify: '修改組織',
delete: '刪除組織',
delete_confirm: '删除該組織會關聯删除該組織下的所有資源(如:相關工作空間,項目,測試用例等),確定要删除嗎?',
input_name: '請輸入組織名稱',
select_organization: '請選擇組織',
search_by_name: '根據名稱搜索',
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
none: '無組織',
select: '選擇組織',
delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
'create': '創建組織',
'modify': '修改組織',
'delete': '刪除組織',
'delete_confirm': '删除該組織會關聯删除該組織下的所有資源(如:相關工作空間,項目,測試用例等),確定要删除嗎?',
'input_name': '請輸入組織名稱',
'select_organization': '請選擇組織',
'search_by_name': '根據名稱搜索',
'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
'none': '無組織',
'select': '選擇組織',
'delete_warning': '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
},
project: {
recent: '最近的項目',
create: '創建項目',
edit: '編輯項目',
delete: '刪除項目',
delete_confirm: '確定要刪除這個項目嗎?',
delete_tip: '删除該項目,會删除該項目下所有測試資源,確定要删除嗎?',
search_by_name: '根據名稱搜索',
input_name: '請輸入項目名稱',
owning_workspace: '所屬工作空間',
please_choose_workspace: '請選擇工作空間',
special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
'recent': '最近的項目',
'create': '創建項目',
'edit': '編輯項目',
'delete': '刪除項目',
'delete_confirm': '確定要刪除這個項目嗎?',
'delete_tip': '删除該項目,會删除該項目下所有測試資源,確定要删除嗎?',
'search_by_name': '根據名稱搜索',
'input_name': '請輸入項目名稱',
'owning_workspace': '所屬工作空間',
'please_choose_workspace': '請選擇工作空間',
'special_characters_are_not_supported': '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
},
member: {
create: '添加成員',
modify: '修改成員',
delete_confirm: '這個用戶確定要刪除嗎?',
please_choose_member: '請選擇成員',
search_by_name: '根據名稱搜索',
modify_personal_info: '修改個人信息',
edit_password: '修改密碼',
edit_information: '編輯信息',
input_name: '請輸入名稱',
input_email: '請輸入郵箱',
special_characters_are_not_supported: '不支持特殊字符',
mobile_number_format_is_incorrect: '手機號碼格式不正確',
email_format_is_incorrect: '郵箱格式不正確',
password_format_is_incorrect: '有效密碼8-16比特英文大小寫字母+數位+特殊字元(可選)',
old_password: '舊密碼',
new_password: '新密碼',
remove_member: '確定要移除該成員嗎',
input_id_or_email: '請輸入用戶 ID, 或者 用戶郵箱',
no_such_user: '無此用戶信息, 請輸入正確的用戶 ID 或者 用戶郵箱!',
'create': '添加成員',
'modify': '修改成員',
'delete_confirm': '這個用戶確定要刪除嗎?',
'please_choose_member': '請選擇成員',
'search_by_name': '根據名稱搜索',
'modify_personal_info': '修改個人信息',
'edit_password': '修改密碼',
'edit_information': '編輯信息',
'input_name': '請輸入名稱',
'input_email': '請輸入郵箱',
'special_characters_are_not_supported': '不支持特殊字符',
'mobile_number_format_is_incorrect': '手機號碼格式不正確',
'email_format_is_incorrect': '郵箱格式不正確',
'password_format_is_incorrect': '有效密碼8-16比特英文大小寫字母+數位+特殊字元(可選)',
'old_password': '舊密碼',
'new_password': '新密碼',
'remove_member': '確定要移除該成員嗎',
'input_id_or_email': '請輸入用戶 ID, 或者 用戶郵箱',
'no_such_user': '無此用戶信息, 請輸入正確的用戶 ID 或者 用戶郵箱!',
},
user: {
create: '創建用戶',
modify: '修改用戶',
input_name: '請輸入用戶名',
input_id: '請輸入ID',
input_email: '請輸入郵箱',
input_password: '請輸入密碼',
input_phone: '請輸入電話號碼',
special_characters_are_not_supported: '不支持特殊字符',
mobile_number_format_is_incorrect: '手機號碼格式不正確',
email_format_is_incorrect: '郵箱格式不正確',
delete_confirm: '這個用戶確定要刪除嗎?',
apikey_delete_confirm: '這個 API Key 確定要刪除嗎?',
input_id_placeholder: '請輸入ID (只支持數字、英文字母)'
'create': '創建用戶',
'modify': '修改用戶',
'input_name': '請輸入用戶名',
'input_id': '請輸入ID',
'input_email': '請輸入郵箱',
'input_password': '請輸入密碼',
'input_phone': '請輸入電話號碼',
'special_characters_are_not_supported': '不支持特殊字符',
'mobile_number_format_is_incorrect': '手機號碼格式不正確',
'email_format_is_incorrect': '郵箱格式不正確',
'delete_confirm': '這個用戶確定要刪除嗎?',
'apikey_delete_confirm': '這個 API Key 確定要刪除嗎?',
'input_id_placeholder': '請輸入ID (只支持數字、英文字母)'
},
role: {
please_choose_role: '請選擇角色',
admin: '系統管理員',
org_admin: '組織管理員',
test_manager: '測試經理',
test_user: '測試人員',
test_viewer: 'Viewer',
add: '添加角色',
'please_choose_role': '請選擇角色',
'admin': '系統管理員',
'org_admin': '組織管理員',
'test_manager': '測試經理',
'test_user': '測試人員',
'test_viewer': 'Viewer',
'add': '添加角色',
},
report: {
name: '項目名稱',
recent: '最近的報告',
search_by_name: '根據名稱搜索',
test_name: '所屬測試',
test_overview: '測試概覽',
test_request_statistics: '請求統計',
test_error_log: '錯誤記錄',
test_log_details: '日誌詳情',
test_details: '測試詳情',
test_duration: '持續時間:{0} 分鐘 {1} 秒',
test_start_time: '開始時間',
test_end_time: '結束時間',
test_stop_now: '立即停止',
test_stop_now_confirm: '確定要立即停止當前測試嗎?',
test_rerun_confirm: '確定要再次執行當前測試嗎?',
test_stop_success: '停止成功',
test_execute_again: '再次執行',
export: '導出',
compare: '比較',
generation_error: '報告生成錯誤,無法查看!',
being_generated: '報告正在生成中...',
delete_confirm: '確認刪除報告: ',
start_status: '測試處於開始狀態,請稍後查看報告!',
run_status: '測試處於運行狀態,請稍後查看報告!',
user_name: '創建人',
project_name: '所屬項目'
'name': '項目名稱',
'recent': '最近的報告',
'search_by_name': '根據名稱搜索',
'test_name': '所屬測試',
'test_overview': '測試概覽',
'test_request_statistics': '請求統計',
'test_error_log': '錯誤記錄',
'test_log_details': '日誌詳情',
'test_details': '測試詳情',
'test_duration': '持續時間:{0} 分鐘 {1} 秒',
'test_start_time': '開始時間',
'test_end_time': '結束時間',
'test_stop_now': '立即停止',
'test_stop_now_confirm': '確定要立即停止當前測試嗎?',
'test_rerun_confirm': '確定要再次執行當前測試嗎?',
'test_stop_success': '停止成功',
'test_execute_again': '再次執行',
'export': '導出',
'compare': '比較',
'generation_error': '報告生成錯誤,無法查看!',
'being_generated': '報告正在生成中...',
'delete_confirm': '確認刪除報告: ',
'start_status': '測試處於開始狀態,請稍後查看報告!',
'run_status': '測試處於運行狀態,請稍後查看報告!',
'user_name': '創建人',
'project_name': '所屬項目'
},
load_test: {
operating: '操作',
recent: '最近的測試',
search_by_name: '根據名稱搜索',
project_name: '所屬項目',
delete_confirm: '確認刪除測試: ',
input_name: '請輸入名稱',
select_project: '請選擇項目',
save_and_run: '保存並執行',
basic_config: '場景配置',
pressure_config: '壓力配置',
advanced_config: '高級配置',
runtime_config: '運行配置',
is_running: '正在運行! ',
test_name_is_null: '測試名稱不能為空! ',
project_is_null: '項目不能為空! ',
jmx_is_null: '必需包含一個JMX文件且只能包含一個JMX文件',
file_name: '文件名',
file_size: '文件大小',
file_type: '文件類型',
file_status: '文件狀態',
last_modify_time: '修改時間',
upload_tips: '將文件拖到此處,或<em>點擊上傳</em>',
upload_type: '只能上傳JMX/CSV文件',
related_file_not_found: "未找到關聯的測試文件!",
delete_file_confirm: '確認刪除文件: ',
file_size_limit: "文件個數超出限制!",
delete_file: "文件已存在,請先刪除同名文件!",
thread_num: '並髮用戶數:',
input_thread_num: '請輸入線程數',
duration: '壓測時長(分鐘):',
input_duration: '請輸入時長',
rps_limit: 'RPS上限',
input_rps_limit: '請輸入限制',
ramp_up_time_within: '在',
ramp_up_time_minutes: '分鐘內,分',
ramp_up_time_times: '次增加並髮用戶',
advanced_config_error: '高級配置校驗失敗',
domain_bind: '域名綁定',
domain: '域名',
enable: '是否啟用',
ip: 'IP地址',
params: '自定義屬性',
param_name: '屬性名',
param_value: '屬性值',
domain_is_duplicate: '域名不能重複',
param_is_duplicate: '參數名不能重複',
domain_ip_is_empty: '域名和IP不能為空',
param_name_value_is_empty: '參數名和參數值不能為空',
connect_timeout: '建立連接超時時間',
custom_http_code: '自定義 HTTP 響應成功狀態碼',
separated_by_commas: '按逗號分隔',
create: '創建測試',
select_resource_pool: '請選擇資源池',
resource_pool_is_null: '資源池為空',
download_log_file: '下載完整日誌文件',
pressure_prediction_chart: '壓力預估圖',
user_name: '創建人',
special_characters_are_not_supported: '測試名稱不支持特殊字符',
pressure_config_params_is_empty: '壓力配置參數不能為空!'
'operating': '操作',
'recent': '最近的測試',
'search_by_name': '根據名稱搜索',
'project_name': '所屬項目',
'delete_confirm': '確認刪除測試: ',
'input_name': '請輸入名稱',
'select_project': '請選擇項目',
'save_and_run': '保存並執行',
'basic_config': '場景配置',
'pressure_config': '壓力配置',
'advanced_config': '高級配置',
'runtime_config': '運行配置',
'is_running': '正在運行! ',
'test_name_is_null': '測試名稱不能為空! ',
'project_is_null': '項目不能為空! ',
'jmx_is_null': '必需包含一個JMX文件且只能包含一個JMX文件',
'file_name': '文件名',
'file_size': '文件大小',
'file_type': '文件類型',
'file_status': '文件狀態',
'last_modify_time': '修改時間',
'upload_tips': '將文件拖到此處,或<em>點擊上傳</em>',
'upload_type': '只能上傳JMX/CSV文件',
'related_file_not_found': "未找到關聯的測試文件!",
'delete_file_confirm': '確認刪除文件: ',
'file_size_limit': "文件個數超出限制!",
'delete_file': "文件已存在,請先刪除同名文件!",
'thread_num': '並髮用戶數:',
'input_thread_num': '請輸入線程數',
'duration': '壓測時長(分鐘):',
'input_duration': '請輸入時長',
'rps_limit': 'RPS上限',
'input_rps_limit': '請輸入限制',
'ramp_up_time_within': '在',
'ramp_up_time_minutes': '分鐘內,分',
'ramp_up_time_times': '次增加並髮用戶',
'advanced_config_error': '高級配置校驗失敗',
'domain_bind': '域名綁定',
'domain': '域名',
'enable': '是否啟用',
'ip': 'IP地址',
'params': '自定義屬性',
'param_name': '屬性名',
'param_value': '屬性值',
'domain_is_duplicate': '域名不能重複',
'param_is_duplicate': '參數名不能重複',
'domain_ip_is_empty': '域名和IP不能為空',
'param_name_value_is_empty': '參數名和參數值不能為空',
'connect_timeout': '建立連接超時時間',
'custom_http_code': '自定義 HTTP 響應成功狀態碼',
'separated_by_commas': '按逗號分隔',
'create': '創建測試',
'select_resource_pool': '請選擇資源池',
'resource_pool_is_null': '資源池為空',
'download_log_file': '下載完整日誌文件',
'pressure_prediction_chart': '壓力預估圖',
'user_name': '創建人',
'special_characters_are_not_supported': '測試名稱不支持特殊字符',
'pressure_config_params_is_empty': '壓力配置參數不能為空!'
},
api_test: {
title: "測試",
@ -291,6 +293,15 @@ export default {
value: "值",
create_performance_test: "創建性能測試",
export_config: "匯出配寘",
environment: {
name: "環境名稱",
socket: "環境域名",
globalVariable: "全局變量",
environment_list: "環境列表",
environment_config: "環境配置",
environment: "環境",
please_save_test: "請先保存測試",
},
scenario: {
creator: "創建人",
config: "場景配寘",
@ -311,7 +322,13 @@ export default {
name: "請求名稱",
method: "請求方法",
url: "請求URL",
path: "請求路徑",
address: "請求地址",
refer_to_environment: "引用環境",
please_configure_environment_in_scenario: "請在場景中配置環境",
please_add_environment_to_scenario: "請先在場景中添加環境配置",
url_description: "例如https://fit2cloud.com",
path_description: "例如:/login",
url_invalid: "URL無效",
parameters: "請求參數",
parameters_desc: "參數追加到URL,例如https://fit2cloud.com/entrieskey1=Value1&amp;Key2=Value2",
@ -345,6 +362,19 @@ export default {
json_path_expression: "JSONPath運算式",
xpath_expression: "XPath運算式",
}
},
api_import: {
label: "導入",
title: "接口測試導入",
data_format: "數據格式",
file_size_limit: "文件大小不超過 20 M",
tip: "說明",
export_tip: "導出方法",
ms_tip: "支持 Metersphere json 格式",
ms_export_tip: "通過 Metersphere Api 測試頁面或者瀏覽器插件導出 json 格式文件",
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
post_man_export_tip: "通過 Postman 導出測試集合",
suffixFormatErr: "文件格式不符合要求",
}
},
api_report: {
@ -530,63 +560,105 @@ export default {
}
},
test_resource_pool: {
type: '類型',
enable_disable: '啟用/禁用',
search_by_name: '根據名稱搜索',
create_resource_pool: '創建資源池',
update_resource_pool: '修改資源池',
select_pool_type: '選擇資源類型',
max_threads: '最大並發數',
input_pool_name: '請輸入資源池名稱',
pool_name_valid: '資源池名稱不支持特殊字符',
cannot_remove_all_node: '不能刪除所有獨立節點',
cannot_empty: '資源池不能為空',
fill_the_data: '請完善數據',
delete_prompt: '此操作將永久刪除該資源池, 是否繼續?',
status_change_success: '狀態修改成功!',
status_change_failed: '狀態修改失敗, 校驗不通過!',
check_in: '校驗中',
'type': '類型',
'enable_disable': '啟用/禁用',
'search_by_name': '根據名稱搜索',
'create_resource_pool': '創建資源池',
'update_resource_pool': '修改資源池',
'select_pool_type': '選擇資源類型',
'max_threads': '最大並發數',
'input_pool_name': '請輸入資源池名稱',
'pool_name_valid': '資源池名稱不支持特殊字符',
'cannot_remove_all_node': '不能刪除所有獨立節點',
'cannot_empty': '資源池不能為空',
'fill_the_data': '請完善數據',
'delete_prompt': '此操作將永久刪除該資源池, 是否繼續?',
'status_change_success': '狀態修改成功!',
'status_change_failed': '狀態修改失敗, 校驗不通過!',
'check_in': '校驗中',
},
system_parameter_setting: {
mailbox_service_settings: '郵件設置',
ldap_setting: 'LDAP設置',
test_connection: '測試連結',
SMTP_host: 'SMTP主機',
SMTP_port: 'SMTP埠',
SMTP_account: 'SMTP帳戶',
SMTP_password: 'SMTP密碼',
SSL: '開啟SSL如果SMTP埠是465通常需要啟用SSL',
TLS: '開啟TLS如果SMTP埠是587通常需要啟用TLS',
SMTP: '是否匿名 SMTP',
'mailbox_service_settings': '郵件設置',
'ldap_setting': 'LDAP設置',
'test_connection': '測試連結',
'SMTP_host': 'SMTP主機',
'SMTP_port': 'SMTP埠',
'SMTP_account': 'SMTP帳戶',
'SMTP_password': 'SMTP密碼',
'SSL': '開啟SSL如果SMTP埠是465通常需要啟用SSL',
'TLS': '開啟TLS如果SMTP埠是587通常需要啟用TLS',
'SMTP': '是否匿名 SMTP',
},
i18n: {
home: '首頁'
'home': '首頁'
},
ldap: {
url: 'LDAP地址',
dn: '綁定DN',
password: '密碼',
ou: '用戶OU',
filter: '用戶過濾器',
mapping: 'LDAP屬性映射',
open: '啟用LDAP認證',
input_url: '請輸入LDAP地址',
input_dn: '請輸入DN',
input_password: '請輸入密碼',
input_ou: '請輸入用戶OU',
input_filter: '請輸入用戶過濾器',
input_mapping: '請輸入LDAP屬性映射',
input_username: '請輸入用戶名',
input_url_placeholder: '請輸入LDAP地址 (如 ldap://localhost:389)',
input_ou_placeholder: '輸入用戶OU (使用|分隔各OU)',
input_filter_placeholder: '輸入過濾器 [可能的選項是cn或uid或sAMAccountName=%(user)s]',
test_connect: '測試連接',
test_login: '測試登錄',
edit: '編輯',
login_success: '登錄成功',
url_cannot_be_empty: 'LDAP 地址不能為空',
dn_cannot_be_empty: 'LDAP DN不能為空',
ou_cannot_be_empty: 'LDAP OU不能為空',
password_cannot_be_empty: 'LDAP 密碼不能為空',
}
'url': 'LDAP地址',
'dn': '綁定DN',
'password': '密碼',
'ou': '用戶OU',
'filter': '用戶過濾器',
'mapping': 'LDAP屬性映射',
'open': '啟用LDAP認證',
'input_url': '請輸入LDAP地址',
'input_dn': '請輸入DN',
'input_password': '請輸入密碼',
'input_ou': '請輸入用戶OU',
'input_filter': '請輸入用戶過濾器',
'input_mapping': '請輸入LDAP屬性映射',
'input_username': '請輸入用戶名',
'input_url_placeholder': '請輸入LDAP地址 (如 ldap://localhost:389)',
'input_ou_placeholder': '輸入用戶OU',
'input_filter_placeholder': '輸入過濾器 [可能的選項是cn或uid或sAMAccountName={0}, 如:(uid={0})]',
'test_connect': '測試連接',
'test_login': '測試登錄',
'edit': '編輯',
'login_success': '登錄成功',
'url_cannot_be_empty': 'LDAP 地址不能為空',
'dn_cannot_be_empty': 'LDAP DN不能為空',
'ou_cannot_be_empty': 'LDAP OU不能為空',
'filter_cannot_be_empty': 'LDAP 用戶過濾器不能為空',
'password_cannot_be_empty': 'LDAP 密碼不能為空',
},
schedule: {
not_set: "未設置",
next_execution_time: "下次執行時間",
edit_timer_task: "編輯定時任務",
please_input_cron_expression: "請輸入 Cron 表達式",
generate_expression: "生成表達式",
cron_expression_format_error: "Cron 表達式格式錯誤",
cron_expression_interval_short_error: "間隔時間請大於 5 分鐘",
cron: {
seconds: "秒",
minutes: "分鐘",
hours: "小時",
day: "日",
month: "月",
weeks: "周",
years: "年",
week: "星期",
time_expression: "時間表達式",
complete_expression: "完整表達式",
allowed_wildcards: "允許的通配符[, - * /]",
day_allowed_wildcards: "允許的通配符[, - * / L M]",
weeks_allowed_wildcards: "允許的通配符[, - * / L M]",
not_specify: "不指定",
specify: "指定",
period: "周期",
from: "從",
every: "每",
day_unit: "號",
start: "開始",
execute_once: "執行壹次",
last_working_day: "最近的那個工作日",
last_day_of_the_month: "本月最後壹天",
multi_select: "可多選",
num: "第",
week_of_weeks: "周的星期",
last_week_of_the_month: "本月最後壹個星期",
not_fill: "不填",
recent_run_time: "最近5次運行時間",
no_qualifying_results: "沒有達到條件的結果",
}
},
};

View File

@ -1,26 +1,27 @@
module.exports = {
productionSourceMap: true,
configureWebpack: {
devtool: 'source-map'
},
devServer: {
proxy: {
['^(?!/login)']: {
target: 'http://localhost:8081',
ws: true,
}
}
},
pages: {
business: {
entry: "src/business/main.js",
template: "src/business/index.html",
filename: "index.html"
},
login: {
entry: "src/login/login.js",
template: "src/login/login.html",
filename: "login.html"
}
productionSourceMap: true,
configureWebpack: {
devtool: 'source-map'
},
devServer: {
port: 8080,
proxy: {
['^(?!/login)']: {
target: 'http://localhost:8081',
ws: true,
}
}
},
pages: {
business: {
entry: "src/business/main.js",
template: "src/business/index.html",
filename: "index.html"
},
login: {
entry: "src/login/login.js",
template: "src/login/login.html",
filename: "login.html"
}
}
};