Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frontend): 工具箱支持资源池协议变更_扩容接入层 TencentBlueKing#8076
Browse files Browse the repository at this point in the history
# Reviewed, transaction id: 28906
JustaCattt committed Jan 9, 2025
1 parent 3661f99 commit f5b4808
Showing 7 changed files with 851 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
<!--
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
*
* Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License athttps://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
* the specific language governing permissions and limitations under the License.
-->

<template>
<SmartAction>
<BkAlert
class="mb-20"
closable
:title="t('扩容接入层:增加集群的Proxy数量')" />
<BkForm
v-model="formData"
class="mb-20"
form-type="vertical">
<EditableTable
ref="table"
class="mb-20"
:model="formData.tableData">
<EditableTableRow
v-for="(item, index) in formData.tableData"
:key="index">
<ClusterColumn
v-model="item.cluster"
:selected="selected"
@batch-edit="handleBatchEdit" />
<Column
field="cluster.role"
:label="t('扩容节点类型')"
:min-width="200"
required>
<Select
v-model="item.cluster.role"
:input-search="false"
:list="getNodeTypeOptions(item.cluster)" />
</Column>
<SpecColumn
v-model="item.specId"
:cluster="item.cluster" />
<Column
field="hostType"
:label="t('主机选择方式')"
:min-width="200">
<Select
v-model="item.hostType"
:list="hostTypeOptions" />
</Column>
<Column
field="count"
:label="t('扩容数量(台)')"
:min-width="200"
required>
<Input
v-model="item.count"
:max="37 - item.cluster.mnt_count"
:min="1"
type="number" />
</Column>
<OperationColumn
v-model:table-data="formData.tableData"
:create-row-method="createTableRow" />
</EditableTableRow>
</EditableTable>
<TicketRemark v-model="formData.remark" />
</BkForm>
<template #action>
<BkButton
class="mr-8 w-88"
:loading="isSubmitting"
theme="primary"
@click="handleSubmit">
{{ t('提交') }}
</BkButton>
<DbPopconfirm
:confirm-handler="handleReset"
:content="t('重置将会情况当前填写的所有内容_请谨慎操作')"
:title="t('确认重置页面')">
<BkButton
class="ml8 w-88"
:disabled="isSubmitting">
{{ t('重置') }}
</BkButton>
</DbPopconfirm>
</template>
</SmartAction>
</template>
<script lang="ts" setup>
import { reactive, useTemplateRef } from 'vue';
import { useI18n } from 'vue-i18n';

import TendbClusterModel from '@services/model/tendbcluster/tendbcluster';

import { useCreateTicket } from '@hooks';

import { TicketTypes } from '@common/const';

import EditableTable, { Column, Input, Row as EditableTableRow, Select } from '@components/editable-table/Index.vue';

import OperationColumn from '@views/db-manage/common/toolbox-field/column/operation-column/Index.vue';
import TicketRemark from '@views/db-manage/common/toolbox-field/form-item/ticket-remark/Index.vue';

import ClusterColumn from './components/ClusterColumn.vue';
import SpecColumn from './components/spec-column/Index.vue';

interface RowData {
cluster: {
id: number;
master_domain: string;
bk_cloud_id: number;
role: string;
master_spec_ids: number[];
slave_spec_ids: number[];
mnt_count: number;
};
specId: number;
hostType: string;
count: string;
}

const { t } = useI18n();
const tableRef = useTemplateRef('table');

const createTableRow = (data = {} as Partial<RowData>) => ({
cluster: data.cluster || {
id: 0,
master_domain: '',
bk_cloud_id: 0,
role: '',
master_spec_ids: [],
slave_spec_ids: [],
mnt_count: 0,
},
specId: data.specId || 0,
hostType: data.cluster ? 'auto' : '',
count: data.count || '',
});

const defaultData = () => ({
tableData: [createTableRow()],
remark: '',
});

const formData = reactive(defaultData());
const selected = computed(() => formData.tableData.filter((item) => item.cluster.id).map((item) => item.cluster));
const selectedMap = computed(() => Object.fromEntries(selected.value.map((cur) => [cur.master_domain, true])));

const hostTypeOptions = [
{
label: t('资源池自动匹配'),
value: 'auto',
},
];

const { run: createTicketRun, loading: isSubmitting } = useCreateTicket<{
ip_source: 'resource_pool';
infos: {
cluster_id: number;
add_spider_role: string;
resource_spec: {
spider_ip_list: {
spec_id: number;
count: number;
};
};
}[];
}>(TicketTypes.TENDBCLUSTER_SPIDER_ADD_NODES);

const handleSubmit = async () => {
const result = await tableRef.value!.validate();
if (!result) {
return;
}
createTicketRun({
details: {
ip_source: 'resource_pool',
infos: formData.tableData.map((item) => ({
cluster_id: item.cluster.id,
add_spider_role: item.cluster.role,
resource_spec: {
spider_ip_list: {
spec_id: item.specId,
count: Number(item.count),
},
},
})),
},
remark: formData.remark,
});
};

const handleReset = () => {
Object.assign(formData, defaultData());
};

const handleBatchEdit = (list: TendbClusterModel[]) => {
const dataList = list.reduce<RowData[]>((acc, item) => {
if (!selectedMap.value[item.master_domain]) {
acc.push(
createTableRow({
cluster: {
id: item.id,
master_domain: item.master_domain,
bk_cloud_id: item.bk_cloud_id,
role:
// eslint-disable-next-line no-nested-ternary
item.spider_master.length > 0 ? 'spider_master' : item.spider_slave.length > 0 ? 'spider_slave' : '',
master_spec_ids: item.spider_master.map((item) => item.spec_config?.id),
slave_spec_ids: item.spider_slave.map((item) => item.spec_config?.id),
mnt_count: item.spider_mnt.length,
},
}),
);
}
return acc;
}, []);
formData.tableData = [...(selected.value.length ? formData.tableData : []), ...dataList];
};

const getNodeTypeOptions = (cluster: RowData['cluster']) => {
const list = [];
if (cluster.master_spec_ids.length) {
list.push({
value: 'spider_master',
label: 'Master',
});
}
if (cluster.slave_spec_ids.length) {
list.push({
value: 'spider_slave',
label: 'Slave',
});
}
return list;
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!--
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
*
* Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License athttps://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
* the specific language governing permissions and limitations under the License.
-->

<template>
<Component :is="components[page]" />
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';

import Page2 from '@views/db-manage/common/create-ticket-success/Index.vue';

import Page1 from './Create.vue';

const route = useRoute();

const components = {
create: Page1,
success: Page2,
};

const page = computed(() => (route.params.page as keyof typeof components) || 'create');
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<!--
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
*
* Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License athttps://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
* the specific language governing permissions and limitations under the License.
-->

<template>
<Column
:append-rules="rules"
field="cluster.master_domain"
fixed="left"
:label="t('目标集群')"
:loading="loading"
:min-width="200"
required>
<template #headAppend>
<span
v-bk-tooltips="t('批量选择')"
class="batch-host-select"
@click="handleShowSelector">
<DbIcon type="batch-host-select" />
</span>
</template>
<Input
v-model="modelValue.master_domain"
:placeholder="t('请输入集群域名')"
@change="handleInputChange" />
</Column>
<ClusterSelector
v-model:is-show="showSelector"
:cluster-types="[ClusterTypes.TENDBCLUSTER]"
:selected="selectedClusters"
support-offline-data
@change="handleSelectorChange" />
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import { useRequest } from 'vue-request';

import TendbClusterModel from '@services/model/tendbcluster/tendbcluster';
import { filterClusters } from '@services/source/dbbase';

import { ClusterTypes } from '@common/const';
import { domainRegex } from '@common/regex';

import ClusterSelector from '@components/cluster-selector/Index.vue';
import { Column, Input } from '@components/editable-table/Index.vue';

interface Props {
selected: {
id: number;
master_domain: string;
}[];
}

interface Emits {
(e: 'batch-edit', list: TendbClusterModel[]): void;
}

const props = defineProps<Props>();

const emits = defineEmits<Emits>();

const modelValue = defineModel<{
id?: number;
master_domain: string;
bk_cloud_id: number;
role: string;
master_spec_ids: number[];
slave_spec_ids: number[];
mnt_count: number;
}>({
default: () => ({
id: undefined,
master_domain: '',
bk_cloud_id: 0,
role: '',
master_spec_ids: [],
slave_spec_ids: [],
mnt_count: 0,
}),
});

const { t } = useI18n();

const showSelector = ref(false);
const selectedClusters = computed<Record<string, TendbClusterModel[]>>(() => ({
[ClusterTypes.TENDBCLUSTER]: props.selected.map(
(item) =>
({
id: item.id,
master_domain: item.master_domain,
}) as TendbClusterModel,
),
}));

const rules = [
{
validator: (value: string) => domainRegex.test(value),
message: t('集群域名格式不正确'),
trigger: 'change',
},
{
validator: (value: string) => props.selected.filter((item) => item.master_domain === value).length < 2,
message: t('目标集群重复'),
trigger: 'blur',
},
{
validator: () => {
if (!modelValue.value.master_domain) {
return true;
}
return Boolean(modelValue.value.id);
},
message: t('目标集群不存在'),
trigger: 'blur',
},
];

const { run: queryCluster, loading } = useRequest(filterClusters<TendbClusterModel>, {
manual: true,
onSuccess: (data) => {
modelValue.value.id = undefined;
if (data.length) {
const [currentCluster] = data;
modelValue.value = {
id: currentCluster.id,
master_domain: currentCluster.master_domain,
bk_cloud_id: currentCluster.bk_cloud_id,
role:
// eslint-disable-next-line no-nested-ternary
currentCluster.spider_master.length > 0
? 'spider_master'
: currentCluster.spider_slave.length > 0
? 'spider_slave'
: '',
master_spec_ids: currentCluster.spider_master.map((item) => item.spec_config.id),
slave_spec_ids: currentCluster.spider_slave.map((item) => item.spec_config.id),
mnt_count: currentCluster.spider_mnt.length,
};
}
},
});

const handleShowSelector = () => {
showSelector.value = true;
};

const handleInputChange = (value: string) => {
if (value) {
queryCluster({
bk_biz_id: window.PROJECT_CONFIG.BIZ_ID,
exact_domain: value,
});
}
};

const handleSelectorChange = (selected: Record<string, TendbClusterModel[]>) => {
emits('batch-edit', selected[ClusterTypes.TENDBCLUSTER]);
};
</script>
<style lang="less" scoped>
.batch-host-select {
font-size: 14px;
color: #3a84ff;
cursor: pointer;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<!--
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
*
* Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License athttps://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
* the specific language governing permissions and limitations under the License.
-->

<template>
<Column
field="specId"
:label="t('规格')"
:loading="loading"
:min-width="200"
required>
<Select
v-model="modelValue"
:list="selectList">
<template #option="{ item }">
<SpecPanel
:key="item.value"
:data="item.specData">
<div
class="tendb-slave-apply-option-item"
:class="{
active: item.value === modelValue,
}">
<span>{{ item.label }}</span>
<MiniTag
v-if="item.isCurrent"
:content="t('当前规格')"
theme="info" />
<span
class="spec-display-count"
:class="{ 'count-active': item.value === modelValue }">
{{ item.specData.count }}
</span>
</div>
</SpecPanel>
</template>
</Select>
</Column>
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import { useRequest } from 'vue-request';

import { getSpecResourceCount } from '@services/source/dbresourceResource';
import { getResourceSpecList } from '@services/source/dbresourceSpec';

import { Column, Select } from '@components/editable-table/Index.vue';
import MiniTag from '@components/mini-tag/index.vue';

import SpecPanel, { type SpecInfo } from './components/Panel.vue';

interface IListItem {
value: number;
label: string;
isCurrent: boolean;
specData: SpecInfo;
}

interface Props {
cluster: {
bk_cloud_id: number;
role: string;
master_spec_ids: number[];
slave_spec_ids: number[];
};
}

const props = defineProps<Props>();

const modelValue = defineModel<number>({
default: 0,
});

const { t } = useI18n();

const specList = shallowRef<IListItem[]>([]);

const currentSpecIds = computed(() => {
if (props.cluster.role === 'spider_master') {
return props.cluster.master_spec_ids;
}
if (props.cluster.role === 'spider_slave') {
return props.cluster.slave_spec_ids;
}
return [];
});

const selectList = computed(() =>
specList.value.map((item) => Object.assign({}, item, { isCurrent: currentSpecIds.value.includes(item.value) })),
);

const { run: fetchSpecList, loading } = useRequest(getResourceSpecList, {
manual: true,
onSuccess: async ({ results }) => {
const countResult = await getSpecResourceCount({
bk_biz_id: window.PROJECT_CONFIG.BIZ_ID,
bk_cloud_id: props.cluster.bk_cloud_id,
spec_ids: results.map((item) => item.spec_id),
});
specList.value = results.map((item) => ({
value: item.spec_id,
label: item.spec_name,
isCurrent: false,
specData: {
name: item.spec_name,
cpu: item.cpu,
id: item.spec_id,
mem: item.mem,
count: countResult[item.spec_id],
storage_spec: item.storage_spec,
},
}));
modelValue.value = props.cluster.master_spec_ids[0] || props.cluster.slave_spec_ids[0];
},
});

watch(
() => props.cluster,
() => {
fetchSpecList({
spec_cluster_type: 'tendbcluster',
spec_machine_type: 'proxy',
limit: -1,
offset: 0,
});
},
);
</script>
<style lang="less" scoped>
.tendb-slave-apply-option-item {
display: flex;
width: 100%;
align-items: center;

.spec-display-count {
height: 16px;
min-width: 20px;
margin-left: auto;
font-size: 12px;
line-height: 16px;
color: @gray-color;
text-align: center;
background-color: #f0f1f5;
border-radius: 2px;
}

.count-active {
color: white;
background-color: #a3c5fd;
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<!--
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
*
* Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License athttps://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
* the specific language governing permissions and limitations under the License.
-->

<template>
<BkPopover
height="220"
:offset="20"
placement="right"
:popover-delay="0"
theme="light"
width="558">
<slot />
<template #content>
<div class="spec-panel">
<div class="title">{{ data.name }} {{ t('规格') }}</div>
<div class="item">
<div class="item-title">CPU:</div>
<div class="item-content">
{{
data.cpu.min === data.cpu.max
? t('n核', { n: data.cpu.min })
: t('((n-m))核', { n: data.cpu.min, m: data.cpu.max })
}}
</div>
</div>
<div class="item">
<div class="item-title">{{ t('内存') }}:</div>
<div class="item-content">
{{ data.mem.min === data.mem.max ? data.mem.min : `(${data.mem.min}~${data.mem.max})` }} G
</div>
</div>
<div class="item">
<div class="item-title">{{ t('磁盘') }}:</div>
<div class="item-content">
<div class="table">
<div class="head">
<div class="head-mount-point">
{{ t('挂载点') }}
</div>
<div class="head-size">
{{ t('最小容量(G)') }}
</div>
<div class="head-type">
{{ t('磁盘类别') }}
</div>
</div>
<div class="row">
<div class="row-mount-point">
{{ data.storage_spec[0]?.mount_point }}
</div>
<div class="row-size">
{{ data.storage_spec[0]?.size }}
</div>
<div class="row-type">
{{ data.storage_spec[0]?.type }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</BkPopover>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';

export interface SpecInfo {
name: string;
cpu: {
max: number;
min: number;
};
id: number;
mem: {
max: number;
min: number;
};
count: number;
storage_spec: {
mount_point: string;
size: number;
type: string;
}[];
}

interface Props {
data?: SpecInfo;
}

withDefaults(defineProps<Props>(), {
data: () => ({
id: 1,
name: '默认规格',
cpu: {
min: 0,
max: 1,
},
mem: {
min: 0,
max: 1,
},
count: 1,
storage_spec: [
{
mount_point: '/data',
size: 0,
type: '默认',
},
],
}),
});

const { t } = useI18n();
</script>
<style lang="less" scoped>
.spec-panel {
display: flex;
width: 560px;
height: 220px;
padding: 16px;
margin-top: -14px;
margin-left: -14px;
background: #fff;
border: 1px solid #dcdee5;
box-shadow: 0 3px 6px 0 #00000029;
box-sizing: border-box;
flex-direction: column;

.title {
height: 20px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 700;
line-height: 20px;
color: #63656e;
}

.item {
display: flex;
width: 100%;
height: 32px;
align-items: center;

.item-title {
width: 72px;
height: 20px;
margin-right: 5px;
font-size: 12px;
letter-spacing: 0;
color: #63656e;
text-align: right;
}

.item-content {
height: 20px;
font-size: 12px;
letter-spacing: 0;
color: #313238;

.table {
display: flex;
width: 100%;
flex-direction: column;

.cell-common {
width: 200px;
height: 42px;
padding: 11px 16px;
border: 1px solid #dcdee5;
border-right: 1px solid #dcdee5;
border-bottom: 1px solid #dcdee5;
}

.head {
display: flex;
width: 100%;
background: #f0f1f5;
border: 1px solid #dcdee5;

.head-mount-point {
.cell-common();

border-bottom: none;
}

.head-size {
.cell-common();

width: 120px;
border-bottom: none;
}

.head-type {
.cell-common();

width: 120px;
border-bottom: none;
}
}

.row {
display: flex;
width: 100%;
border: 1px solid #dcdee5;
border-top: none;

.row-mount-point {
.cell-common();
}

.row-size {
.cell-common();

width: 120px;
}

.row-type {
.cell-common();

width: 120px;
}
}
}
}
}
}
</style>
9 changes: 1 addition & 8 deletions dbm-ui/frontend/src/views/db-manage/tendb-cluster/routes.ts
Original file line number Diff line number Diff line change
@@ -50,14 +50,7 @@ const spiderCapacityChangeRoute = {
component: () => import('@views/db-manage/tendb-cluster/capacity-change/Index.vue'),
};

const spiderProxyScaleUpRoute = {
name: 'SpiderProxyScaleUp',
path: 'proxy-scale-up/:page?',
meta: {
navName: t('扩容接入层'),
},
component: () => import('@views/db-manage/tendb-cluster/proxy-scale-up/Index.vue'),
};
const spiderProxyScaleUpRoute = createRouteItem(TicketTypes.TENDBCLUSTER_SPIDER_ADD_NODES, t('扩容接入层'));

const spiderProxyScaleDownRoute = createRouteItem(TicketTypes.TENDBCLUSTER_SPIDER_REDUCE_NODES, t('缩容接入层'));

Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ export default [
},
{
name: t('扩容接入层'),
id: 'SpiderProxyScaleUp',
id: TicketTypes.TENDBCLUSTER_SPIDER_ADD_NODES,
parentId: 'spider_cluster_maintain',
dbConsoleValue: 'tendbCluster.toolbox.proxyScaleUp',
},

0 comments on commit f5b4808

Please sign in to comment.