Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Captain.B 2020-05-12 15:40:06 +08:00
commit 94556d6687
12 changed files with 266 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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=无法删除当前登录用户

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
}
}
},

View File

@ -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: "已拷贝"
}
}
},