Skip to content

Commit 4923e73

Browse files
committed
feat(frontend): 工具箱支持资源池协议变更_redis整机替换 #8076
# Reviewed, transaction id: 28115
1 parent f2c5b18 commit 4923e73

File tree

17 files changed

+2649
-12
lines changed

17 files changed

+2649
-12
lines changed

dbm-ui/frontend/src/components/render-table/columns/spec-display/Panel.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@
5858
</div>
5959
<div class="table-row">
6060
<div class="row-one">
61-
{{ data.storage_spec[0].mount_point }}
61+
{{ data.storage_spec[0]?.mount_point || '--' }}
6262
</div>
6363
<div class="row-two">
64-
{{ data.storage_spec[0].size }}
64+
{{ data.storage_spec[0]?.size || '--' }}
6565
</div>
6666
<div class="row-three">
67-
{{ data.storage_spec[0].type }}
67+
{{ data.storage_spec[0]?.type || '--' }}
6868
</div>
6969
</div>
7070
</div>
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
<!--
2+
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
3+
*
4+
* Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
5+
*
6+
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License athttps://opensource.org/licenses/MIT
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
10+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for
11+
* the specific language governing permissions and limitations under the License.
12+
-->
13+
14+
<template>
15+
<SmartAction>
16+
<BkAlert
17+
class="mb-20"
18+
closable
19+
:title="t('添加运维节点:在原集群上增加运维节点实例来实现额外的数据访问,在运维节点上的操作不会影响原集群')" />
20+
<BkForm
21+
v-model="formData"
22+
class="mb-20"
23+
form-type="vertical">
24+
<EditableTable
25+
ref="table"
26+
class="mb-20"
27+
:model="formData.tableData"
28+
:rules="rules">
29+
<EditableTableRow
30+
v-for="(item, index) in formData.tableData"
31+
:key="index">
32+
<HostColumn
33+
ref="rowHostRef"
34+
v-model="item.host"
35+
:selected="selected"
36+
@batch-edit="handleBatchEdit"
37+
@change="(data) => handleChange(data, item, index)" />
38+
<Column
39+
:label="t('角色类型')"
40+
:min-width="150">
41+
<Block
42+
v-model="item.role"
43+
:placeholder="t('自动生成')" />
44+
</Column>
45+
<Column
46+
:label="t('所属集群')"
47+
:min-width="150"
48+
:rowspan="rowSpan[item.cluster.domain]">
49+
<Block
50+
v-model="item.cluster.domain"
51+
:placeholder="t('自动生成')" />
52+
</Column>
53+
<SpecColumn v-model="item.spec" />
54+
<OperationColumn
55+
v-model:table-data="formData.tableData"
56+
:create-row-method="createTableRow" />
57+
</EditableTableRow>
58+
</EditableTable>
59+
<TicketRemark v-model="formData.remark" />
60+
</BkForm>
61+
<template #action>
62+
<BkButton
63+
class="mr-8 w-88"
64+
:loading="isSubmitting"
65+
theme="primary"
66+
@click="handleSubmit">
67+
{{ t('提交') }}
68+
</BkButton>
69+
<DbPopconfirm
70+
:confirm-handler="handleReset"
71+
:content="t('重置将会情况当前填写的所有内容_请谨慎操作')"
72+
:title="t('确认重置页面')">
73+
<BkButton
74+
class="ml8 w-88"
75+
:disabled="isSubmitting">
76+
{{ t('重置') }}
77+
</BkButton>
78+
</DbPopconfirm>
79+
</template>
80+
</SmartAction>
81+
</template>
82+
<script lang="ts" setup>
83+
import _ from 'lodash';
84+
import { reactive, useTemplateRef } from 'vue';
85+
import { useI18n } from 'vue-i18n';
86+
87+
import { queryMasterSlavePairs } from '@services/source/redisToolbox';
88+
89+
import { useCreateTicket } from '@hooks';
90+
91+
import { TicketTypes } from '@common/const';
92+
93+
import EditableTable, { Block, Column, Row as EditableTableRow } from '@components/editable-table/Index.vue';
94+
95+
import OperationColumn from '@views/db-manage/common/toolbox-field/operation-column/Index.vue';
96+
import TicketRemark from '@views/db-manage/common/toolbox-field/ticket-remark/Index.vue';
97+
import type { SpecInfo } from '@views/db-manage/redis/common/spec-panel/Index.vue';
98+
99+
import HostColumn, { type HostInputed, type SelectorHost } from './components/HostColumn.vue';
100+
import SpecColumn from './components/SpecColumn.vue';
101+
102+
interface RowData {
103+
host: {
104+
bk_biz_id: number;
105+
bk_host_id: number;
106+
bk_cloud_id: number;
107+
ip: string;
108+
};
109+
role: string;
110+
cluster: {
111+
domain: string;
112+
cluster_ids: number[]; // 关联集群的id集合
113+
};
114+
spec: SpecInfo;
115+
}
116+
117+
const { t } = useI18n();
118+
const tableRef = useTemplateRef('table');
119+
120+
const createTableRow = (data = {} as Partial<RowData>) => ({
121+
host: data.host || {
122+
bk_biz_id: window.PROJECT_CONFIG.BIZ_ID,
123+
bk_cloud_id: 0,
124+
bk_host_id: 0,
125+
ip: '',
126+
},
127+
role: data.role || '',
128+
cluster: data.cluster || {
129+
domain: '',
130+
cluster_ids: [],
131+
},
132+
spec: data.spec || ({} as SpecInfo),
133+
});
134+
135+
const defaultData = () => ({
136+
tableData: [createTableRow()],
137+
remark: '',
138+
});
139+
140+
const formData = reactive(defaultData());
141+
const rowHostRef = ref();
142+
143+
const selected = computed(() => formData.tableData.filter((item) => item.host.bk_host_id).map((item) => item.host));
144+
const selectedMap = computed(() => Object.fromEntries(selected.value.map((cur) => [cur.ip, true])));
145+
const rowSpan = computed(() =>
146+
formData.tableData.reduce<Record<string, number>>((acc, item) => {
147+
if (item.cluster.domain) {
148+
acc[item.cluster.domain] = (acc[item.cluster.domain] || 0) + 1;
149+
}
150+
return acc;
151+
}, {}),
152+
);
153+
154+
const rules = {
155+
'host.tp': [
156+
{
157+
validator: (value: string) => selected.value.filter((item) => item.ip === value).length < 2,
158+
message: t('目标主机重复'),
159+
trigger: 'change',
160+
},
161+
],
162+
};
163+
164+
interface TicketDetail {
165+
ip_source: 'resource_pool';
166+
infos: {
167+
bk_cloud_id: number;
168+
cluster_ids: number[];
169+
redis_master: {
170+
ip: string;
171+
spec_id: number;
172+
bk_host_id: number;
173+
}[];
174+
redis_slave: TicketDetail['infos'][0]['redis_master'];
175+
proxy: TicketDetail['infos'][0]['redis_master'];
176+
display_info: {
177+
data: {
178+
ip: string;
179+
role: string;
180+
cluster_domain: string;
181+
spec_id: number;
182+
spec_name: string;
183+
}[];
184+
};
185+
}[];
186+
}
187+
188+
const { run: createTicketRun, loading: isSubmitting } = useCreateTicket<TicketDetail>(
189+
TicketTypes.REDIS_CLUSTER_CUTOFF,
190+
);
191+
192+
const handleSubmit = async () => {
193+
const result = await tableRef.value!.validate();
194+
if (!result) {
195+
return;
196+
}
197+
const sameClusters: Record<string, RowData[]> = {};
198+
const taskList: Promise<ServiceReturnType<typeof queryMasterSlavePairs>>[] = [];
199+
formData.tableData.forEach((item) => {
200+
if (!sameClusters[item.cluster.domain]) {
201+
sameClusters[item.cluster.domain] = [];
202+
}
203+
sameClusters[item.cluster.domain].push(item);
204+
item.cluster.cluster_ids.forEach((clusterId) => {
205+
taskList.push(
206+
queryMasterSlavePairs({
207+
cluster_id: clusterId,
208+
}),
209+
);
210+
});
211+
});
212+
const results = await Promise.all(taskList);
213+
// 主从映射关系
214+
const slaveMasterMap = _.flatten(results).reduce<Record<string, string>>((acc, item) => {
215+
acc[item.master_ip] = item.slave_ip;
216+
return acc;
217+
}, {});
218+
219+
const infos = Object.values(sameClusters).map((sameRows) => {
220+
const info = {
221+
cluster_ids: sameRows[0].cluster.cluster_ids,
222+
bk_cloud_id: sameRows[0].host.bk_cloud_id,
223+
proxy: [],
224+
redis_master: [],
225+
redis_slave: [],
226+
display_info: {
227+
data: [],
228+
},
229+
} as unknown as TicketDetail['infos'][0];
230+
const needDeleteSlaves: string[] = [];
231+
sameRows.forEach((item) => {
232+
const spec = {
233+
bk_host_id: item.host.bk_host_id,
234+
ip: item.host.ip,
235+
spec_id: item.spec.id,
236+
};
237+
info.display_info.data.push({
238+
ip: item.host.ip,
239+
role: item.role,
240+
cluster_domain: item.cluster.domain,
241+
spec_id: item.spec?.id ?? 0,
242+
spec_name: item.spec?.name ?? '',
243+
});
244+
const list = info[
245+
item.role as 'redis_slave' | 'redis_master' | 'proxy'
246+
] as TicketDetail['infos'][0]['redis_master'];
247+
_.merge(info, {
248+
[item.role]: [...list, spec],
249+
});
250+
if (item.role === 'redis_master') {
251+
const deleteSlaveIp = slaveMasterMap[item.host.ip];
252+
if (deleteSlaveIp) {
253+
needDeleteSlaves.push(deleteSlaveIp);
254+
}
255+
}
256+
});
257+
// 当选择了master的时候,过滤对应的slave
258+
info.redis_slave = info.redis_slave.filter((item) => !needDeleteSlaves.includes(item.ip));
259+
return info;
260+
});
261+
262+
createTicketRun(
263+
{
264+
ip_source: 'resource_pool',
265+
infos,
266+
},
267+
formData.remark,
268+
);
269+
};
270+
271+
const handleReset = () => {
272+
Object.assign(formData, defaultData());
273+
};
274+
275+
const handleBatchEdit = (list: SelectorHost[]) => {
276+
const dataList = list.reduce<RowData[]>((acc, item) => {
277+
if (!selectedMap.value[item.ip]) {
278+
acc.push(
279+
createTableRow({
280+
host: {
281+
bk_biz_id: window.PROJECT_CONFIG.BIZ_ID,
282+
bk_cloud_id: item.bk_cloud_id,
283+
bk_host_id: item.bk_host_id,
284+
ip: item.ip,
285+
},
286+
role: item.role,
287+
cluster: {
288+
domain: item.cluster_domain,
289+
cluster_ids: item.cluster_ids,
290+
},
291+
spec: item.spec_config,
292+
}),
293+
);
294+
}
295+
return acc;
296+
}, []);
297+
formData.tableData = [...(selected.value.length ? formData.tableData : []), ...dataList];
298+
};
299+
300+
/**
301+
*
302+
* @param target 手输ip后查询到的数据
303+
* @param item 行数据
304+
* @param index 行索引
305+
*/
306+
const handleChange = (target: HostInputed, item: RowData, index: number) => {
307+
const roleMap = {
308+
master: 'redis_master',
309+
slave: 'redis_slave',
310+
proxy: 'proxy',
311+
} as Record<string, string>;
312+
const row = createTableRow({
313+
host: {
314+
bk_biz_id: window.PROJECT_CONFIG.BIZ_ID,
315+
bk_cloud_id: target.bk_cloud_id,
316+
bk_host_id: target.bk_host_id,
317+
ip: target.ip,
318+
},
319+
role: roleMap[target.role] || '',
320+
cluster: {
321+
domain: target.master_domain,
322+
cluster_ids: target.related_clusters.map((item) => item.id),
323+
},
324+
spec: target.spec_config,
325+
});
326+
Object.assign(item, row);
327+
328+
// 输入的主机为master主机带出slave主机
329+
if (target.role === 'master') {
330+
queryMasterSlavePairs({
331+
cluster_id: target.cluster_id,
332+
}).then((data) => {
333+
const { slaves } = data[0];
334+
if (slaves) {
335+
formData.tableData.splice(
336+
index + 1,
337+
0,
338+
createTableRow({
339+
host: {
340+
bk_biz_id: slaves.bk_biz_id,
341+
bk_cloud_id: slaves.bk_cloud_id,
342+
bk_host_id: slaves.bk_host_id,
343+
ip: slaves.ip,
344+
},
345+
role: 'redis_slave',
346+
cluster: row.cluster,
347+
spec: row.spec,
348+
}),
349+
);
350+
}
351+
});
352+
}
353+
};
354+
</script>

0 commit comments

Comments
 (0)