This commit is contained in:
chenjianxing 2020-04-09 14:48:08 +08:00
commit d60dc5ec3c
12 changed files with 284 additions and 92 deletions

View File

@ -230,6 +230,19 @@ public class WorkspaceService {
} }
public void addWorkspaceByAdmin(Workspace workspace) { public void addWorkspaceByAdmin(Workspace workspace) {
if (StringUtils.isBlank(workspace.getName())) {
MSException.throwException(Translator.get("workspace_name_is_null"));
}
if (StringUtils.isBlank(workspace.getOrganizationId())) {
MSException.throwException(Translator.get("organization_id_is_null"));
}
WorkspaceExample example = new WorkspaceExample();
example.createCriteria()
.andOrganizationIdEqualTo(workspace.getOrganizationId())
.andNameEqualTo(workspace.getName());
if (workspaceMapper.countByExample(example) > 0) {
MSException.throwException(Translator.get("workspace_name_already_exists"));
}
workspace.setId(UUID.randomUUID().toString()); workspace.setId(UUID.randomUUID().toString());
workspace.setCreateTime(System.currentTimeMillis()); workspace.setCreateTime(System.currentTimeMillis());
workspace.setUpdateTime(System.currentTimeMillis()); workspace.setUpdateTime(System.currentTimeMillis());

View File

@ -17,5 +17,6 @@
"node_deep_limit": "The node depth does not exceed 5 layers!", "node_deep_limit": "The node depth does not exceed 5 layers!",
"no_nodes_message": "No node message", "no_nodes_message": "No node message",
"duplicate_node_ip": "Duplicate IPs", "duplicate_node_ip": "Duplicate IPs",
"only_one_k8s": "Only one K8s can be added" "only_one_k8s": "Only one K8s can be added",
"organization_id_is_null": "Organization ID cannot be null"
} }

View File

@ -17,5 +17,6 @@
"node_deep_limit": "节点深度不超过5层", "node_deep_limit": "节点深度不超过5层",
"no_nodes_message": "没有节点信息", "no_nodes_message": "没有节点信息",
"duplicate_node_ip": "节点 IP 重复", "duplicate_node_ip": "节点 IP 重复",
"only_one_k8s": "只能添加一个 K8s" "only_one_k8s": "只能添加一个 K8s",
"organization_id_is_null": "组织 ID 不能为空"
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<el-col> <el-col>
<api-header-menus/> <ms-api-header-menus/>
<div> <div>
<transition> <transition>
<keep-alive> <keep-alive>
@ -12,11 +12,11 @@
</template> </template>
<script> <script>
import ApiHeaderMenus from "./head/ApiHeaderMenus"; import MsApiHeaderMenus from "./head/ApiHeaderMenus";
export default { export default {
name: "ApiTest", name: "MsApiTest",
components: {ApiHeaderMenus}, components: {MsApiHeaderMenus},
data() { data() {
return { return {
beaseUrl: "api" beaseUrl: "api"

View File

@ -1,96 +1,111 @@
<template> <template>
<div id="menu-bar" v-if="isRouterAlive"> <div id="menu-bar" v-if="isRouterAlive">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router <el-row type="flex">
:default-active='$route.path'> <el-col :span="8">
<el-menu-item :index="'/api/home'"> <el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
{{ $t("i18n.home") }} <el-menu-item :index="'/api/home'">
</el-menu-item> {{ $t("i18n.home") }}
</el-menu-item>
<el-submenu v-if="isCurrentWorkspaceUser" <el-submenu v-if="isCurrentWorkspaceUser"
index="3" popper-class="submenu" v-permission="['test_manager']" > index="3" popper-class="submenu" v-permission="['test_manager']">
<template v-slot:title>{{$t('commons.project')}}</template> <template v-slot:title>{{$t('commons.project')}}</template>
<api-recent-project/> <ms-recent-list :options="projectRecent"/>
<el-divider/> <el-divider/>
<el-menu-item :index="'/api/project/all'"> <ms-show-all :index="'/api/project/all'"/>
<font-awesome-icon :icon="['fa', 'list-ul']"/> <ms-create-button :index="'/api/project/create'" :title="$t('project.create')"/>
<span style="padding-left: 5px;">{{$t('commons.show_all')}}</span> </el-submenu>
</el-menu-item>
<el-menu-item :index="'/api/project/create'">
<el-button type="text">{{$t('project.create')}}</el-button>
</el-menu-item>
</el-submenu>
<el-submenu v-if="isCurrentWorkspaceUser" <el-submenu v-if="isCurrentWorkspaceUser"
index="4" popper-class="submenu" v-permission="['test_manager', 'test_user']"> index="4" popper-class="submenu" v-permission="['test_manager', 'test_user']">
<template v-slot:title>{{$t('commons.test')}}</template> <template v-slot:title>{{$t('commons.test')}}</template>
<api-recent-test/> <ms-recent-list :options="testRecent"/>
<el-divider/> <el-divider/>
<el-menu-item :index="'/api/test/all'"> <ms-show-all :index="'/api/test/all'"/>
<font-awesome-icon :icon="['fa', 'list-ul']"/> <ms-create-button :index="'/api/test/create'" :title="$t('load_test.create')"/>
<span style="padding-left: 5px;">{{$t('commons.show_all')}}</span> <el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>
</el-menu-item> <el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>
<el-menu-item :index="'/api/test/create'"> </el-submenu>
<el-button type="text">{{$t('load_test.create')}}</el-button>
</el-menu-item>
<el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>
<el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>
</el-submenu>
<el-submenu v-if="isCurrentWorkspaceUser" <el-submenu v-if="isCurrentWorkspaceUser"
index="5" popper-class="submenu" v-permission="['test_manager', 'test_user', 'test_viewer']"> index="5" popper-class="submenu" v-permission="['test_manager', 'test_user', 'test_viewer']">
<template v-slot:title>{{$t('commons.report')}}</template> <template v-slot:title>{{$t('commons.report')}}</template>
<api-recent-report/> <ms-recent-list :options="reportRecent"/>
<el-divider/> <el-divider/>
<el-menu-item :index="'/api/report/all'"> <ms-show-all :index="'/api/report/all'"/>
<font-awesome-icon :icon="['fa', 'list-ul']"/> <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>
<span style="padding-left: 5px;">{{$t('commons.show_all')}}</span> </el-submenu>
</el-menu-item> </el-menu>
<el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item> </el-col>
</el-submenu> <el-col :span="8">
<el-row type="flex" justify="center">
<router-link v-if="isCurrentWorkspaceUser" <ms-create-test :show="isCurrentWorkspaceUser" :to="'/api/test/create'"/>
class="header-bottom" :to="'/api/test/create'" v-permission="['test_user','test_manager']"> </el-row>
<el-button type="primary" size="small">{{$t('load_test.create')}}</el-button> </el-col>
</router-link> <el-col :span="8"/>
</el-row>
</el-menu>
</div> </div>
</template> </template>
<script> <script>
import ApiRecentTest from "../../api/test/ApiRecentTest";
import ApiRecentProject from "../../api/project/ApiRecentProject";
import ApiRecentReport from "../../api/report/ApiRecentReport";
import {checkoutCurrentWorkspace} from "../../../../common/utils"; import {checkoutCurrentWorkspace} from "../../../../common/utils";
import MsRecentList from "../../common/head/RecentList";
import MsShowAll from "../../common/head/ShowAll";
import MsCreateButton from "../../common/head/CreateButton";
import MsCreateTest from "../../common/head/CreateTest";
export default { export default {
name: "ApiHeaderMenus", name: "MsApiHeaderMenus",
components: {ApiRecentTest, ApiRecentReport, ApiRecentProject}, components: {MsCreateTest, MsCreateButton, MsShowAll, MsRecentList},
data() { data() {
return { return {
isCurrentWorkspaceUser: false, isCurrentWorkspaceUser: false,
testCaseProjectPath: '', testCaseProjectPath: '',
testEditPath: '', testEditPath: '',
reportViewPath: '', reportViewPath: '',
isRouterAlive: true isRouterAlive: true,
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index: function (item) {
return '/api/' + item.id;
},
router: function (item) {
return {name: 'fucPlan', params: {projectId: item.id, projectName: item.name}}
}
},
testRecent: {
title: this.$t('load_test.recent'),
url: "/api/recent/5",
index: function (item) {
return '/api/test/edit/' + item.id;
}
},
reportRecent: {
title: this.$t('report.recent'),
url: "/api/report/recent/5",
index: function (item) {
return '/api/report/view/' + item.id;
}
}
} }
}, },
watch: { watch: {
'$route'(to, from) { '$route'(to, from) {
let path = to.path; let path = to.path;
// //
if (path.indexOf("/api/test/") >= 0){ if (path.indexOf("/api/test/") >= 0) {
this.testCaseProjectPath = '/api/test/' + this.$route.params.projectId; this.testCaseProjectPath = '/api/test/' + this.$route.params.projectId;
this.reload(); this.reload();
} }
if (path.indexOf("/api/test/edit/") >= 0){ if (path.indexOf("/api/test/edit/") >= 0) {
this.testEditPath = '/api/test/edit/' + this.$route.params.testId; this.testEditPath = '/api/test/edit/' + this.$route.params.testId;
this.reload(); this.reload();
} }
if (path.indexOf("/api/report/view/") >= 0){ if (path.indexOf("/api/report/view/") >= 0) {
this.reportViewPath = '/api/report/view/' + this.$route.params.reportId; this.reportViewPath = '/api/report/view/' + this.$route.params.reportId;
this.reload(); this.reload();
} }
@ -100,7 +115,7 @@
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace(); this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
}, },
methods: { methods: {
reload () { reload() {
this.isRouterAlive = false; this.isRouterAlive = false;
this.$nextTick(function () { this.$nextTick(function () {
this.isRouterAlive = true; this.isRouterAlive = true;
@ -112,7 +127,6 @@
</script> </script>
<style> <style>
.header-menu.el-menu--horizontal > li { .header-menu.el-menu--horizontal > li {
height: 39px; height: 39px;
line-height: 40px; line-height: 40px;
@ -125,10 +139,6 @@
color: dimgray; color: dimgray;
} }
.header-bottom {
line-height: 40px;
margin-left: 20%;
}
</style> </style>
@ -136,8 +146,14 @@
.el-divider--horizontal { .el-divider--horizontal {
margin: 0; margin: 0;
} }
.el-menu.el-menu--horizontal {
border-bottom: none;
}
#menu-bar { #menu-bar {
border-bottom: 1px solid #E6E6E6; border-bottom: 1px solid #E6E6E6;
background-color: #FFF;
} }
.blank_item { .blank_item {

View File

@ -1,15 +1,14 @@
<template> <template>
<el-menu router menu-trigger="click" :default-active="$route.path"> <div>
<div class="recent-text"> <div class="recent-text">
<i class="el-icon-time"/> <i class="el-icon-time"/>
{{$t('project.recent')}} <span>{{$t('project.recent')}}</span>
</div> </div>
<el-menu-item :key="p.id" v-for="p in recentProjects" <el-menu-item :key="p.id" v-for="p in recentProjects"
:index="'/api/' + p.id" :route="{name:'fucPlan', params:{projectId:p.id, projectName:p.name}}"> :index="'/api/' + p.id" :route="{name:'fucPlan', params:{projectId:p.id, projectName:p.name}}">
{{ p.name }} <span class="title">{{ p.name }}</span>
</el-menu-item> </el-menu-item>
</el-menu> </div>
</template> </template>
<script> <script>
@ -20,7 +19,6 @@
export default { export default {
name: "ApiRecentProject", name: "ApiRecentProject",
mounted() { mounted() {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) { if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get('/project/recent/5', (response) => { this.$get('/project/recent/5', (response) => {
this.recentProjects = response.data; this.recentProjects = response.data;
@ -38,7 +36,18 @@
<style scoped> <style scoped>
.recent-text { .recent-text {
padding-left: 10%; padding: 0 10px;
margin-top: -5px;
line-height: 36px;
color: #777777; color: #777777;
background-color: #F5F5F5;
}
.recent-text span {
padding-left: 6px;
}
.title {
padding-left: 20px;
} }
</style> </style>

View File

@ -1,14 +1,14 @@
<template> <template>
<el-menu router menu-trigger="click" :default-active="$route.path"> <div>
<div class="recent-text"> <div class="recent-text">
<i class="el-icon-time"/> <i class="el-icon-time"/>
{{$t('load_test.recent')}} <span>{{$t('load_test.recent')}}</span>
</div> </div>
<el-menu-item :key="p.id" v-for="p in recentReports" <el-menu-item :key="p.id" v-for="p in recentReports"
:index="'/api/report/view/' + p.id" :route="{path: '/api/report/view/' + p.id}"> :index="'/api/report/view/' + p.id" :route="{path: '/api/report/view/' + p.id}">
{{ p.name }} <span class="title">{{ p.name }}</span>
</el-menu-item> </el-menu-item>
</el-menu> </div>
</template> </template>
<script> <script>
@ -37,7 +37,18 @@
<style scoped> <style scoped>
.recent-text { .recent-text {
padding-left: 10%; padding: 0 10px;
margin-top: -5px;
line-height: 36px;
color: #777777; color: #777777;
background-color: #F5F5F5;
}
.recent-text span {
padding-left: 6px;
}
.title {
padding-left: 20px;
} }
</style> </style>

View File

@ -0,0 +1,22 @@
<template>
<el-menu-item :index="this.index">
<el-button type="text" class="create-button">{{this.title}}</el-button>
</el-menu-item>
</template>
<script>
export default {
name: "MsCreateButton",
props: {
index: String,
title: String,
click: Function
}
}
</script>
<style scoped>
.create-button {
padding-left: 18px;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<router-link v-if="this.show" class="create-test" :to="this.to" v-permission="this.permission">
<el-button type="primary" size="small">{{this.title}}</el-button>
</router-link>
</template>
<script>
export default {
name: "MsCreateTest",
props: {
show: Boolean,
to: String,
title: {
type: String,
default: function () {
return this.$t('load_test.create');
}
},
permission: {
type: Array,
default: function () {
return ['test_user', 'test_manager'];
}
}
}
}
</script>
<style scoped>
.create-test {
line-height: 38px;
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<div>
<div class="recent-text">
<i class="el-icon-time"/>
<span>{{options.title}}</span>
</div>
<el-menu-item :key="i.id" v-for="i in items"
:index="getIndex(i)" :route="getRouter(i)">
<span class="title">{{ i.name }}</span>
</el-menu-item>
</div>
</template>
<script>
import {hasRoles} from "../../../../common/utils";
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER} from "../../../../common/constants";
export default {
name: "MsRecentList",
props: {
options: Object
},
mounted() {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get(this.options.url, (response) => {
this.items = response.data;
});
}
},
data() {
return {
items: []
}
},
computed: {
getIndex: function () {
return function (item) {
return this.options.index(item);
}
},
getRouter: function () {
return function (item) {
return this.options.router(item);
}
}
}
}
</script>
<style scoped>
.recent-text {
padding: 0 10px;
margin-top: -5px;
line-height: 36px;
color: #777777;
background-color: #F5F5F5;
}
.recent-text span {
padding-left: 6px;
}
.title {
padding-left: 20px;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<el-menu-item :index="this.index">
<font-awesome-icon :icon="['fa', 'list-ul']"/>
<span>{{$t('commons.show_all')}}</span>
</el-menu-item>
</template>
<script>
export default {
name: "MsShowAll",
props: {
index: String
}
}
</script>
<style scoped>
svg + span {
padding-left: 5px;
}
</style>

View File

@ -244,7 +244,6 @@
data() { data() {
return { return {
result: {}, result: {},
testLoading: false,
createVisible: false, createVisible: false,
infoList: [], infoList: [],
updateVisible: false, updateVisible: false,
@ -386,11 +385,13 @@
}); });
}, },
createTestResourcePool(createTestResourcePoolForm) { createTestResourcePool(createTestResourcePoolForm) {
if (this.result.loading) {
return;
}
this.$refs[createTestResourcePoolForm].validate(valide => { this.$refs[createTestResourcePoolForm].validate(valide => {
if (valide) { if (valide) {
let vri = this.validateResourceInfo(); let vri = this.validateResourceInfo();
if (vri.validate) { if (vri.validate) {
this.testLoading = true;
this.convertSubmitResources(); this.convertSubmitResources();
this.result = this.$post("/testresourcepool/add", this.form, () => { this.result = this.$post("/testresourcepool/add", this.form, () => {
this.$message({ this.$message({
@ -399,14 +400,12 @@
}, },
this.createVisible = false, this.createVisible = false,
this.initTableData()); this.initTableData());
this.testLoading = false;
}); });
} else { } else {
this.$message({ this.$message({
type: 'warning', type: 'warning',
message: vri.msg message: vri.msg
}); });
this.testLoading = false;
return false; return false;
} }
@ -428,9 +427,11 @@
this.form.resources = resources; this.form.resources = resources;
}, },
updateTestResourcePool(updateTestResourcePoolForm) { updateTestResourcePool(updateTestResourcePoolForm) {
if (this.result.loading) {
return;
}
this.$refs[updateTestResourcePoolForm].validate(valide => { this.$refs[updateTestResourcePoolForm].validate(valide => {
if (valide) { if (valide) {
this.testLoading = true;
let vri = this.validateResourceInfo(); let vri = this.validateResourceInfo();
if (vri.validate) { if (vri.validate) {
this.convertSubmitResources(); this.convertSubmitResources();
@ -442,20 +443,18 @@
this.updateVisible = false, this.updateVisible = false,
this.initTableData(), this.initTableData(),
self.loading = false); self.loading = false);
this.testLoading = false;
}); });
} else { } else {
this.$message({ this.$message({
type: 'warning', type: 'warning',
message: vri.msg message: vri.msg
}); });
this.testLoading = false;
return false; return false;
} }
} else { } else {
return false; return false;
} }
}) });
}, },
closeFunc() { closeFunc() {
this.form = {}; this.form = {};