Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
94556d6687
|
@ -11,6 +11,7 @@ import io.metersphere.controller.request.UserRequest;
|
|||
import io.metersphere.controller.request.member.AddMemberRequest;
|
||||
import io.metersphere.controller.request.member.EditPassWordRequest;
|
||||
import io.metersphere.controller.request.member.QueryMemberRequest;
|
||||
import io.metersphere.controller.request.member.SetAdminRequest;
|
||||
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
|
||||
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
|
||||
import io.metersphere.dto.UserDTO;
|
||||
|
@ -265,4 +266,10 @@ public class UserController {
|
|||
return userService.updateUserPassword(request);
|
||||
}
|
||||
|
||||
@PostMapping("/set/admin")
|
||||
@RequiresRoles(RoleConstants.ADMIN)
|
||||
public void setAdmin(@RequestBody SetAdminRequest request) {
|
||||
userService.setAdmin(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.controller.request.member;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SetAdminRequest {
|
||||
private String id;
|
||||
private String adminId;
|
||||
private String password;
|
||||
}
|
|
@ -4,12 +4,14 @@ import io.metersphere.base.domain.*;
|
|||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.base.mapper.ext.ExtUserMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtUserRoleMapper;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CodingUtil;
|
||||
import io.metersphere.controller.request.UserRequest;
|
||||
import io.metersphere.controller.request.member.AddMemberRequest;
|
||||
import io.metersphere.controller.request.member.EditPassWordRequest;
|
||||
import io.metersphere.controller.request.member.QueryMemberRequest;
|
||||
import io.metersphere.controller.request.member.SetAdminRequest;
|
||||
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
|
||||
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
|
||||
import io.metersphere.dto.UserDTO;
|
||||
|
@ -129,6 +131,10 @@ public class UserService {
|
|||
}
|
||||
|
||||
public void deleteUser(String userId) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
if (StringUtils.equals(user.getId(), userId)) {
|
||||
MSException.throwException(Translator.get("cannot_delete_current_user"));
|
||||
}
|
||||
userMapper.deleteByPrimaryKey(userId);
|
||||
}
|
||||
|
||||
|
@ -322,4 +328,20 @@ public class UserService {
|
|||
return extUserMapper.updatePassword(user);
|
||||
}
|
||||
|
||||
public void setAdmin(SetAdminRequest request) {
|
||||
String adminId = request.getAdminId();
|
||||
String password = request.getPassword();
|
||||
if (!checkUserPassword(adminId, password)) {
|
||||
MSException.throwException("verification failed!");
|
||||
}
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setId(UUID.randomUUID().toString());
|
||||
userRole.setUserId(request.getId());
|
||||
// TODO 修改admin sourceId
|
||||
userRole.setSourceId("adminSourceId");
|
||||
userRole.setRoleId(RoleConstants.ADMIN);
|
||||
userRole.setCreateTime(System.currentTimeMillis());
|
||||
userRole.setUpdateTime(System.currentTimeMillis());
|
||||
userRoleMapper.insertSelective(userRole);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,4 +32,5 @@ workspace_not_exists=Workspace is not exists
|
|||
#api
|
||||
api_load_script_error=Load script error
|
||||
user_id_already_exists=User ID already exists
|
||||
password_modification_failed=Password modification failed
|
||||
password_modification_failed=Password modification failed
|
||||
cannot_delete_current_user=Cannot delete the user currently logged in
|
|
@ -32,4 +32,5 @@ workspace_not_exists=工作空间不存在
|
|||
#api
|
||||
api_load_script_error=读取脚本失败
|
||||
user_id_already_exists=用户id已存在
|
||||
password_modification_failed=密码修改失败
|
||||
password_modification_failed=密码修改失败
|
||||
cannot_delete_current_user=无法删除当前登录用户
|
|
@ -122,7 +122,8 @@
|
|||
})
|
||||
},
|
||||
cancel: function () {
|
||||
this.$router.push('/api/test/list/all');
|
||||
console.log(this.test.toJMX().xml)
|
||||
// this.$router.push('/api/test/list/all');
|
||||
},
|
||||
getOptions: function (url) {
|
||||
let formData = new FormData();
|
||||
|
|
|
@ -2,8 +2,17 @@
|
|||
<div>
|
||||
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
|
||||
<el-col :span="10">
|
||||
<el-input v-model="common.variable" maxlength="60" size="small" @input="change"
|
||||
:placeholder="$t('api_test.request.extract.variable_name')"/>
|
||||
<div class="variable">
|
||||
<el-input v-model="common.variable" maxlength="60" size="small" @input="change"
|
||||
:placeholder="$t('api_test.request.extract.variable_name')"/>
|
||||
<div class="variable-combine" v-if="common.variable && edit">
|
||||
<div class="value">{{common.value}}</div>
|
||||
<el-tooltip :content="$t('api_test.request.extract.copied')" manual v-model="visible" placement="top"
|
||||
:visible-arrow="false">
|
||||
<i class="el-icon-copy-document copy" @click="copy"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-input v-model="common.expression" maxlength="255" size="small" :placeholder="expression"/>
|
||||
|
@ -43,6 +52,12 @@
|
|||
list: Array
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.list.push(new ExtractCommon(this.extractType, this.common));
|
||||
|
@ -58,6 +73,21 @@
|
|||
this.common.variable = null;
|
||||
this.common.expression = null;
|
||||
this.common.value = null;
|
||||
},
|
||||
copy() {
|
||||
let input = document.createElement("input");
|
||||
document.body.appendChild(input);
|
||||
input.value = this.common.value;
|
||||
input.select();
|
||||
if (input.setSelectionRange) {
|
||||
input.setSelectionRange(0, input.value.length);
|
||||
}
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(input);
|
||||
this.visible = true;
|
||||
setTimeout(() => {
|
||||
this.visible = false;
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -79,6 +109,37 @@
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.variable {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.variable-combine {
|
||||
color: #7F7F7F;
|
||||
max-width: 80px;
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 25px;
|
||||
margin-right: -20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.variable-combine .value {
|
||||
display: inline-block;
|
||||
max-width: 60px;
|
||||
margin-right: 10px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.variable-combine .copy {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: #1E90FF;
|
||||
}
|
||||
|
||||
.extract-btn {
|
||||
width: 60px;
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ export class DefaultTestElement extends TestElement {
|
|||
}
|
||||
|
||||
export class TestPlan extends DefaultTestElement {
|
||||
constructor(testName) {
|
||||
constructor(testName, args) {
|
||||
super('TestPlan', 'TestPlanGui', 'TestPlan', testName || 'TestPlan');
|
||||
|
||||
this.boolProp("TestPlan.functional_mode", false);
|
||||
|
@ -176,6 +176,7 @@ export class TestPlan extends DefaultTestElement {
|
|||
this.boolProp("TestPlan.tearDown_on_shutdown", true);
|
||||
this.stringProp("TestPlan.comments", "");
|
||||
this.stringProp("TestPlan.user_define_classpath", "");
|
||||
this.add(new ElementArguments(args, "TestPlan.user_defined_variables", "User Defined Variables"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,25 +383,59 @@ export class BackendListener extends DefaultTestElement {
|
|||
}
|
||||
|
||||
export class ElementArguments extends Element {
|
||||
constructor(args) {
|
||||
constructor(args, name, testName) {
|
||||
super('elementProp', {
|
||||
name: "arguments",
|
||||
name: name || "arguments",
|
||||
elementType: "Arguments",
|
||||
guiclass: "ArgumentsPanel",
|
||||
testclass: "Arguments",
|
||||
testname: testName || "",
|
||||
enabled: "true"
|
||||
});
|
||||
|
||||
let collectionProp = this.collectionProp('Arguments.arguments');
|
||||
args.forEach(arg => {
|
||||
let elementProp = collectionProp.elementProp(arg.name, 'Argument');
|
||||
elementProp.stringProp('Argument.name', arg.name);
|
||||
elementProp.stringProp('Argument.value', arg.value);
|
||||
elementProp.stringProp('Argument.metadata', "=");
|
||||
});
|
||||
if (args) {
|
||||
args.forEach(arg => {
|
||||
let elementProp = collectionProp.elementProp(arg.name, 'Argument');
|
||||
elementProp.stringProp('Argument.name', arg.name);
|
||||
elementProp.stringProp('Argument.value', arg.value);
|
||||
elementProp.stringProp('Argument.metadata', "=");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Class {
|
||||
|
||||
export class RegexExtractor extends DefaultTestElement {
|
||||
constructor(testName, props) {
|
||||
super('RegexExtractor', 'RegexExtractorGui', 'RegexExtractor', testName || 'Regular Expression Extractor');
|
||||
this.props = props || {}
|
||||
this.stringProp('RegexExtractor.useHeaders', props.headers);
|
||||
this.stringProp('RegexExtractor.refname', props.name);
|
||||
this.stringProp('RegexExtractor.regex', props.expression);
|
||||
this.stringProp('RegexExtractor.template', props.template);
|
||||
this.stringProp('RegexExtractor.default', props.default);
|
||||
this.stringProp('RegexExtractor.match_number', props.match);
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONPostProcessor extends DefaultTestElement {
|
||||
constructor(testName, props) {
|
||||
super('JSONPostProcessor', 'JSONPostProcessorGui', 'JSONPostProcessor', testName || 'JSON Extractor');
|
||||
this.props = props || {}
|
||||
this.stringProp('JSONPostProcessor.referenceNames', props.name);
|
||||
this.stringProp('JSONPostProcessor.jsonPathExprs', props.expression);
|
||||
this.stringProp('JSONPostProcessor.match_numbers', props.match);
|
||||
}
|
||||
}
|
||||
|
||||
export class XPath2Extractor extends DefaultTestElement {
|
||||
constructor(testName, props) {
|
||||
super('XPath2Extractor', 'XPath2ExtractorGui', 'XPath2Extractor', testName || 'XPath2 Extractor');
|
||||
this.props = props || {}
|
||||
this.stringProp('XPathExtractor2.default', props.default);
|
||||
this.stringProp('XPathExtractor2.refname', props.name);
|
||||
this.stringProp('XPathExtractor2.xpathQuery', props.expression);
|
||||
this.stringProp('XPathExtractor2.namespaces', props.namespaces);
|
||||
this.stringProp('XPathExtractor2.matchNumber', props.match);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
ResponseCodeAssertion,
|
||||
ResponseDataAssertion,
|
||||
ResponseHeadersAssertion,
|
||||
BackendListener
|
||||
BackendListener, RegexExtractor, JSONPostProcessor, XPath2Extractor
|
||||
} from "./JMX";
|
||||
|
||||
export const uuid = function () {
|
||||
|
@ -414,6 +414,8 @@ class JMXGenerator {
|
|||
|
||||
this.addRequestAssertion(httpSamplerProxy, request);
|
||||
|
||||
this.addRequestExtractor(httpSamplerProxy, request);
|
||||
|
||||
threadGroup.put(httpSamplerProxy);
|
||||
})
|
||||
|
||||
|
@ -486,6 +488,48 @@ class JMXGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
addRequestExtractor(httpSamplerProxy, request) {
|
||||
let extract = request.extract;
|
||||
if (extract.regex.length > 0) {
|
||||
extract.regex.filter(this.filter).forEach(regex => {
|
||||
httpSamplerProxy.put(this.getExtractor(regex));
|
||||
})
|
||||
}
|
||||
|
||||
if (extract.json.length > 0) {
|
||||
extract.json.filter(this.filter).forEach(json => {
|
||||
httpSamplerProxy.put(this.getExtractor(json));
|
||||
})
|
||||
}
|
||||
|
||||
if (extract.xpath.length > 0) {
|
||||
extract.xpath.filter(this.filter).forEach(xpath => {
|
||||
httpSamplerProxy.put(this.getExtractor(xpath));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getExtractor(extractCommon) {
|
||||
let props = {
|
||||
name: extractCommon.variable,
|
||||
expression: extractCommon.expression,
|
||||
}
|
||||
let testName = props.name
|
||||
switch (extractCommon.type) {
|
||||
case EXTRACT_TYPE.REGEX:
|
||||
testName += " RegexExtractor";
|
||||
props.headers = "false"; // 对应jMeter body
|
||||
props.template = "$1$";
|
||||
return new RegexExtractor(testName, props);
|
||||
case EXTRACT_TYPE.JSON_PATH:
|
||||
testName += " JSONExtractor";
|
||||
return new JSONPostProcessor(testName, props);
|
||||
case EXTRACT_TYPE.XPATH:
|
||||
testName += " XPath2Evaluator";
|
||||
return new XPath2Extractor(testName, props);
|
||||
}
|
||||
}
|
||||
|
||||
addBackendListener(threadGroup) {
|
||||
let testName = 'API Backend Listener';
|
||||
let className = 'io.metersphere.api.jmeter.APIBackendListenerClient';
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
<template v-slot:behind>
|
||||
<ms-table-operator-button :tip="$t('member.edit_password')" icon="el-icon-s-tools"
|
||||
type="success" @exec="editPassword(scope.row)"/>
|
||||
<ms-table-operator-button :tip="$t('commons.set_admin')" icon="el-icon-user-solid"
|
||||
type="danger" @exec="openCheckDialog(scope.row)"/>
|
||||
</template>
|
||||
</ms-table-operator>
|
||||
</template>
|
||||
|
@ -45,6 +47,21 @@
|
|||
:total="total"/>
|
||||
</el-card>
|
||||
|
||||
<el-dialog :title="$t('commons.verification')" :visible.sync="checkPasswordVisible" width="30%"
|
||||
@close="closeCheckPassword" :destroy-on-close="true">
|
||||
<el-form :model="checkPasswordForm" label-position="right" label-width="100px" size="small" :rules="rule"
|
||||
ref="checkPasswordForm">
|
||||
<el-form-item :label="$t('commons.password')" prop="password">
|
||||
<el-input type="password" v-model="checkPasswordForm.password" autocomplete="off" show-password></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<ms-dialog-footer
|
||||
@cancel="checkPasswordVisible = false"
|
||||
@confirm="setAdmin('checkPasswordForm')"/>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!--Create user-->
|
||||
<el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="handleClose"
|
||||
:destroy-on-close="true">
|
||||
|
@ -97,7 +114,8 @@
|
|||
</el-dialog>
|
||||
<!--Changing user password in system settings-->
|
||||
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="30%" left>
|
||||
<el-form :model="ruleForm" label-position="right" label-width="100px" size="small" :rules="rule" ref="editPasswordForm" class="demo-ruleForm">
|
||||
<el-form :model="ruleForm" label-position="right" label-width="100px" size="small" :rules="rule"
|
||||
ref="editPasswordForm" class="demo-ruleForm">
|
||||
<el-form-item :label="$t('member.new_password')" prop="newpassword">
|
||||
<el-input type="password" v-model="ruleForm.newpassword" autocomplete="off" show-password></el-input>
|
||||
</el-form-item>
|
||||
|
@ -122,6 +140,7 @@
|
|||
import MsTableOperator from "../../common/components/MsTableOperator";
|
||||
import MsDialogFooter from "../../common/components/MsDialogFooter";
|
||||
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
|
||||
import {getCurrentUser} from "../../../../common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "MsUser",
|
||||
|
@ -136,7 +155,8 @@
|
|||
result: {},
|
||||
createVisible: false,
|
||||
updateVisible: false,
|
||||
editPasswordVisible:false,
|
||||
editPasswordVisible: false,
|
||||
checkPasswordVisible: false,
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
|
@ -145,7 +165,9 @@
|
|||
condition: {},
|
||||
tableData: [],
|
||||
form: {},
|
||||
checkPasswordForm: {},
|
||||
ruleForm: {},
|
||||
setAdminParam: {},
|
||||
rule: {
|
||||
id: [
|
||||
{required: true, message: this.$t('user.input_id'), trigger: 'blur'},
|
||||
|
@ -181,20 +203,20 @@
|
|||
password: [
|
||||
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
||||
{
|
||||
required:true,
|
||||
required: true,
|
||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
||||
message: this.$t('member.password_format_is_incorrect'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
newpassword: [
|
||||
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
||||
{
|
||||
required:true,
|
||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
||||
message: this.$t('member.password_format_is_incorrect'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
{required: true, message: this.$t('user.input_password'), trigger: 'blur'},
|
||||
{
|
||||
required: true,
|
||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,
|
||||
message: this.$t('member.password_format_is_incorrect'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -210,8 +232,8 @@
|
|||
this.updateVisible = true;
|
||||
this.form = Object.assign({}, row);
|
||||
},
|
||||
editPassword(row){
|
||||
this.editPasswordVisible=true;
|
||||
editPassword(row) {
|
||||
this.editPasswordVisible = true;
|
||||
this.ruleForm = Object.assign({}, row);
|
||||
},
|
||||
del(row) {
|
||||
|
@ -260,10 +282,10 @@
|
|||
this.result = this.$post(this.editPasswordPath, this.ruleForm, response => {
|
||||
this.$success(this.$t('commons.modify_success'));
|
||||
this.editPasswordVisible = false;
|
||||
this.search() ;
|
||||
this.search();
|
||||
window.location.reload();
|
||||
});
|
||||
}else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
|
@ -288,6 +310,28 @@
|
|||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
closeCheckPassword() {
|
||||
this.checkPasswordForm = {};
|
||||
},
|
||||
openCheckDialog(row) {
|
||||
this.$set(this.setAdminParam, 'id', row.id);
|
||||
this.checkPasswordVisible = true;
|
||||
},
|
||||
setAdmin(checkPasswordForm) {
|
||||
let user = getCurrentUser();
|
||||
this.$set(this.setAdminParam, 'adminId', user.id);
|
||||
this.$set(this.setAdminParam, 'password', this.checkPasswordForm.password);
|
||||
this.$refs[checkPasswordForm].validate(valid => {
|
||||
if (valid) {
|
||||
this.$post("/user/set/admin", this.setAdminParam, () => {
|
||||
this.$success(this.$t('commons.modify_success'));
|
||||
this.checkPasswordVisible = false;
|
||||
})
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ export default {
|
|||
'search_by_name': 'Search by name',
|
||||
'personal_information': 'Personal Information',
|
||||
'exit_system': 'Exit System',
|
||||
'verification': 'Verification',
|
||||
'set_admin': 'Set Admin',
|
||||
},
|
||||
workspace: {
|
||||
'create': 'Create Workspace',
|
||||
|
@ -255,6 +257,7 @@ export default {
|
|||
regex_expression: "Regular expression",
|
||||
json_path_expression: "JSONPath expression",
|
||||
xpath_expression: "XPath expression",
|
||||
copied: "Copied"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -55,6 +55,8 @@ export default {
|
|||
'search_by_name': '根据名称搜索',
|
||||
'personal_information': '个人信息',
|
||||
'exit_system': '退出系统',
|
||||
'verification': '验证',
|
||||
'set_admin': '设置为管理员',
|
||||
},
|
||||
workspace: {
|
||||
'create': '创建工作空间',
|
||||
|
@ -243,18 +245,19 @@ export default {
|
|||
start_with: "以...开始",
|
||||
end_with: "以...结束",
|
||||
value: "值",
|
||||
expression: "表达式",
|
||||
expression: "Perl型正则表达式",
|
||||
response_in_time: "响应时间在...毫秒以内",
|
||||
},
|
||||
extract: {
|
||||
label: "提取",
|
||||
select_type: "请选择类型",
|
||||
description: "从响应中提取数据并将其存储在变量中,在后续请求中使用变量。",
|
||||
description: "从响应结果中提取数据并将其存储在变量中,在后续请求中使用变量。",
|
||||
regex: "正则",
|
||||
variable_name: "变量名",
|
||||
regex_expression: "正则表达式",
|
||||
regex_expression: "Perl型正则表达式",
|
||||
json_path_expression: "JSONPath表达式",
|
||||
xpath_expression: "XPath表达式",
|
||||
copied: "已拷贝"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue