Merge branch 'v1.1'

This commit is contained in:
chenjianxing 2020-07-28 16:23:08 +08:00
commit 15798a7726
33 changed files with 613 additions and 292 deletions

View File

@ -44,6 +44,11 @@
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
@ -126,10 +131,6 @@
<version>1.2.72</version> <version>1.2.72</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<!-- openapi --> <!-- openapi -->
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId>

View File

@ -4,13 +4,17 @@ import io.metersphere.config.JmeterProperties;
import io.metersphere.config.KafkaProperties; import io.metersphere.config.KafkaProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class}) @SpringBootApplication(exclude = {
QuartzAutoConfiguration.class,
LdapAutoConfiguration.class
})
@ServletComponentScan @ServletComponentScan
@EnableConfigurationProperties({ @EnableConfigurationProperties({
KafkaProperties.class, KafkaProperties.class,

View File

@ -48,6 +48,7 @@ public class ShiroConfig implements EnvironmentAware {
filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/signin", "anon"); filterChainDefinitionMap.put("/signin", "anon");
filterChainDefinitionMap.put("/ldap/signin", "anon"); filterChainDefinitionMap.put("/ldap/signin", "anon");
filterChainDefinitionMap.put("/ldap/open", "anon");
filterChainDefinitionMap.put("/isLogin", "anon"); filterChainDefinitionMap.put("/isLogin", "anon");
filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon");

View File

@ -7,6 +7,7 @@ import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.util.StringUtils; import com.alibaba.excel.util.StringUtils;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.ExcelErrData; import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.excel.utils.EasyExcelI18nTranslator; import io.metersphere.excel.utils.EasyExcelI18nTranslator;
import io.metersphere.excel.utils.ExcelValidateHelper; import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
@ -24,6 +25,8 @@ public abstract class EasyExcelListener<T> extends AnalysisEventListener<T> {
protected EasyExcelI18nTranslator easyExcelI18nTranslator; protected EasyExcelI18nTranslator easyExcelI18nTranslator;
protected List<TestCaseExcelData> excelDataList = new ArrayList<>();
/** /**
* 每隔2000条存储数据库然后清理list 方便内存回收 * 每隔2000条存储数据库然后清理list 方便内存回收
*/ */

View File

@ -10,10 +10,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestCaseService; import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.Collections; import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -57,10 +54,35 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
if (!userIds.contains(data.getMaintainer())) { if (!userIds.contains(data.getMaintainer())) {
stringBuilder.append(Translator.get("user_not_exists") + "" + data.getMaintainer() + "; "); stringBuilder.append(Translator.get("user_not_exists") + "" + data.getMaintainer() + "; ");
} }
if (testCaseNames.contains(data.getName())) { if (testCaseNames.contains(data.getName())) {
stringBuilder.append(Translator.get("test_case_already_exists_excel") + "" + data.getName() + "; "); TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
BeanUtils.copyBean(testCase, data);
testCase.setProjectId(projectId);
String steps = getSteps(data);
testCase.setSteps(steps);
boolean dbExist = testCaseService.exist(testCase);
boolean excelExist = false;
if (dbExist) {
// db exist
stringBuilder.append(Translator.get("test_case_already_exists_excel") + "" + data.getName() + "; ");
} else {
// @Data 重写了 equals hashCode 方法
excelExist = excelDataList.contains(data);
}
if (excelExist) {
// excel exist
stringBuilder.append(Translator.get("test_case_already_exists_excel") + "" + data.getName() + "; ");
} else {
excelDataList.add(data);
}
} else { } else {
testCaseNames.add(data.getName()); testCaseNames.add(data.getName());
excelDataList.add(data);
} }
return stringBuilder.toString(); return stringBuilder.toString();
} }
@ -103,6 +125,13 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
testCase.setNodePath(nodePath); testCase.setNodePath(nodePath);
String steps = getSteps(data);
testCase.setSteps(steps);
return testCase;
}
public String getSteps(TestCaseExcelData data) {
JSONArray jsonArray = new JSONArray(); JSONArray jsonArray = new JSONArray();
String[] stepDesc = new String[1]; String[] stepDesc = new String[1];
@ -124,7 +153,8 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++) {
JSONObject step = new JSONObject(); // 保持插入顺序判断用例是否有相同的steps
JSONObject step = new JSONObject(true);
step.put("num", i + 1); step.put("num", i + 1);
Pattern descPattern = Pattern.compile(pattern); Pattern descPattern = Pattern.compile(pattern);
@ -150,10 +180,7 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
jsonArray.add(step); jsonArray.add(step);
} }
return jsonArray.toJSONString();
testCase.setSteps(jsonArray.toJSONString());
return testCase;
} }
} }

View File

@ -13,10 +13,7 @@ import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -82,4 +79,9 @@ public class LdapController {
ldapService.authenticate(request); ldapService.authenticate(request);
} }
@GetMapping("/open")
public boolean isOpen() {
return ldapService.isOpen();
}
} }

View File

@ -92,9 +92,7 @@ public class LdapService {
if (result.size() == 1) { if (result.size() == 1) {
return result.get(0); return result.get(0);
} }
} catch (NameNotFoundException e) { } catch (NameNotFoundException | InvalidNameException e) {
MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidNameException e) {
MSException.throwException(Translator.get("login_fail_ou_error")); MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidSearchFilterException e) { } catch (InvalidSearchFilterException e) {
MSException.throwException(Translator.get("login_fail_filter_error")); MSException.throwException(Translator.get("login_fail_filter_error"));
@ -125,9 +123,7 @@ public class LdapService {
MSException.throwException(Translator.get("ldap_ou_is_null")); MSException.throwException(Translator.get("ldap_ou_is_null"));
} }
String[] arr = ou.split("\\|"); return ou.split("\\|");
return arr;
} }
private static class MsContextMapper extends AbstractContextMapper<DirContextOperations> { private static class MsContextMapper extends AbstractContextMapper<DirContextOperations> {
@ -217,4 +213,11 @@ public class LdapService {
return result; return result;
} }
public boolean isOpen() {
String open = service.getValue(ParamConstants.LDAP.OPEN.getValue());
if (StringUtils.isBlank(open)) {
return false;
}
return StringUtils.equals(Boolean.TRUE.toString(), open);
}
} }

View File

@ -102,9 +102,9 @@ public class PerformanceTestController {
return performanceTestService.run(request); return performanceTestService.run(request);
} }
@GetMapping("stop/{reportId}") @GetMapping("stop/{reportId}/{forceStop}")
public void stopTest(@PathVariable String reportId) { public void stopTest(@PathVariable String reportId, @PathVariable boolean forceStop) {
performanceTestService.stopTest(reportId); performanceTestService.stopTest(reportId, forceStop);
} }
@GetMapping("/file/metadata/{testId}") @GetMapping("/file/metadata/{testId}")

View File

@ -401,7 +401,19 @@ public class PerformanceTestService {
scheduleService.addOrUpdateCronJob(request, PerformanceTestJob.getJobKey(request.getResourceId()), PerformanceTestJob.getTriggerKey(request.getResourceId()), PerformanceTestJob.class); scheduleService.addOrUpdateCronJob(request, PerformanceTestJob.getJobKey(request.getResourceId()), PerformanceTestJob.getTriggerKey(request.getResourceId()), PerformanceTestJob.class);
} }
public void stopTest(String reportId) { public void stopTest(String reportId, boolean forceStop) {
reportService.deleteReport(reportId); if (forceStop) {
reportService.deleteReport(reportId);
} else {
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(loadTestReport.getTestId());
final Engine engine = EngineFactory.createEngine(loadTest);
if (engine == null) {
MSException.throwException(String.format("Stop report fail. create engine failreport ID%s", reportId));
}
reportService.stopEngine(loadTest, engine);
// 停止测试之后设置报告的状态
reportService.updateStatus(reportId, PerformanceTestStatus.Completed.name());
}
} }
} }

View File

@ -99,7 +99,7 @@ public class ReportService {
loadTestReportMapper.deleteByPrimaryKey(reportId); loadTestReportMapper.deleteByPrimaryKey(reportId);
} }
private void stopEngine(LoadTestWithBLOBs loadTest, Engine engine) { public void stopEngine(LoadTestWithBLOBs loadTest, Engine engine) {
engine.stop(); engine.stop();
loadTest.setStatus(PerformanceTestStatus.Saved.name()); loadTest.setStatus(PerformanceTestStatus.Saved.name());
loadTestMapper.updateByPrimaryKeySelective(loadTest); loadTestMapper.updateByPrimaryKeySelective(loadTest);
@ -239,4 +239,11 @@ public class ReportService {
public LoadTestReport getReport(String reportId) { public LoadTestReport getReport(String reportId) {
return loadTestReportMapper.selectByPrimaryKey(reportId); return loadTestReportMapper.selectByPrimaryKey(reportId);
} }
public void updateStatus(String reportId, String status) {
LoadTestReport report = new LoadTestReport();
report.setId(reportId);
report.setStatus(status);
loadTestReportMapper.updateByPrimaryKeySelective(report);
}
} }

View File

@ -106,13 +106,16 @@ public class TestCaseService {
TestCaseExample.Criteria criteria = example.createCriteria(); TestCaseExample.Criteria criteria = example.createCriteria();
criteria.andNameEqualTo(testCase.getName()) criteria.andNameEqualTo(testCase.getName())
.andProjectIdEqualTo(testCase.getProjectId()) .andProjectIdEqualTo(testCase.getProjectId())
.andNodeIdEqualTo(testCase.getNodeId())
.andNodePathEqualTo(testCase.getNodePath()) .andNodePathEqualTo(testCase.getNodePath())
.andTypeEqualTo(testCase.getType()) .andTypeEqualTo(testCase.getType())
.andMaintainerEqualTo(testCase.getMaintainer()) .andMaintainerEqualTo(testCase.getMaintainer())
.andPriorityEqualTo(testCase.getPriority()) .andPriorityEqualTo(testCase.getPriority())
.andMethodEqualTo(testCase.getMethod()); .andMethodEqualTo(testCase.getMethod());
// if (StringUtils.isNotBlank(testCase.getNodeId())) {
// criteria.andNodeIdEqualTo(testCase.getTestId());
// }
if (StringUtils.isNotBlank(testCase.getTestId())) { if (StringUtils.isNotBlank(testCase.getTestId())) {
criteria.andTestIdEqualTo(testCase.getTestId()); criteria.andTestIdEqualTo(testCase.getTestId());
} }
@ -371,8 +374,8 @@ public class TestCaseService {
JSONArray jsonArray = JSON.parseArray(steps); JSONArray jsonArray = JSON.parseArray(steps);
for (int j = 0; j < jsonArray.size(); j++) { for (int j = 0; j < jsonArray.size(); j++) {
int num = j + 1; int num = j + 1;
step.append(num + ":" + jsonArray.getJSONObject(j).getString("desc") + "\n"); step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\n");
result.append(num + ":" + jsonArray.getJSONObject(j).getString("result") + "\n"); result.append(num + "." + jsonArray.getJSONObject(j).getString("result") + "\n");
} }
data.setStepDesc(step.toString()); data.setStepDesc(step.toString());
@ -471,4 +474,21 @@ public class TestCaseService {
return Optional.ofNullable(testCase.getNum() + 1).orElse(100001); return Optional.ofNullable(testCase.getNum() + 1).orElse(100001);
} }
} }
/**
* 导入用例前检查数据库是否存在此用例
* @param testCaseWithBLOBs
* @return
*/
public boolean exist(TestCaseWithBLOBs testCaseWithBLOBs) {
try {
checkTestCaseExist(testCaseWithBLOBs);
} catch (MSException e) {
return true;
}
return false;
}
} }

View File

@ -84,6 +84,9 @@ public class ReportWebSocket {
session.close(); session.close();
break; break;
} }
if (!session.isOpen()) {
return;
}
if (PerformanceTestStatus.Running.name().equals(report.getStatus())) { if (PerformanceTestStatus.Running.name().equals(report.getStatus())) {
session.getBasicRemote().sendText("refresh-" + this.refresh++); session.getBasicRemote().sendText("refresh-" + this.refresh++);
} }

View File

@ -74,3 +74,7 @@ quartz.scheduler-name=msServerJob
spring.servlet.multipart.max-file-size=30MB spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=30MB spring.servlet.multipart.max-request-size=30MB
# actuator
management.server.port=8083
management.endpoints.web.exposure.include=*

View File

@ -146,14 +146,17 @@
</root> </root>
<logger name="io.metersphere" additivity="false"> <logger name="io.metersphere" additivity="false">
<level value="${logger.level:INFO}" /> <level value="${logger.level:INFO}"/>
<appender-ref ref="infoAsyncAppender" /> <appender-ref ref="infoAsyncAppender"/>
<appender-ref ref="warnAsyncAppender" /> <appender-ref ref="warnAsyncAppender"/>
<appender-ref ref="errorAsyncAppender" /> <appender-ref ref="errorAsyncAppender"/>
</logger> </logger>
<logger name="io.metersphere.Application" additivity="false" level="${logger.level:INFO}"> <logger name="io.metersphere.Application" additivity="false" level="${logger.level:INFO}">
<appender-ref ref="infoAsyncAppender" /> <appender-ref ref="infoAsyncAppender"/>
</logger> </logger>
<logger name="com.alibaba.nacos.naming.client.listener" additivity="false" level="ERROR"/>
<logger name="org.apache.dubbo" additivity="false" level="ERROR"/>
</configuration> </configuration>

View File

@ -34,7 +34,8 @@
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0", "eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10",
"vue2-ace-editor": "0.0.15"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="container" v-loading="loading"> <div class="container" v-loading="loading" :element-loading-text="$t('api_report.running')">
<div class="main-content"> <div class="main-content">
<el-card> <el-card>
<section class="report-container" v-if="this.report.testId"> <section class="report-container" v-if="this.report.testId">
<header class="report-header"> <header class="report-header">
<span>{{report.projectName}} / </span> <span>{{ report.projectName }} / </span>
<router-link :to="path">{{report.testName}}</router-link> <router-link :to="path">{{ report.testName }}</router-link>
<span class="time">{{report.createTime | timestampFormatDate}}</span> <span class="time">{{ report.createTime | timestampFormatDate }}</span>
</header> </header>
<main v-if="this.isNotRunning"> <main v-if="this.isNotRunning">
<ms-metric-chart :content="content"/> <ms-metric-chart :content="content"/>
@ -16,7 +16,7 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="fail"> <el-tab-pane name="fail">
<template slot="label"> <template slot="label">
<span class="fail">{{$t('api_report.fail')}}</span> <span class="fail">{{ $t('api_report.fail') }}</span>
</template> </template>
<ms-scenario-results :scenarios="fails"/> <ms-scenario-results :scenarios="fails"/>
</el-tab-pane> </el-tab-pane>
@ -30,126 +30,126 @@
<script> <script>
import MsRequestResult from "./components/RequestResult"; import MsRequestResult from "./components/RequestResult";
import MsScenarioResult from "./components/ScenarioResult"; import MsScenarioResult from "./components/ScenarioResult";
import MsMetricChart from "./components/MetricChart"; import MsMetricChart from "./components/MetricChart";
import MsScenarioResults from "./components/ScenarioResults"; import MsScenarioResults from "./components/ScenarioResults";
export default { export default {
name: "MsApiReportView", name: "MsApiReportView",
components: {MsScenarioResults, MsMetricChart, MsScenarioResult, MsRequestResult}, components: {MsScenarioResults, MsMetricChart, MsScenarioResult, MsRequestResult},
data() { data() {
return { return {
activeName: "total", activeName: "total",
content: {}, content: {},
report: {}, report: {},
loading: true, loading: true,
fails: [] fails: []
}
},
methods: {
init() {
this.loading = true;
this.report = {};
this.content = {};
this.fails = [];
},
getReport() {
this.init();
if (this.reportId) {
let url = "/api/report/get/" + this.reportId;
this.$get(url, response => {
this.report = response.data || {};
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
} catch (e) {
console.log(this.report.content)
throw e;
}
this.getFails();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
}
});
} }
}, },
getFails() {
methods: { if (this.isNotRunning) {
init() {
this.loading = true;
this.report = {};
this.content = {};
this.fails = []; this.fails = [];
}, this.content.scenarios.forEach((scenario) => {
getReport() { let failScenario = Object.assign({}, scenario);
this.init(); if (scenario.error > 0) {
this.fails.push(failScenario);
if (this.reportId) { failScenario.requestResults = [];
let url = "/api/report/get/" + this.reportId; scenario.requestResults.forEach((request) => {
this.$get(url, response => { if (!request.success) {
this.report = response.data || {}; let failRequest = Object.assign({}, request);
if (this.isNotRunning) { failScenario.requestResults.push(failRequest);
try {
this.content = JSON.parse(this.report.content);
} catch (e) {
console.log(this.report.content)
throw e;
} }
this.getFails(); })
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
}
});
}
},
getFails() {
if (this.isNotRunning) {
this.fails = [];
this.content.scenarios.forEach((scenario) => {
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);
failScenario.requestResults = [];
scenario.requestResults.forEach((request) => {
if (!request.success) {
let failRequest = Object.assign({}, request);
failScenario.requestResults.push(failRequest);
}
})
} }
}) })
}
}
},
watch: {
'$route': 'getReport',
},
created() {
this.getReport();
},
computed: {
reportId: function () {
return this.$route.params.reportId;
},
path() {
return "/api/test/edit?id=" + this.report.testId;
},
isNotRunning() {
return "Running" !== this.report.status;
} }
} }
},
watch: {
'$route': 'getReport',
},
created() {
this.getReport();
},
computed: {
reportId: function () {
return this.$route.params.reportId;
},
path() {
return "/api/test/edit?id=" + this.report.testId;
},
isNotRunning() {
return "Running" !== this.report.status;
}
} }
}
</script> </script>
<style> <style>
.report-container .el-tabs__header { .report-container .el-tabs__header {
margin-bottom: 1px; margin-bottom: 1px;
} }
</style> </style>
<style scoped> <style scoped>
.report-container { .report-container {
height: calc(100vh - 150px); height: calc(100vh - 150px);
min-height: 600px; min-height: 600px;
overflow-y: auto; overflow-y: auto;
} }
.report-header { .report-header {
font-size: 15px; font-size: 15px;
} }
.report-header a { .report-header a {
text-decoration: none; text-decoration: none;
} }
.report-header .time { .report-header .time {
color: #909399; color: #909399;
margin-left: 10px; margin-left: 10px;
} }
.report-container .fail { .report-container .fail {
color: #F56C6C; color: #F56C6C;
} }
.report-container .is-active .fail { .report-container .is-active .fail {
color: inherit; color: inherit;
} }
</style> </style>

View File

@ -7,7 +7,7 @@
<el-collapse-transition> <el-collapse-transition>
<el-tabs v-model="activeName" v-show="isActive"> <el-tabs v-model="activeName" v-show="isActive">
<el-tab-pane label="Body" name="body" class="pane"> <el-tab-pane label="Body" name="body" class="pane">
<div>{{response.body}}</div> <ms-code-edit :read-only="true" :data="response.body" :modes="modes" ref="codeEdit"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Headers" name="headers" class="pane"> <el-tab-pane label="Headers" name="headers" class="pane">
<pre>{{response.headers}}</pre> <pre>{{response.headers}}</pre>
@ -15,6 +15,13 @@
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions"> <el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
<ms-assertion-results :assertions="response.assertions"/> <ms-assertion-results :assertions="response.assertions"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane assertions">
<template v-slot:label>
<ms-dropdown :commands="modes" @command="modeChange"/>
</template>
</el-tab-pane>
</el-tabs> </el-tabs>
</el-collapse-transition> </el-collapse-transition>
</div> </div>
@ -22,11 +29,17 @@
<script> <script>
import MsAssertionResults from "./AssertionResults"; import MsAssertionResults from "./AssertionResults";
import MsCodeEdit from "../../../common/components/MsCodeEdit";
import MsDropdown from "../../../common/components/MsDropdown";
export default { export default {
name: "MsResponseText", name: "MsResponseText",
components: {MsAssertionResults}, components: {
MsDropdown,
MsCodeEdit,
MsAssertionResults,
},
props: { props: {
response: Object response: Object
@ -36,12 +49,16 @@
return { return {
isActive: false, isActive: false,
activeName: "body", activeName: "body",
modes: ['text', 'json', 'xml', 'html'],
} }
}, },
methods: { methods: {
active() { active() {
this.isActive = !this.isActive; this.isActive = !this.isActive;
},
modeChange(mode) {
this.$refs.codeEdit.setMode(mode);
} }
}, },
} }

View File

@ -1,10 +1,10 @@
<template> <template>
<el-form :model="config" :rules="rules" ref="config" label-width="100px" size="small" :disabled="isReadOnly"> <el-form :model="config" :rules="rules" ref="config" label-width="100px" size="small" :disabled="isReadOnly">
<div class="dubbo-form-description" v-if="description"> <div class="dubbo-form-description" v-if="description">
{{description}} {{ description }}
</div> </div>
<el-form-item label="Protocol" prop="protocol" class="dubbo-form-item"> <el-form-item label="Protocol" prop="protocol" class="dubbo-form-item">
<el-select v-model="config.protocol" class="select-100"> <el-select v-model="config.protocol" class="select-100" clearable>
<el-option v-for="p in protocols" :key="p" :label="p" :value="p"/> <el-option v-for="p in protocols" :key="p" :label="p" :value="p"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -43,41 +43,41 @@
</template> </template>
<script> <script>
import './dubbo.css' import './dubbo.css'
import {ConfigCenter} from "@/business/components/api/test/model/ScenarioModel"; import {ConfigCenter} from "@/business/components/api/test/model/ScenarioModel";
export default { export default {
name: "MsDubboConfigCenter", name: "MsDubboConfigCenter",
props: { props: {
description: String, description: String,
config: ConfigCenter, config: ConfigCenter,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
data() { data() {
return { return {
protocols: ConfigCenter.PROTOCOLS, protocols: ConfigCenter.PROTOCOLS,
methods: [], methods: [],
rules: { rules: {
group: [ group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
namespace: [ namespace: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
username: [ username: [
{max: 100, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 100, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
password: [ password: [
{max: 30, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 30, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
address: [ address: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
] ]
}
} }
} }
} }
}
</script> </script>

View File

@ -2,7 +2,7 @@
<el-form :model="consumer" :rules="rules" ref="consumer" label-width="100px" size="small" :disabled="isReadOnly"> <el-form :model="consumer" :rules="rules" ref="consumer" label-width="100px" size="small" :disabled="isReadOnly">
<div class="dubbo-form-description" v-if="description"> <div class="dubbo-form-description" v-if="description">
{{description}} {{ description }}
</div> </div>
<el-form-item label="Timeout" prop="timeout" class="dubbo-form-item"> <el-form-item label="Timeout" prop="timeout" class="dubbo-form-item">
<el-input type="number" v-model="consumer.timeout" :placeholder="$t('commons.input_content')"/> <el-input type="number" v-model="consumer.timeout" :placeholder="$t('commons.input_content')"/>
@ -32,13 +32,13 @@
</el-form-item> </el-form-item>
<el-form-item label="Async" prop="async" class="dubbo-form-item"> <el-form-item label="Async" prop="async" class="dubbo-form-item">
<el-select v-model="consumer.async" class="select-100"> <el-select v-model="consumer.async" class="select-100" clearable>
<el-option v-for="option in asyncOptions" :key="option" :label="option" :value="option"/> <el-option v-for="option in asyncOptions" :key="option" :label="option" :value="option"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="LoadBalance" prop="loadBalance" class="dubbo-form-item"> <el-form-item label="LoadBalance" prop="loadBalance" class="dubbo-form-item">
<el-select v-model="consumer.loadBalance" class="select-100"> <el-select v-model="consumer.loadBalance" class="select-100" clearable>
<el-option v-for="option in loadBalances" :key="option" :label="option" :value="option"/> <el-option v-for="option in loadBalances" :key="option" :label="option" :value="option"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -47,36 +47,36 @@
</template> </template>
<script> <script>
import './dubbo.css' import './dubbo.css'
import {ConsumerAndService, RegistryCenter} from "@/business/components/api/test/model/ScenarioModel"; import {ConsumerAndService, RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
export default { export default {
name: "MsDubboConsumerService", name: "MsDubboConsumerService",
props: { props: {
description: String, description: String,
consumer: ConsumerAndService, consumer: ConsumerAndService,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
data() { data() {
return { return {
asyncOptions: ConsumerAndService.ASYNC_OPTIONS, asyncOptions: ConsumerAndService.ASYNC_OPTIONS,
loadBalances: ConsumerAndService.LOAD_BALANCE_OPTIONS, loadBalances: ConsumerAndService.LOAD_BALANCE_OPTIONS,
methods: [], methods: [],
rules: { rules: {
version: [ version: [
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'} {max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
], ],
cluster: [ cluster: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
group: [ group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
] ]
}
} }
} }
} }
}
</script> </script>

View File

@ -1,10 +1,10 @@
<template> <template>
<el-form :model="registry" :rules="rules" ref="registry" label-width="100px" size="small" :disabled="isReadOnly"> <el-form :model="registry" :rules="rules" ref="registry" label-width="100px" size="small" :disabled="isReadOnly">
<div class="dubbo-form-description" v-if="description"> <div class="dubbo-form-description" v-if="description">
{{description}} {{ description }}
</div> </div>
<el-form-item label="Protocol" prop="protocol" class="dubbo-form-item"> <el-form-item label="Protocol" prop="protocol" class="dubbo-form-item">
<el-select v-model="registry.protocol" class="select-100"> <el-select v-model="registry.protocol" class="select-100" clearable>
<el-option v-for="p in protocols" :key="p" :label="p" :value="p"/> <el-option v-for="p in protocols" :key="p" :label="p" :value="p"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -36,38 +36,38 @@
</template> </template>
<script> <script>
import './dubbo.css' import './dubbo.css'
import {RegistryCenter} from "@/business/components/api/test/model/ScenarioModel"; import {RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
export default { export default {
name: "MsDubboRegistryCenter", name: "MsDubboRegistryCenter",
props: { props: {
description: String, description: String,
registry: RegistryCenter, registry: RegistryCenter,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
data() { data() {
return { return {
protocols: RegistryCenter.PROTOCOLS, protocols: RegistryCenter.PROTOCOLS,
methods: [], methods: [],
rules: { rules: {
group: [ group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
username: [ username: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
], ],
password: [ password: [
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'} {max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
], ],
address: [ address: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'} {max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
] ]
}
} }
} }
} }
}
</script> </script>

View File

@ -1,9 +1,12 @@
<template> <template>
<el-aside :width="width" class="ms-aside-container"
<el-aside :width="width" class="ms-aside-container"> :style="{'margin-left': asideHidden ? '0' : '-' + width}">
<div class="hiddenBottom" @click="asideHidden = !asideHidden" :style="{'left': width}">
<i v-if="asideHidden" class="el-icon-arrow-left"/>
<i v-if="!asideHidden" class="el-icon-arrow-right"/>
</div>
<slot></slot> <slot></slot>
</el-aside> </el-aside>
</template> </template>
<script> <script>
@ -14,6 +17,11 @@
type: String, type: String,
default: '300px' default: '300px'
} }
},
data() {
return {
asideHidden: true
}
} }
} }
</script> </script>
@ -27,6 +35,31 @@
box-sizing: border-box; box-sizing: border-box;
background-color: #FFF; background-color: #FFF;
height: calc(100vh - 80px); height: calc(100vh - 80px);
position: relative;
overflow: inherit;
}
.hiddenBottom {
z-index: 199;
width: 8px;
height: 50px;
top: calc((100vh - 80px)/2);
line-height: 50px;
border-radius: 0 15px 15px 0;
background-color: #acb7c1;
display: inline-block;
position: absolute;
cursor: pointer;
opacity: 0.2;
font-size: 2px;
}
.hiddenBottom i {
margin-left: -2px;
}
.hiddenBottom:hover {
opacity: 0.5;
} }
</style> </style>

View File

@ -0,0 +1,74 @@
<template>
<editor v-model="formatData" :lang="mode" @init="editorInit" theme="chrome"/>
</template>
<script>
export default {
name: "MsCodeEdit",
components: { editor: require('vue2-ace-editor')},
data() {
return {
mode: 'text',
formatData: ''
}
},
props: {
data: {
type: String
},
init: {
type: Function
},
readOnly: {
type: Boolean,
default() {
return false;
}
},
modes: {
type: Array,
default() {
return ['text', 'json', 'xml', 'html'];
}
}
},
mounted() {
this.format();
},
methods: {
editorInit: function (editor) {
require('brace/ext/language_tools') //language extension prerequsite...
this.modes.forEach(mode => {
require('brace/mode/' + mode); //language
});
require('brace/theme/chrome')
require('brace/snippets/javascript') //snippet
if (this.readOnly) {
editor.setReadOnly(true);
}
if (this.init) {
this.init(editor);
}
},
format() {
if (this.mode === 'json') {
try {
this.formatData = JSON.stringify(JSON.parse(this.data), null, '\t');
} catch (e) {
this.formatData = this.data;
}
} else {
this.formatData = this.data;
}
},
setMode(mode) {
this.mode = mode;
this.format();
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,55 @@
<template>
<el-dropdown @command="handleCommand">
<slot>
<span class="el-dropdown-link">
{{currentCommand}}
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
</slot>
<el-dropdown-menu slot="dropdown" chang>
<el-dropdown-item v-for="(command, index) in commands" :key="index" :command="command">
{{command}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
name: "MsDropdown",
data() {
return {
currentCommand: ''
}
},
props: {
commands: {
type: Array
}
},
created() {
if (this.commands && this.commands.length > 0) {
this.currentCommand = this.commands [0];
}
},
methods: {
handleCommand(command) {
this.currentCommand = command;
this.$emit('command', command);
}
}
}
</script>
<style scoped>
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>

View File

@ -14,11 +14,11 @@
</el-breadcrumb> </el-breadcrumb>
</el-row> </el-row>
<el-row class="ms-report-view-btns"> <el-row class="ms-report-view-btns">
<el-button :disabled="isReadOnly || status !== 'Running'" type="primary" plain size="mini" <el-button :disabled="isReadOnly || report.status !== 'Running'" type="primary" plain size="mini"
@click="stopTest(reportId)"> @click="dialogFormVisible=true">
{{$t('report.test_stop_now')}} {{$t('report.test_stop_now')}}
</el-button> </el-button>
<el-button :disabled="isReadOnly || status !== 'Completed'" type="success" plain size="mini" <el-button :disabled="isReadOnly || report.status !== 'Completed'" type="success" plain size="mini"
@click="rerun(testId)"> @click="rerun(testId)">
{{$t('report.test_execute_again')}} {{$t('report.test_execute_again')}}
</el-button> </el-button>
@ -62,6 +62,16 @@
</el-tabs> </el-tabs>
</el-card> </el-card>
<el-dialog :title="$t('report.test_stop_now_confirm')" :visible.sync="dialogFormVisible" width="30%">
<p v-html="$t('report.force_stop_tips')"></p>
<p v-html="$t('report.stop_tips')"></p>
<div slot="footer" class="dialog-footer">
<el-button type="danger" size="small" @click="stopTest(true)">{{$t('report.force_stop_btn')}}
</el-button>
<el-button type="primary" size="small" @click="stopTest(false)">{{$t('report.stop_btn')}}
</el-button>
</div>
</el-dialog>
</ms-main-container> </ms-main-container>
</ms-container> </ms-container>
</template> </template>
@ -103,7 +113,8 @@
title: 'Logging', title: 'Logging',
report: {}, report: {},
isReadOnly: false, isReadOnly: false,
websocket: null websocket: null,
dialogFormVisible: false,
} }
}, },
methods: { methods: {
@ -156,7 +167,7 @@
this.$warning(this.$t('report.generation_error')); this.$warning(this.$t('report.generation_error'));
break; break;
case 'Starting': case 'Starting':
this.$warning(this.$t('report.start_status')); this.$alert(this.$t('report.start_status'));
break; break;
case 'Reporting': case 'Reporting':
case 'Running': case 'Running':
@ -171,18 +182,16 @@
this.minutes = '0'; this.minutes = '0';
this.seconds = '0'; this.seconds = '0';
}, },
stopTest(reportId) { stopTest(forceStop) {
this.$confirm(this.$t('report.test_stop_now_confirm'), '', { this.result = this.$get('/performance/stop/' + this.reportId + '/' + forceStop, () => {
confirmButtonText: this.$t('commons.confirm'), this.$success(this.$t('report.test_stop_success'));
cancelButtonText: this.$t('commons.cancel'), if (forceStop) {
type: 'warning'
}).then(() => {
this.result = this.$get('/performance/stop/' + reportId, () => {
this.$success(this.$t('report.test_stop_success'));
this.$router.push('/performance/report/all'); this.$router.push('/performance/report/all');
}) } else {
}).catch(() => { this.report.status = 'Completed';
}); }
})
this.dialogFormVisible = false;
}, },
rerun(testId) { rerun(testId) {
this.$confirm(this.$t('report.test_rerun_confirm'), '', { this.$confirm(this.$t('report.test_rerun_confirm'), '', {
@ -190,26 +199,32 @@
cancelButtonText: this.$t('commons.cancel'), cancelButtonText: this.$t('commons.cancel'),
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.result = this.$post('/performance/run', {id: testId, triggerMode: 'MANUAL'}, () => { this.result = this.$post('/performance/run', {id: testId, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('load_test.is_running')) this.reportId = response.data;
this.$router.push({path: '/performance/report/all'}) this.$router.push({path: '/performance/report/view/' + this.reportId})
// socket
this.initWebSocket();
}) })
}).catch(() => { }).catch(() => {
}); });
}, },
onOpen() { onOpen() {
window.console.log("open WebSocket"); window.console.log("socket opening.");
}, },
onError(e) { onError(e) {
window.console.error(e) window.console.error(e)
}, },
onMessage(e) { onMessage(e) {
this.$set(this.report, "refresh", e.data); // this.$set(this.report, "refresh", e.data); //
this.$set(this.report, "status", 'Running');
this.initReportTimeInfo(); this.initReportTimeInfo();
window.console.log('receive a message:', e.data);
}, },
onClose(e) { onClose(e) {
this.$set(this.report, "refresh", e.data); // this.$set(this.report, "refresh", Math.random()); //
this.$set(this.report, "status", 'Completed');
this.initReportTimeInfo(); this.initReportTimeInfo();
window.console.log("socket closed.");
} }
}, },
created() { created() {

View File

@ -153,6 +153,9 @@
watch: { watch: {
report: { report: {
handler(val) { handler(val) {
if (!val.status || !val.id) {
return;
}
let status = val.status; let status = val.status;
this.id = val.id; this.id = val.id;
if (status === "Completed" || status === "Running") { if (status === "Completed" || status === "Running") {

View File

@ -80,6 +80,9 @@
watch: { watch: {
report: { report: {
handler(val) { handler(val) {
if (!val.status || !val.id) {
return;
}
let status = val.status; let status = val.status;
this.id = val.id; this.id = val.id;
if (status === "Completed" || status === "Running") { if (status === "Completed" || status === "Running") {

View File

@ -162,6 +162,9 @@
watch: { watch: {
report: { report: {
handler(val){ handler(val){
if (!val.status || !val.id) {
return;
}
let status = val.status; let status = val.status;
this.id = val.id; this.id = val.id;
if (status === "Completed" || status === "Running") { if (status === "Completed" || status === "Running") {

View File

@ -325,6 +325,9 @@
watch: { watch: {
report: { report: {
handler(val) { handler(val) {
if (!val.status || !val.id) {
return;
}
let status = val.status; let status = val.status;
this.id = val.id; this.id = val.id;
if (status === "Completed" || status === "Running") { if (status === "Completed" || status === "Running") {

View File

@ -4,11 +4,13 @@
<el-card v-loading="result.loading"> <el-card v-loading="result.loading">
<el-row> <el-row>
<el-col :span="10"> <el-col :span="10">
<el-input :disabled="isReadOnly" :placeholder="$t('load_test.input_name')" v-model="testPlan.name" class="input-with-select" <el-input :disabled="isReadOnly" :placeholder="$t('load_test.input_name')" v-model="testPlan.name"
class="input-with-select"
maxlength="30" show-word-limit maxlength="30" show-word-limit
> >
<template v-slot:prepend> <template v-slot:prepend>
<el-select :disabled="isReadOnly" v-model="testPlan.projectId" :placeholder="$t('load_test.select_project')"> <el-select :disabled="isReadOnly" v-model="testPlan.projectId"
:placeholder="$t('load_test.select_project')">
<el-option <el-option
v-for="item in projects" v-for="item in projects"
:key="item.id" :key="item.id"
@ -21,10 +23,13 @@
</el-col> </el-col>
<el-col :span="12" :offset="2"> <el-col :span="12" :offset="2">
<el-button :disabled="isReadOnly" type="primary" plain @click="save">{{$t('commons.save')}}</el-button> <el-button :disabled="isReadOnly" type="primary" plain @click="save">{{$t('commons.save')}}</el-button>
<el-button :disabled="isReadOnly" type="primary" plain @click="saveAndRun">{{$t('load_test.save_and_run')}}</el-button> <el-button :disabled="isReadOnly" type="primary" plain @click="saveAndRun">
{{$t('load_test.save_and_run')}}
</el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button> <el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule" :check-open="checkScheduleEdit" :custom-validate="durationValidate"/> <ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
:check-open="checkScheduleEdit" :custom-validate="durationValidate"/>
</el-col> </el-col>
</el-row> </el-row>
@ -34,7 +39,8 @@
<performance-basic-config :is-read-only="isReadOnly" :test-plan="testPlan" ref="basicConfig"/> <performance-basic-config :is-read-only="isReadOnly" :test-plan="testPlan" ref="basicConfig"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('load_test.pressure_config')"> <el-tab-pane :label="$t('load_test.pressure_config')">
<performance-pressure-config :is-read-only="isReadOnly" :test-plan="testPlan" :test-id="testId" ref="pressureConfig" @changeActive="changeTabActive"/> <performance-pressure-config :is-read-only="isReadOnly" :test-plan="testPlan" :test-id="testId"
ref="pressureConfig" @changeActive="changeTabActive"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config"> <el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
<performance-advanced-config :read-only="isReadOnly" :test-id="testId" ref="advancedConfig"/> <performance-advanced-config :read-only="isReadOnly" :test-id="testId" ref="advancedConfig"/>
@ -67,7 +73,7 @@
data() { data() {
return { return {
result: {}, result: {},
testPlan: {schedule:{}}, testPlan: {schedule: {}},
listProjectPath: "/project/listAll", listProjectPath: "/project/listAll",
savePath: "/performance/save", savePath: "/performance/save",
editPath: "/performance/edit", editPath: "/performance/edit",
@ -177,9 +183,9 @@
this.result = this.$request(options, (response) => { this.result = this.$request(options, (response) => {
this.testPlan.id = response.data; this.testPlan.id = response.data;
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, () => { this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('load_test.is_running')) let reportId = response.data;
this.$router.push({path: '/performance/report/all'}) this.$router.push({path: '/performance/report/view/' + reportId})
}) })
}); });
}, },
@ -249,7 +255,7 @@
return true; return true;
}, },
changeTabActive(activeName) { changeTabActive(activeName) {
this.$nextTick(()=> { this.$nextTick(() => {
this.active = activeName; this.active = activeName;
}); });
}, },

View File

@ -258,10 +258,14 @@ export default {
generation_error: 'Report generation error, cannot be viewed!', generation_error: 'Report generation error, cannot be viewed!',
being_generated: 'Report is being generated...', being_generated: 'Report is being generated...',
delete_confirm: 'Confirm delete: ', delete_confirm: 'Confirm delete: ',
start_status: 'The test is starting, please check the report later!', start_status: 'The test is in the beginning state, we will automatically display it on the page after we generate the report!',
run_status: 'The test is running, please check the report later', run_status: 'The test is running, please check the report later',
user_name: 'Creator', user_name: 'Creator',
project_name: 'Project Name' project_name: 'Project Name',
force_stop_tips: '<strong>Terminating</strong> the servers will immediately kill the servers and the JTL files will be lost.',
stop_tips: 'A <strong>Graceful shutdown</strong> will archive the JTL files and then stop the servers.',
force_stop_btn: 'Terminating',
stop_btn: 'Graceful shutdown',
}, },
load_test: { load_test: {
operating: 'Operating', operating: 'Operating',
@ -470,6 +474,7 @@ export default {
sub_result: "Sub Result", sub_result: "Sub Result",
detail: "Report detail", detail: "Report detail",
delete: "Delete report", delete: "Delete report",
running: "The test is running",
}, },
test_track: { test_track: {
test_track: "Track", test_track: "Track",

View File

@ -256,11 +256,14 @@ export default {
generation_error: '报告生成错误,无法查看!', generation_error: '报告生成错误,无法查看!',
being_generated: '报告正在生成中...', being_generated: '报告正在生成中...',
delete_confirm: '确认删除报告: ', delete_confirm: '确认删除报告: ',
start_status: '测试处于开始状态,请稍后查看报告', start_status: '测试处于开始状态, 我们生成报告后会自动展示到页面上',
run_status: '测试处于运行状态,请稍后查看报告!', run_status: '测试处于运行状态,请稍后查看报告!',
user_name: '创建人', user_name: '创建人',
project_name: '所属项目', project_name: '所属项目',
force_stop_tips: '<strong>强制停止</strong>测试会立刻结束当前测试并删除报告数据',
stop_tips: '<strong>停止</strong>测试会结束当前测试并保留报告数据',
force_stop_btn: '强制停止',
stop_btn: '停止',
}, },
load_test: { load_test: {
operating: '操作', operating: '操作',
@ -470,6 +473,7 @@ export default {
sub_result: "子请求", sub_result: "子请求",
detail: "报告详情", detail: "报告详情",
delete: "删除报告", delete: "删除报告",
running: "测试执行中",
}, },
test_track: { test_track: {
test_track: "测试跟踪", test_track: "测试跟踪",

View File

@ -256,10 +256,14 @@ export default {
generation_error: '報告生成錯誤,無法查看!', generation_error: '報告生成錯誤,無法查看!',
being_generated: '報告正在生成中...', being_generated: '報告正在生成中...',
delete_confirm: '確認刪除報告: ', delete_confirm: '確認刪除報告: ',
start_status: '測試處於開始狀態,請稍後查看報告', start_status: '測試處於開始狀態, 我們生成報告後會自動展示到頁面上',
run_status: '測試處於運行狀態,請稍後查看報告!', run_status: '測試處於運行狀態,請稍後查看報告!',
user_name: '創建人', user_name: '創建人',
project_name: '所屬項目' project_name: '所屬項目',
force_stop_tips: '<strong>強制停止</strong>測試會立刻結束當前測試並刪除報告數據',
stop_tips: '<strong>停止</strong>測試會結束當前測試並保留報告數據',
force_stop_btn: '強制停止',
stop_btn: '停止',
}, },
load_test: { load_test: {
operating: '操作', operating: '操作',
@ -469,6 +473,7 @@ export default {
sub_result: "子請求", sub_result: "子請求",
detail: "報告詳情", detail: "報告詳情",
delete: "刪除報告", delete: "刪除報告",
running: "測試執行中",
}, },
test_track: { test_track: {
test_track: "測試跟踪", test_track: "測試跟踪",

View File

@ -17,8 +17,8 @@
<div class="form"> <div class="form">
<el-form-item v-slot:default> <el-form-item v-slot:default>
<el-radio-group v-model="form.authenticate"> <el-radio-group v-model="form.authenticate">
<el-radio label="LDAP" size="mini">LDAP</el-radio> <el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio>
<el-radio label="LOCAL" size="mini">普通登录</el-radio> <el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item prop="username"> <el-form-item prop="username">
@ -81,7 +81,8 @@
] ]
}, },
msg: '', msg: '',
ready: false ready: false,
openLdap: false
} }
}, },
beforeCreate() { beforeCreate() {
@ -92,6 +93,9 @@
window.location.href = "/" window.location.href = "/"
} }
}); });
this.$get("/ldap/open", response => {
this.openLdap = response.data;
})
}, },
created: function () { created: function () {
// ,, // ,,
@ -145,7 +149,7 @@
if (!language) { if (!language) {
this.$get("language", response => { this.$get("language", response => {
language = response.data; language = response.data;
localStorage.setItem(DEFAULT_LANGUAGE, language) localStorage.setItem(DEFAULT_LANGUAGE, language);
window.location.href = "/" window.location.href = "/"
}) })
} else { } else {