feat(系统设置): 资源池增加并发限制页面未联调

This commit is contained in:
xinxin.wu 2024-10-09 15:29:40 +08:00 committed by Craftsman
parent e7d0bfc788
commit 79ae95ec07
10 changed files with 393 additions and 23 deletions

View File

@ -150,7 +150,7 @@
<MsTagGroup
:is-string-tag="item.isStringTag"
:tag-list="record[item.dataIndex as string]"
type="primary"
:type="item.tagPrimary || 'primary'"
theme="outline"
show-table
:tag-position="item.tagPosition"

View File

@ -1,3 +1,5 @@
import { TagType } from '@/components/pure/ms-tag/ms-tag.vue';
import type { TableQueryParams } from '@/models/common';
import { ColumnEditTypeEnum, SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -72,6 +74,7 @@ export interface MsTableColumnData extends TableColumnData {
filterItem?: any;
// 是否标签编辑列
allowEditTag?: boolean;
tagPrimary?: TagType;
// 自定义属性
[key: string]: any;
}

View File

@ -19,6 +19,7 @@ export enum TableKeyEnum {
API_SCENARIO = 'apiScenario',
SYSTEM_USER = 'systemUser',
SYSTEM_RESOURCEPOOL = 'systemResourcePool',
SYSTEM_RESOURCE_POOL_CAPACITY = 'systemResourcePoolCapacity',
SYSTEM_AUTH = 'systemAuth',
SYSTEM_ORGANIZATION = 'systemOrganization',
SYSTEM_PROJECT = 'systemProject',

View File

@ -3,6 +3,7 @@ export interface NodesListItem {
ip: string;
port: string;
concurrentNumber: number;
singleTaskConcurrentNumber: number; // 单个任务最大并发数
}
// 应用组织id和name映射对象
@ -18,6 +19,7 @@ export interface TestResourceDTO {
token: string; // k8s token
namespace: string; // k8s 命名空间
concurrentNumber: number; // k8s 最大并发数
singleTaskConcurrentNumber: number; // 单个任务最大并发数
podThreads: number; // k8s 单pod最大线程数
jobDefinition: string; // k8s job自定义模板
deployName: string; // k8s api测试部署名称

View File

@ -0,0 +1,218 @@
<template>
<MsDrawer
v-model:visible="innerVisible"
:title="props.activeRecord?.name"
:width="800"
:footer="false"
no-content-padding
unmount-on-close
>
<template #tbutton>
<div class="flex items-center gap-[16px]">
<div class="count-resources">
{{ t('system.resourcePool.concurrentNumber') }}
<span class="capacity-count">100</span>
</div>
<div class="count-resources">
{{ t('system.resourcePool.remainingConcurrency') }}
<span class="capacity-count">100</span>
</div>
</div>
</template>
<!-- TODO 等待联调 -->
<div class="flex items-center justify-between p-[16px]">
<div class="flex items-center gap-[8px]">
<a-select
v-model="selectedNode"
class="mr-[16px] w-[200px]"
:placeholder="t('common.pleaseSelect')"
allow-clear
>
<template #prefix>
<div class="text-[var(--color-text-brand)]">{{ t('system.resourcePool.capacityNode') }}</div>
</template>
<template #tree-slot-title="node">
<a-tooltip :content="`${node.name}`" position="tl">
<div class="one-line-text w-[180px]">{{ node.name }}</div>
</a-tooltip>
</template>
</a-select>
<CapacityProgress :name="t('system.resourcePool.memory')" :percent="0.3" color="rgb(var(--link-6))" />
<CapacityProgress name="CPU" :percent="0.3" color="rgb(var(--success-6))" />
</div>
<MsTag no-margin size="large" :tooltip-disabled="true" class="cursor-pointer" theme="outline" @click="searchList">
<MsIcon class="text-[16px] text-[var(color-text-4)]" :size="32" type="icon-icon_reset_outlined" />
</MsTag>
</div>
<div class="p-[16px]">
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #[FilterSlotNameEnum.TEST_PLAN_REPORT_EXEC_STATUS]="{ filterContent }">
<ExecStatus :status="filterContent.value" />
</template>
<template #execStatus="{ record }">
<ExecStatus :status="record.execStatus" />
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_STATUS]="{ filterContent }">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
</template>
<template #status="{ record }">
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="record.status" />
</template>
</ms-base-table>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import CapacityProgress from './capacityProgress.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import ExecStatus from '@/views/test-plan/report/component/execStatus.vue';
import { useI18n } from '@/hooks/useI18n';
import { useTableStore } from '@/store';
import type { ResourcePoolItem } from '@/models/setting/resourcePool';
import { ReportExecStatus } from '@/enums/apiEnum';
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const { t } = useI18n();
const tableStore = useTableStore();
const props = defineProps<{
activeRecord?: ResourcePoolItem;
}>();
const innerVisible = defineModel<boolean>('visible', {
required: true,
});
const ExecStatusList = computed(() => {
return Object.values(ReportExecStatus).map((e) => {
return {
value: e,
key: e,
};
});
});
const statusList = computed(() => {
return Object.keys(ReportStatus).map((key) => {
return {
value: key,
label: t(ReportStatus[key].label),
};
});
});
const columns: MsTableColumn = [
{
title: 'system.resourcePool.taskID',
slotName: 'ID',
dataIndex: 'id',
showTooltip: true,
width: 200,
columnSelectorDisabled: true,
},
{
title: 'system.resourcePool.taskName',
slotName: 'taskName',
dataIndex: 'enable',
showDrag: true,
},
{
title: 'system.resourcePool.useCaseName',
slotName: 'useCaseName',
dataIndex: 'useCaseName',
showTooltip: true,
width: 150,
showDrag: true,
},
{
title: 'system.resourcePool.tableColumnStatus',
dataIndex: 'status',
slotName: 'status',
filterConfig: {
options: ExecStatusList.value,
filterSlotName: FilterSlotNameEnum.TEST_PLAN_REPORT_EXEC_STATUS,
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'common.executionResult',
slotName: 'execStatus',
dataIndex: 'execStatus',
filterConfig: {
options: statusList.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_STATUS,
},
showDrag: true,
},
{
title: '',
slotName: 'action',
dataIndex: 'operation',
fixed: 'right',
width: 50,
showInTable: true,
showDrag: false,
},
];
const { propsRes, propsEvent, loadList, setKeyword } = useTable(undefined, {
tableKey: TableKeyEnum.SYSTEM_RESOURCE_POOL_CAPACITY,
scroll: { y: 'auto' },
selectable: false,
showSetting: true,
heightUsed: 310,
showSelectAll: false,
});
const selectedNode = ref<string>('');
async function searchList() {
loadList();
}
await tableStore.initColumn(TableKeyEnum.SYSTEM_RESOURCE_POOL_CAPACITY, columns, 'drawer');
</script>
<style scoped lang="less">
.count-resources {
font-size: 16px;
color: var(--color-text-4);
@apply font-normal;
.capacity-count {
color: var(--color-text-1);
@apply font-medium;
}
}
.progress-capacity {
padding: 4px 8px;
min-width: 116px;
height: 32px;
border-radius: 4px;
background-color: var(--color-text-n9) !important;
@apply flex flex-col;
.capacity-attr {
font-size: 12px;
color: var(--color-text-4);
@apply flex items-center justify-between;
.capacity-value {
color: var(--color-text-1);
}
}
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<!-- TODO 等待联调 -->
<div class="progress-capacity">
<div class="capacity-attr">
<span>{{ props.name }}</span>
<span class="capacity-value"> {{ props.percent * 100 }} %</span>
</div>
<a-progress :percent="props.percent" :show-text="false" :color="props.color" />
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
name: string;
percent: number;
color: string;
}>();
</script>
<style scoped lang="less">
.progress-capacity {
padding: 4px 8px;
min-width: 116px;
height: 32px;
border-radius: 4px;
background-color: var(--color-text-n9) !important;
@apply flex flex-col;
.capacity-attr {
font-size: 12px;
color: var(--color-text-4);
@apply flex items-center justify-between;
.capacity-value {
color: var(--color-text-1);
@apply font-medium;
}
}
}
</style>

View File

@ -432,12 +432,15 @@
uiGrid: '',
girdConcurrentNumber: 1,
podThreads: 1,
concurrentNumber: 1,
concurrentNumber: 10,
// TODO
singleTaskConcurrentNumber: 3,
nodesList: [
{
ip: '',
port: '',
concurrentNumber: 1,
concurrentNumber: 10,
singleTaskConcurrentNumber: 3,
},
],
ip: '',
@ -474,6 +477,13 @@
return 10;
});
const maxSingleTaskConcurrentNumber = computed(() => {
if (isXpack.value) {
return 9999999;
}
return 3;
});
onBeforeMount(async () => {
orgOptions.value = await getSystemOrgOption();
if (!isXpack.value) {
@ -489,7 +499,8 @@
loading.value = true;
const res = await getPoolInfo(route.query.id);
const { testResourceReturnDTO } = res;
const { girdConcurrentNumber, podThreads, concurrentNumber, orgIdNameMap } = testResourceReturnDTO;
const { girdConcurrentNumber, podThreads, concurrentNumber, orgIdNameMap, singleTaskConcurrentNumber } =
testResourceReturnDTO;
form.value = {
...res,
addType: 'single',
@ -499,7 +510,8 @@
...testResourceReturnDTO,
girdConcurrentNumber: girdConcurrentNumber || 1,
podThreads: podThreads || 1,
concurrentNumber: concurrentNumber || 1,
concurrentNumber: concurrentNumber || 10,
singleTaskConcurrentNumber: singleTaskConcurrentNumber || 3,
orgIds: orgIdNameMap?.map((e) => e.id) || [],
},
};
@ -611,7 +623,27 @@
min: 1,
max: maxConcurrentNumber.value,
tooltip: licenseStore.hasLicense() ? '' : t('system.resourcePool.concurrentNumberMinToolTip'),
defaultValue: 1,
defaultValue: 10,
},
{
field: 'singleTaskConcurrentNumber',
type: 'inputNumber',
label: 'system.resourcePool.singleTaskConcurrentNumber',
rules: [
{ required: true, message: t('system.resourcePool.singleConcurrentNumberRequired') },
{
validator: (val, cb) => {
if (val <= 0) {
cb(t('system.resourcePool.singleConcurrentNumberMin'));
}
},
},
],
placeholder: 'system.resourcePool.singleConcurrentNumberPlaceholder',
min: 1,
max: maxSingleTaskConcurrentNumber.value,
tooltip: licenseStore.hasLicense() ? '' : t('system.resourcePool.singleConcurrentNumberMinToolTip'),
defaultValue: 3,
},
]);
@ -630,11 +662,11 @@
let res = '';
for (let i = 0; i < nodesList?.length; i++) {
const node = nodesList[i];
// ipportmonitorconcurrentNumber
// ipportmonitorconcurrentNumbersingleTaskConcurrentNumber
if (!Object.values(node).every((e) => isEmpty(e))) {
res += `${node.ip},${node.port === undefined ? '' : node.port},${
node.concurrentNumber === undefined ? '' : node.concurrentNumber
}\r`;
},${node.singleTaskConcurrentNumber === undefined ? '' : node.singleTaskConcurrentNumber}\r`;
}
}
editorContent.value = res;
@ -665,6 +697,7 @@
ip: line[0],
port: line[1],
concurrentNumber: Number(line[2]),
singleTaskConcurrentNumber: Number(line[3]),
};
if (i === 0) {
// concurrentNumber

View File

@ -1,23 +1,34 @@
<template>
<MsCard :loading="loading" simple>
<MsTrialAlert :tip-content="t('system.authorized.resourcePoolTipContent')" />
<div class="mb-4 flex items-center justify-between">
<a-button v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+ADD']" v-xpack type="primary" @click="addPool">
{{ t('system.resourcePool.createPool') }}
</a-button>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.resourcePool.searchPool')"
class="w-[230px]"
allow-clear
@search="searchPool"
@press-enter="searchPool"
@clear="searchPool"
></a-input-search>
<div class="mb-[16px]">
<MsAdvanceFilter
v-model:keyword="keyword"
:filter-config-list="filterConfigList"
:search-placeholder="t('system.resourcePool.searchPool')"
@keyword-search="searchPool()"
@adv-search="searchPool"
@refresh="searchPool"
>
<template #left>
<a-button v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+ADD']" v-xpack type="primary" @click="addPool">
{{ t('system.resourcePool.createPool') }}
</a-button>
</template>
</MsAdvanceFilter>
</div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #name="{ record }">
<div class="flex w-full items-center justify-start gap-[8px]">
<a-tooltip :content="t('system.resourcePool.viewCapacityInfo')" :mouse-enter-delay="300" position="bottom">
<MsIcon
type="icon-icon_pie_filled"
class="cursor-pointer text-[rgb(var(--primary-5))]"
size="16"
@click="capacityDetail(record)"
/>
</a-tooltip>
<a-button
type="text"
class="px-0"
@ -51,6 +62,9 @@
</a-tooltip>
</div>
</template>
<template #lastConcurrentNumber="{ record }">
{{ record.lastConcurrentNumber || 0 }}
</template>
<template #action="{ record }">
<MsButton v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+UPDATE']" @click="editPool(record)">
{{ t('system.resourcePool.editPool') }}
@ -101,6 +115,7 @@
:default-val="activePool?.testResourceReturnDTO.jobDefinition || ''"
read-only
/>
<CapacityDrawer v-model:visible="showCapacityDrawer" :active-record="activeRecord" />
</template>
<script setup lang="ts">
@ -110,6 +125,8 @@
import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterResult } from '@/components/pure/ms-advance-filter/type';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import type { Description } from '@/components/pure/ms-description/index.vue';
@ -119,6 +136,7 @@
import useTable from '@/components/pure/ms-table/useTable';
import MsTag, { TagType, Theme } from '@/components/pure/ms-tag/ms-tag.vue';
import MsTrialAlert from '@/components/business/ms-trial-alert/index.vue';
import CapacityDrawer from './components/capacityDrawer.vue';
import JobTemplateDrawer from './components/jobTemplateDrawer.vue';
import { delPoolInfo, getPoolInfo, getPoolList, togglePoolStatus } from '@/api/modules/setting/resourcePool';
@ -153,6 +171,29 @@
slotName: 'enable',
dataIndex: 'enable',
},
{
title: 'system.resourcePool.concurrentNumber',
dataIndex: 'maxConcurrentNumber',
showTooltip: true,
width: 150,
},
{
title: 'system.resourcePool.remainingConcurrency',
slotName: 'lastConcurrentNumber',
dataIndex: 'lastConcurrentNumber',
showTooltip: true,
width: 150,
},
{
title: 'system.resourcePool.orgRange',
slotName: 'orgNames',
dataIndex: 'orgNames',
showInTable: true,
showDrag: true,
isTag: true,
isStringTag: true,
tagPrimary: 'default',
},
{
title: 'common.desc',
dataIndex: 'description',
@ -194,6 +235,7 @@
});
const keyword = ref('');
const filterConfigList = ref<FilterFormItem[]>([]);
onMounted(async () => {
setKeyword(keyword.value);
@ -452,6 +494,14 @@
drawerLoading.value = false;
}
}
const showCapacityDrawer = ref<boolean>(false);
const activeRecord = ref<ResourcePoolItem>();
// TODO
//
function capacityDetail(record: ResourcePoolItem) {
showCapacityDrawer.value = true;
activeRecord.value = record;
}
onMounted(() => {
if (route.query.id) {

View File

@ -88,7 +88,7 @@ export default {
'system.resourcePool.concurrentNumberPlaceholder': 'Please enter the maximum number of concurrency',
'system.resourcePool.nodeResourceRequired': 'Please fill in the Node resources added in batches correctly',
'system.resourcePool.nodeConfigEditorTip':
'Writing format: IP, Port, maximum concurrent number; such as 192.168.1.52,8082,1',
'Writing format: IP, Port, maximum concurrent number; such as 192.168.1.52,8082,10,3',
'system.resourcePool.testResourceDTO.ip': 'IP Address/Domain Name',
'system.resourcePool.testResourceDTO.ipRequired': 'Please fill in IP address / domain name',
'system.resourcePool.testResourceDTO.ipPlaceholder': 'example.com',
@ -131,4 +131,17 @@ export default {
'system.resourcePool.concurrentNumberMinToolTip':
'The maximum number of concurrent requests for a single node in the Community version is 10. If you want more, you can apply',
'system.resourcePool.notExit': 'Resource pool does not exist',
'system.resourcePool.remainingConcurrency': 'Remaining Concurrency',
'system.resourcePool.taskID': 'Task ID',
'system.resourcePool.taskName': 'Task name',
'system.resourcePool.useCaseName': 'UseCase Name',
'system.resourcePool.capacityNode': 'Node',
'system.resourcePool.viewCapacityInfo': 'Click to view capacity information',
'system.resourcePool.memory': 'Memory',
'system.resourcePool.singleTaskConcurrentNumber': 'Maximum concurrency of a single task',
'system.resourcePool.singleConcurrentNumberRequired': 'A single task maximum concurrency can not be empty',
'system.resourcePool.singleConcurrentNumberMin': 'A single task maximum concurrency can not be empty',
'system.resourcePool.singleConcurrentNumberPlaceholder': 'Enter the maximum number of concurrent tasks',
'system.resourcePool.singleConcurrentNumberMinToolTip':
'Community edition a single task maximum concurrency of 3, if you need a larger concurrency, can apply for',
};

View File

@ -84,7 +84,7 @@ export default {
'system.resourcePool.concurrentNumberMin': '最大并发数须大于等于 1',
'system.resourcePool.concurrentNumberPlaceholder': '请输入最大并发数',
'system.resourcePool.nodeResourceRequired': '请正确填写批量添加的 Node 资源',
'system.resourcePool.nodeConfigEditorTip': '书写格式IP,Port,最大并发数;如 192.168.1.52,8082,1',
'system.resourcePool.nodeConfigEditorTip': '书写格式IP,Port,最大并发数;如 192.168.1.52,8082,10,3',
'system.resourcePool.testResourceDTO.ip': 'IP 地址/域名',
'system.resourcePool.testResourceDTO.ipRequired': 'IP 地址/域名不能为空',
'system.resourcePool.testResourceDTO.ipPlaceholder': 'example.com',
@ -122,4 +122,16 @@ export default {
'system.resourcePool.supportMultiResource': '社区版仅支持 1 个资源,如需添加更多资源,可申请',
'system.resourcePool.concurrentNumberMinToolTip': '社区版单个节点最大并发数为 10如需更大并发数可申请',
'system.resourcePool.notExit': '资源池不存在',
'system.resourcePool.remainingConcurrency': '剩余并发数',
'system.resourcePool.taskID': '任务ID',
'system.resourcePool.taskName': '任务名称',
'system.resourcePool.useCaseName': '用例名称',
'system.resourcePool.capacityNode': '节点',
'system.resourcePool.viewCapacityInfo': '点击可查看容量信息',
'system.resourcePool.memory': '内存',
'system.resourcePool.singleTaskConcurrentNumber': '单个任务最大并发数',
'system.resourcePool.singleConcurrentNumberRequired': '单个任务最大并发数不能为空',
'system.resourcePool.singleConcurrentNumberMin': '单个任务最大并发数须大于等于 1',
'system.resourcePool.singleConcurrentNumberPlaceholder': '请输入单个任务最大并发数',
'system.resourcePool.singleConcurrentNumberMinToolTip': '社区版单个任务最大并发数为 3如需更大并发数可申请',
};