This commit is contained in:
shiziyuan9527 2020-09-14 16:51:01 +08:00
commit 5c400f616c
20 changed files with 546 additions and 64 deletions

View File

@ -312,7 +312,7 @@
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>

View File

@ -6,7 +6,7 @@ import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.ResultHolder;
import io.metersphere.dto.NodeDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.performance.engine.AbstractEngine;
@ -87,12 +87,14 @@ public class DockerTestEngine extends AbstractEngine {
testRequest.setTestData(context.getTestData());
testRequest.setEnv(context.getEnv());
try {
restTemplate.postForObject(uri, testRequest, String.class);
} catch (Exception e) {
LogUtil.error(e);
ResultHolder result = restTemplate.postForObject(uri, testRequest, ResultHolder.class);
if (result == null) {
MSException.throwException(Translator.get("start_engine_fail"));
}
if (!result.isSuccess()) {
MSException.throwException(result.getMessage());
}
}
@Override
@ -105,12 +107,13 @@ public class DockerTestEngine extends AbstractEngine {
Integer port = node.getPort();
String uri = String.format(BASE_URL + "/jmeter/container/stop/" + testId, ip, port);
try {
restTemplateWithTimeOut.getForObject(uri, String.class);
} catch (Exception e) {
LogUtil.error("stop load test fail... " + testId, e);
ResultHolder result = restTemplateWithTimeOut.getForObject(uri, ResultHolder.class);
if (result == null) {
MSException.throwException(Translator.get("container_delete_fail"));
}
if (!result.isSuccess()) {
MSException.throwException(result.getMessage());
}
});
}
}

View File

@ -0,0 +1,13 @@
CREATE TABLE `license` (
`id` varchar(50) NOT NULL COMMENT 'ID',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
`corporation` varchar(500) NOT NULL COMMENT 'corporation ',
`expired` varchar(255) NOT NULL COMMENT 'expired ',
`product` varchar(500) DEFAULT NULL COMMENT 'product name',
`edition` varchar(255) COMMENT 'edition ',
`license_version` varchar(255) NOT NULL COMMENT 'licenseVersion',
`license_count` INT COMMENT 'license_count',
`license_code` longtext DEFAULT NULL COMMENT 'license_code',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@ -150,4 +150,6 @@ quota_performance_excess_organization=The number of performance tests exceeds th
quota_max_threads_excess_workspace=The maximum number of concurrent threads exceeds the workspace quota
quota_max_threads_excess_organization=The maximum number of concurrent threads exceeds the organization quota
quota_duration_excess_workspace=The stress test duration exceeds the work space quota
quota_duration_excess_organization=The stress test duration exceeds the organization quota
quota_duration_excess_organization=The stress test duration exceeds the organization quota
license_valid_license_error=valid license error
license_valid_license_code=The authorization code already exists

View File

@ -151,6 +151,7 @@ quota_max_threads_excess_workspace=最大并发数超过工作空间限额
quota_max_threads_excess_organization=最大并发数超过组织限额
quota_duration_excess_workspace=压测时长超过工作空间限额
quota_duration_excess_organization=压测时长超过组织限额
license_valid_license_error=授权验证失败
license_valid_license_code=授权码已经存在

View File

@ -150,4 +150,7 @@ quota_performance_excess_organization=性能測試數量超過組織限額
quota_max_threads_excess_workspace=最大並發數超過工作空間限額
quota_max_threads_excess_organization=最大並發數超過組織限額
quota_duration_excess_workspace=壓測時長超過工作空間限額
quota_duration_excess_organization=壓測時長超過組織限額
quota_duration_excess_organization=壓測時長超過組織限額
license_valid_license_error=授權驗證失敗
license_valid_license_code=授權碼已經存在

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -37,6 +37,9 @@
:environment="scenario.environment"
:description="$t('api_test.scenario.kv_description')"/>
</el-tab-pane>
<el-tab-pane :label="'数据库配置'" name="database">
<ms-database-config :configs="scenario.databaseConfigs"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
<div class="dubbo-config-title">Config Center</div>
<ms-dubbo-config-center :config="scenario.dubboConfig.configCenter" :is-read-only="isReadOnly"/>
@ -62,10 +65,12 @@ import {REQUEST_HEADERS} from "@/common/js/constants";
import MsDubboRegistryCenter from "@/business/components/api/test/components/request/dubbo/RegistryCenter";
import MsDubboConfigCenter from "@/business/components/api/test/components/request/dubbo/ConfigCenter";
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsDatabaseConfig from "./request/database/DatabaseConfig";
export default {
name: "MsApiScenarioForm",
components: {
MsDatabaseConfig,
MsDubboConsumerService,
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue
},

View File

@ -0,0 +1,59 @@
<template>
<div>
<ms-database-from :config="currentConfig" @save="addConfig" ref="databaseFrom"/>
<ms-database-config-list v-if="configs.length > 0" :table-data="configs"/>
</div>
</template>
<script>
import MsDatabaseConfigList from "./DatabaseConfigList";
import {DatabaseConfig} from "../../../model/ScenarioModel";
import MsDatabaseFrom from "./DatabaseFrom";
import {getUUID} from "../../../../../../../common/js/utils";
export default {
name: "MsDatabaseConfig",
components: {MsDatabaseFrom, MsDatabaseConfigList},
props: {
configs: Array,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
currentConfig: new DatabaseConfig()
}
},
methods: {
addConfig(config) {
for (let item of this.configs) {
if (item.name === config.name) {
this.$warning("名称重复");
return;
}
}
config.id = getUUID();
this.configs.push(config);
this.currentConfig = new DatabaseConfig();
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<el-dialog :title="'数据库配置'" :visible.sync="visible">
<ms-database-from :config="config" @save="editConfig"/>
</el-dialog>
</template>
<script>
import MsDatabaseConfigList from "./DatabaseConfigList";
import MsDatabaseFrom from "./DatabaseFrom";
import {DatabaseConfig} from "../../../model/ScenarioModel";
export default {
name: "MsDatabaseConfigDialog",
components: {MsDatabaseFrom, MsDatabaseConfigList},
props: {
configs: Array,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
visible: false,
config: new DatabaseConfig(),
}
},
methods: {
open(config) {
this.visible = true;
Object.assign(this.config, config);
},
editConfig(config) {
let currentConfig = undefined;
for (let item of this.configs) {
if (item.name === config.name && item.id != config.id) {
this.$warning("名称重复");
return;
}
if (item.id === config.id) {
currentConfig = item;
}
}
if (currentConfig) {
Object.assign(currentConfig, config)
} else {
//copy
this.configs.push(config);
}
this.visible = false;
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,92 @@
<template>
<ms-main-container>
<el-table border :data="tableData" class="adjust-table table-content"
@row-click="handleView">
<el-table-column prop="name" :label="'连接池名称'" show-overflow-tooltip/>
<el-table-column prop="driver" :label="'数据库驱动'" show-overflow-tooltip/>
<el-table-column prop="dbUrl" :label="'数据库连接URL'" show-overflow-tooltip/>
<el-table-column prop="username" :label="'用户名'" show-overflow-tooltip/>
<el-table-column prop="poolMax" :label="'最大连接数'" show-overflow-tooltip/>
<el-table-column prop="timeout" :label="'最大等待时间'" show-overflow-tooltip/>
<el-table-column
:label="$t('commons.operating')" min-width="100">
<template v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
@deleteClick="handleDelete(scope.$index)">
<template v-slot:middle>
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.copy')"
icon="el-icon-document-copy"
type="success" @exec="handleCopy(scope.row)"/>
</template>
</ms-table-operator>
</template>
</el-table-column>
</el-table>
<ms-database-config-dialog :configs="tableData" ref="databaseConfigEdit"/>
</ms-main-container>
</template>
<script>
import {DatabaseConfig} from "../../../model/ScenarioModel";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
import MsTableOperator from "../../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../../common/components/MsTableOperatorButton";
import MsDatabaseConfigDialog from "./DatabaseConfigDialog";
import {getUUID} from "../../../../../../../common/js/utils";
export default {
name: "MsDatabaseConfigList",
components: {MsDatabaseConfigDialog, MsTableOperatorButton, MsTableOperator, MsMainContainer},
props: {
tableData: Array,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
result: {},
}
},
methods: {
handleView() {
},
handleEdit(config) {
this.$refs.databaseConfigEdit.open(config);
},
handleDelete(index) {
this.tableData.splice(index, 1);
},
handleCopy(config) {
let copy = {};
Object.assign(copy, config);
copy.id = getUUID();
this.$refs.databaseConfigEdit.open(copy);
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<div>
<el-form :model="config" :rules="rules" label-width="150px" size="small" :disabled="isReadOnly" class="database-from" ref="databaseFrom">
<el-form-item :label="'连接池名称'" prop="name">
<el-input v-model="config.name" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'数据库连接URL'" prop="dbUrl">
<el-input v-model="config.dbUrl" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'数据库驱动'" prop="driver">
<el-select v-model="config.driver" class="select-100" clearable>
<el-option v-for="p in drivers" :key="p" :label="p" :value="p"/>
</el-select>
</el-form-item>
<el-form-item :label="'用户名'" prop="username">
<el-input v-model="config.username" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'密码'" prop="password">
<el-input v-model="config.password" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'最大连接数'" prop="poolMax">
<el-input-number size="small" :disabled="isReadOnly" v-model="config.poolMax" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-form-item :label="'最大等待时间(ms)'" prop="timeout">
<el-input-number size="small" :disabled="isReadOnly" v-model="config.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" class="addButton" @click="save">添加</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {DatabaseConfig} from "../../../model/ScenarioModel";
export default {
name: "MsDatabaseFrom",
components: {},
props: {
isReadOnly: {
type: Boolean,
default: false
},
config: {
type: Object,
default() {
return new DatabaseConfig();
}
},
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
// config: new DatabaseConfig(),
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
driver: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
],
password: [
{max: 200, message: this.$t('commons.input_limit', [0, 200]), trigger: 'blur'}
],
dbUrl: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'}
],
username: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 200, message: this.$t('commons.input_limit', [0, 200]), trigger: 'blur'}
]
}
}
},
methods: {
save() {
this.$refs['databaseFrom'].validate((valid) => {
if (valid) {
this.$emit('save', this.config);
// this.config = new DatabaseConfig();
} else {
return false;
}
});
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -211,14 +211,16 @@ export class Scenario extends BaseConfig {
this.environment = undefined;
this.enableCookieShare = false;
this.enable = true;
this.databaseConfigs = undefined;
this.set(options);
this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory}, options);
this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory, databaseConfigs: DatabaseConfig}, options);
}
initOptions(options = {}) {
options.id = options.id || uuid();
options.requests = options.requests || [new RequestFactory()];
options.databaseConfigs = options.databaseConfigs || [];
options.dubboConfig = new DubboConfig(options.dubboConfig);
return options;
}
@ -479,6 +481,51 @@ export class ConfigCenter extends BaseConfig {
}
}
export class DatabaseConfig extends BaseConfig {
static DRIVER_CLASS = ["com.mysql.jdbc.Driver"];
constructor(options) {
super();
this.id = undefined;
this.name = undefined;
this.poolMax = undefined;
this.timeout = undefined;
this.driver = undefined;
this.dbUrl = undefined;
this.username = undefined;
this.password = undefined;
this.set(options);
}
initOptions(options = {}) {
// options.id = options.id || uuid();
return options;
}
// <JDBCDataSource guiclass="TestBeanGUI" testclass="JDBCDataSource" testname="JDBC Connection Configurationqqq" enabled="true">
// <boolProp name="autocommit">true</boolProp>
// <stringProp name="checkQuery"></stringProp>
// <stringProp name="connectionAge">5000</stringProp>
// <stringProp name="connectionProperties"></stringProp>
// <stringProp name="dataSource">test</stringProp>
// <stringProp name="dbUrl">jdbc:mysql://localhost:3306/metersphere?autoReconnect=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;characterSetResults=UTF-8&amp;zeroDateTimeBehavior=convertToNull&amp;allowMultiQueries=true</stringProp>
// <stringProp name="driver">com.mysql.jdbc.Driver</stringProp>
// <stringProp name="initQuery"></stringProp>
// <boolProp name="keepAlive">true</boolProp>
// <stringProp name="password">root</stringProp>
// <stringProp name="poolMax">10</stringProp>
// <boolProp name="preinit">false</boolProp>
// <stringProp name="timeout">10000</stringProp>
// <stringProp name="transactionIsolation">DEFAULT</stringProp>
// <stringProp name="trimInterval">60000</stringProp>
// <stringProp name="username">root</stringProp>
// </JDBCDataSource>
isValid() {
return !!this.name || !!this.poolMax || !!this.timeout || !!this.driver || !!this.dbUrl || !!this.username || !!this.password;
}
}
export class RegistryCenter extends BaseConfig {
static PROTOCOLS = ["none", "zookeeper", "nacos", "apollo", "multicast", "redis", "simple"];

View File

@ -45,64 +45,99 @@
</template>
<script>
import {checkoutCurrentOrganization, checkoutCurrentWorkspace} from "@/common/js/utils";
import Setting from "@/business/components/settings/router";
import {checkoutCurrentOrganization, checkoutCurrentWorkspace} from "@/common/js/utils";
import Setting from "@/business/components/settings/router";
export default {
name: "MsSettingMenu",
data() {
let getMenus = function (group) {
let menus = [];
Setting.children.forEach(child => {
if (child.meta[group] === true) {
let menu = {index: Setting.path + "/" + child.path}
menu.title = child.meta.title;
menu.roles = child.meta.roles;
menus.push(menu);
}
})
return menus;
export default {
name: "MsSettingMenu",
data() {
let getMenus = function (group) {
let menus = [];
Setting.children.forEach(child => {
if (child.meta[group] === true) {
let menu = {index: Setting.path + "/" + child.path}
menu.title = child.meta.title;
menu.roles = child.meta.roles;
menu.valid = child.meta.valid;
menus.push(menu);
}
})
return menus;
}
return {
systems: getMenus('system'),
organizations: getMenus('organization'),
workspaces: getMenus('workspace'),
persons: getMenus('person'),
isCurrentOrganizationAdmin: false,
isCurrentWorkspaceUser: false,
}
},
mounted() {
this.valid();
this.isCurrentOrganizationAdmin = checkoutCurrentOrganization();
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
},
methods: {
valid() {
let _this = this;
this.result = this.$get("/license/valid", response => {
let data = response.data;
if (data === undefined || data != true) {
this.systems.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.systems.splice(this.systems.indexOf(item), 1);
}
})
this.organizations.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.organizations.splice(this.organizations.indexOf(item), 1);
}
})
this.workspaces.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.workspaces.splice(this.workspaces.indexOf(item), 1);
}
})
this.persons.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.persons.splice(this.persons.indexOf(item), 1);
}
})
}
})
}
}
return {
systems: getMenus('system'),
organizations: getMenus('organization'),
workspaces: getMenus('workspace'),
persons: getMenus('person'),
isCurrentOrganizationAdmin: false,
isCurrentWorkspaceUser: false,
}
},
mounted() {
this.isCurrentOrganizationAdmin = checkoutCurrentOrganization();
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
}
}
</script>
<style scoped>
.setting {
border-right: 0;
}
.setting {
border-right: 0;
}
.setting .setting-item {
height: 40px;
line-height: 40px;
}
.setting .setting-item {
height: 40px;
line-height: 40px;
}
.icon {
width: 24px;
margin-right: 10px;
}
.icon {
width: 24px;
margin-right: 10px;
}
.account {
color: #5a78f0;
}
.account {
color: #5a78f0;
}
.organization {
color: #b33a5b;
}
.organization {
color: #b33a5b;
}
.workspace {
color: #44b349;
}
.workspace {
color: #44b349;
}
</style>

View File

@ -32,7 +32,7 @@ export default {
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/SystemParameterSetting'),
meta: {system: true, title: 'commons.system_parameter_setting'}
},
...requireContext.keys().map(key => requireContext(key).system),
...requireContext.keys().map(key => requireContext(key).system),...requireContext.keys().map(key => requireContext(key).license),
{
path: 'organizationmember',
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/organization/OrganizationMember'),

View File

@ -110,6 +110,7 @@ export default {
please_save: 'Please save first',
formatErr: 'Format Error',
id: 'ID',
cannot_be_null: 'not null ',
millisecond: 'ms',
please_upload: 'Please upload file',
reference_documentation: "Reference documentation",
@ -153,6 +154,18 @@ export default {
}
}
},
license:{
title: 'Authorization management',
corporation: 'corporation',
expired: 'expired',
product: 'product',
edition: 'edition',
licenseVersion: 'licenseVersion',
count: 'count',
valid_license: 'valid license',
show_license: 'show license',
valid_license_error: 'validate license error',
},
workspace: {
create: 'Create Workspace',
update: 'Update Workspace',
@ -640,6 +653,7 @@ export default {
upload_limit_count: "Only one file can be uploaded at a time",
upload_limit_format: "Upload files can only be XLS, XLSX format!",
upload_limit_size: "Upload file size cannot exceed 20MB!",
upload_limit_other_size: "Upload file size cannot exceed",
success: "Import success",
importing: "Importing...",
},

View File

@ -113,6 +113,7 @@ export default {
reference_documentation: "参考文档",
id: 'ID',
millisecond: '毫秒',
cannot_be_null: '不能为空',
date: {
select_date: '选择日期',
start_date: '开始日期',
@ -153,6 +154,18 @@ export default {
}
}
},
license:{
title: '授权管理',
corporation: '客户名称',
expired: '授权时间',
product: '产品名称',
edition: '产品版本',
licenseVersion: '授权版本',
count: '授权数量',
valid_license: '授权验证',
show_license: '查看授权',
valid_license_error: '授权验证失败',
},
workspace: {
create: '创建工作空间',
update: '修改工作空间',
@ -640,6 +653,7 @@ export default {
download_template: "下载模版",
click_upload: "点击上传",
upload_limit: "只能上传xls/xlsx文件且不超过20M",
upload_limit_other_size: "上传文件大小不能超过",
upload_limit_count: "一次只能上传一个文件",
upload_limit_format: "上传文件只能是 xls、xlsx格式!",
upload_limit_size: "上传文件大小不能超过 20MB!",

View File

@ -108,6 +108,7 @@ export default {
formatErr: '格式錯誤',
please_save: '請先保存',
id: 'ID',
cannot_be_null: '不能为空',
millisecond: '毫秒',
reference_documentation: "參考文檔",
please_upload: '請上傳文件',
@ -151,6 +152,19 @@ export default {
}
}
},
license:{
title: '授權管理',
corporation: '客戶名稱',
expired: '授權時間',
product: '產品名稱',
edition: '產品版本',
licenseVersion: '授權版本',
count: '授權數量',
valid_license: '授權验证',
show_license: '查看授權',
valid_license_error: '授權验证失败',
},
workspace: {
create: '創建工作空間',
update: '修改工作空間',
@ -639,6 +653,7 @@ export default {
upload_limit_count: "一次只能上傳一個文件",
upload_limit_format: "上傳文件只能是 xls、xlsx格式!",
upload_limit_size: "上傳文件大小不能超過 20MB!",
upload_limit_other_size: "上傳文件大小不能超過",
success: "導入成功!",
importing: "導入中...",
},