Merge remote-tracking branch 'origin/master'

This commit is contained in:
wenyann 2020-07-08 14:57:14 +08:00
commit ec3f54de0f
56 changed files with 991 additions and 282 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

@ -96,4 +96,10 @@ public class APITestController {
return apiTestService.run(request);
}
@PostMapping("/import/{platform}/{projectId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public ApiTest testCaseImport(MultipartFile file, @PathVariable String platform, @PathVariable String projectId) {
return apiTestService.apiTestImport(file, platform, projectId);
}
}

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

@ -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,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,16 @@
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();
}
return null;
}
}

View File

@ -0,0 +1,36 @@
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 implements ApiImportParser {
@Override
public ApiImport parse(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 JSON.parseObject(testStr.toString(), ApiImport.class);
}
}

View File

@ -2,14 +2,19 @@ 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.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;
@ -26,8 +31,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;
@ -163,6 +167,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 +259,38 @@ 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, String projectId) {
try {
ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(platform);
ApiImport apiImport = apiImportParser.parse(file.getInputStream());
SaveAPITestRequest request = getImportApiTest(file, projectId, apiImport);
ApiTest test = createTest(request);
return test;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private SaveAPITestRequest getImportApiTest(MultipartFile file, String projectId, ApiImport apiImport) {
SaveAPITestRequest request = new SaveAPITestRequest();
request.setName(file.getOriginalFilename());
request.setProjectId(projectId);
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

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

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

View File

@ -32,6 +32,10 @@ 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) {
DirContext ctx = null;
try {
ctx = ldapTemplate.getContextSource().getContext(dn, credentials);
@ -58,9 +62,8 @@ public class PersonRepoImpl implements PersonRepo {
}
@Override
public List findByName(String name) {
public List<Person> findByName(String name) {
LdapTemplate ldapTemplate = getConnection();
ldapTemplate.setIgnorePartialResultException(true);
LdapQuery query = query().where("cn").is(name);
return ldapTemplate.search(query, getContextMapper());
}
@ -68,7 +71,6 @@ public class PersonRepoImpl implements PersonRepo {
@Override
public String getDnForUser(String uid) {
LdapTemplate ldapTemplate = getConnection();
ldapTemplate.setIgnorePartialResultException(true);
List<String> result = ldapTemplate.search(
query().where("cn").is(uid),
new AbstractContextMapper() {
@ -112,7 +114,6 @@ public class PersonRepoImpl implements PersonRepo {
String credentials = EncryptUtils.aesDecrypt(password).toString();
LdapContextSource sourceLdapCtx = new LdapContextSource();
sourceLdapCtx.setUrl(url);
sourceLdapCtx.setUserDn(dn);
@ -120,8 +121,13 @@ public class PersonRepoImpl implements PersonRepo {
sourceLdapCtx.setBase(ou);
sourceLdapCtx.setDirObjectFactory(DefaultDirObjectFactory.class);
sourceLdapCtx.afterPropertiesSet();
LdapTemplate ldapTemplate = new LdapTemplate(sourceLdapCtx);
ldapTemplate.setIgnorePartialResultException(true);
return new LdapTemplate(sourceLdapCtx);
// ldapTemplate 是否可用
authenticate(dn, credentials, ldapTemplate);
return ldapTemplate;
}
private void preConnect(String url, String dn, String ou, String password) {

View File

@ -5,6 +5,7 @@ import io.metersphere.controller.request.LoginRequest;
import io.metersphere.i18n.Translator;
import io.metersphere.ldap.dao.PersonRepoImpl;
import io.metersphere.ldap.domain.LdapInfo;
import io.metersphere.ldap.domain.Person;
import org.springframework.ldap.CommunicationException;
import org.springframework.stereotype.Service;
@ -18,18 +19,19 @@ public class LdapService {
private PersonRepoImpl personRepo;
public void authenticate(LoginRequest request) {
public Person authenticate(LoginRequest request) {
String dn = null;
String username = request.getUsername();
String credentials = request.getPassword();
List<Person> personList = null;
try {
// select user by sAMAccountName
List user = personRepo.findByName(username);
personList = personRepo.findByName(username);
if (user.size() == 1) {
if (personList.size() == 1) {
dn = personRepo.getDnForUser(username);
} else if (user.size() == 0) {
} else if (personList.size() == 0) {
MSException.throwException(Translator.get("user_not_exist") + username);
} else {
MSException.throwException(Translator.get("find_more_user"));
@ -38,6 +40,8 @@ public class LdapService {
MSException.throwException(Translator.get("ldap_connect_fail"));
}
personRepo.authenticate(dn, credentials);
return personList.get(0);
}
public void testConnect(LdapInfo ldap) {

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

@ -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
@ -120,7 +121,8 @@ 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
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
login_fail_email_null=Login failed, user mailbox is empty

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=模块树最大深度为
@ -120,10 +121,10 @@ ldap_dn_is_null=LDAP绑定DN为空
ldap_ou_is_null=LDAP参数OU为空
ldap_password_is_null=LDAP密码为空
ldap_connect_fail=连接失败
authentication_failed=用户认证失败
authentication_failed=用户认证失败,用户名或密码错误
user_not_found_or_not_unique=用户不存在或者不唯一
find_more_user=查找到多个用户
ldap_authentication_not_enabled=LDAP认证未启用
login_fail_email_null=登录失败,用户邮箱为空

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=模塊樹最大深度為
@ -120,7 +121,8 @@ ldap_dn_is_null=LDAP綁定DN為空
ldap_ou_is_null=LDAP參數OU為空
ldap_password_is_null=LDAP密碼為空
ldap_connect_fail=連接失敗
authentication_failed=用戶認證失敗
authentication_failed=用戶認證失敗,用戶名或密碼錯誤
user_not_found_or_not_unique=用戶不存在或者不唯一
find_more_user=查找到多個用戶
ldap_authentication_not_enabled=LDAP認證未啟用
login_fail_email_null=登錄失敗,用戶郵箱為空

View File

@ -39,18 +39,24 @@
<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.export_config')}}-->
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<api-import :project-id="test.projectId" 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 +70,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 +218,9 @@
case "export":
downloadFile(this.test.name + ".json", this.test.export());
break;
case "import":
this.$refs.apiImport.open();
break;
}
},
saveCronExpression(cronExpression) {
@ -228,13 +238,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);
useEnvironmentChange(value) {
if (value && !this.request.environment) {
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
this.request.useEnvironment = false;
}
})
this.request.url = this.getURL(url);
},
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,11 +1,18 @@
<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.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="100"/>-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="200"/>-->
<!-- </el-form-item>-->
<el-tabs v-model="activeName">
@ -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,5 +1,6 @@
<template>
<div>
<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')"
@ -14,8 +15,10 @@
<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,6 +3,7 @@
<div class="extract-description">
{{$t('api_test.request.extract.description')}}
</div>
<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')"
@ -15,7 +16,10 @@
<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,132 @@
<template>
<el-dialog :title="'接口测试导入'" :visible.sync="visible" class="api-import" v-loading="result.loading">
<div class="data-format">
<div>数据格式</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">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">文件大小不超过 20 M</div>
</el-upload>
</div>
<div class="format-tip">
<div>
<span>说明{{selectedPlatform.tip}}</span>
</div>
<div>
<span>导出方法{{selectedPlatform.exportTip}}</span>
</div>
</div>
</el-dialog>
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
export default {
name: "ApiImport",
components: {MsDialogFooter},
props: ['projectId'],
data() {
return {
visible: false,
platforms: [
{
name: 'Metersphere',
value: 'Metersphere',
tip: '支持 Metersphere json 格式',
exportTip: '通过 Metersphere Api 测试页面或者浏览器插件导出 json 格式文件',
suffixes: new Set(['json'])
}
],
selectedPlatform: {},
selectedPlatformValue: 'Metersphere',
fileList: [],
result: {},
}
},
created() {
this.selectedPlatform = this.platforms[0];
},
watch: {
selectedPlatformId() {
for (let i in this.platforms) {
if (this.platforms[i].id === 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.projectId, 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("格式错误");
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 || {};
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,25 +165,31 @@ 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;
}
}
if (!this.name) {
return false;
}
return true;
}
}
export class Request extends BaseConfig {
constructor(options) {
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,6 +399,9 @@ 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") {
@ -463,6 +473,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 +497,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">
小时允许的通配符[, - * /]
{{$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">
分钟允许的通配符[, - * /]
{{$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,19 +2,19 @@
<el-form size="small">
<el-form-item>
<el-radio :label="1" v-model='radioValue'>
不填允许的通配符[, - * /]
{{$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'>
周期从
{{$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>
@ -22,9 +22,9 @@
<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

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

@ -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,6 +75,7 @@
showEdit: true,
showSave: false,
showCancel: false,
showLogin: false,
loginVisible: false,
rules: {
url: {required: true, message: this.$t('ldap.input_url'), trigger: ['change', 'blur']},
@ -120,6 +121,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() {
@ -172,6 +176,7 @@
this.showEdit = true;
this.showSave = false;
this.showCancel = false;
this.showLogin = false;
this.$success(this.$t('commons.save_success'));
this.init();
});

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

@ -96,7 +96,7 @@
param.testCaseIds = [...this.selectIds];
this.$post('/test/plan/relevance' , param, () => {
this.selectIds.clear();
this.$success("保存成功");
this.$success(this.$t('commons.save_success'));
this.dialogFormVisible = false;
this.$emit('refresh');
});

View File

@ -101,6 +101,8 @@ export default {
'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',
@ -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",
@ -591,5 +608,46 @@ export default {
'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',
},
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

@ -101,6 +101,8 @@ export default {
'delete_confirm': '请输入以下内容,确认删除:',
'login_username': 'ID 或 邮箱',
'input_login_username': '请输入用户 ID 或 邮箱',
'input_name': '请输入名称',
'formatErr': '格式错误'
},
workspace: {
'create': '创建工作空间',
@ -292,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: "请输入场景名称",
@ -311,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",
@ -588,5 +605,46 @@ export default {
'dn_cannot_be_empty': 'LDAP DN不能为空',
'ou_cannot_be_empty': 'LDAP OU不能为空',
'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

@ -99,6 +99,8 @@ export default {
'not_performed_yet': '尚未執行',
'incorrect_input': '輸入內容不正確',
'delete_confirm': '請輸入以下內容,確認刪除:',
'input_name': '請輸入名稱',
'formatErr': '格式錯誤'
},
workspace: {
'create': '創建工作空間',
@ -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",
@ -588,5 +605,46 @@ export default {
'dn_cannot_be_empty': 'LDAP DN不能為空',
'ou_cannot_be_empty': 'LDAP OU不能為空',
'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: "沒有達到條件的結果",
}
},
};