Skip to content

Commit 94b40e5

Browse files
JustaCatttiSecloud
authored andcommitted
feat(frontend): mongodb webconsole #9915
1 parent 7117eba commit 94b40e5

File tree

17 files changed

+447
-117
lines changed

17 files changed

+447
-117
lines changed

dbm-ui/frontend/src/services/model/mongodb/mongodb.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export default class Mongodb extends ClusterBase {
8181
mongodb_enable_disable: boolean;
8282
mongodb_plugin_create_clb: boolean;
8383
mongodb_view: boolean;
84+
mongodb_webconsole: boolean;
8485
};
8586
phase: string;
8687
phase_name: string;

dbm-ui/frontend/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ export * from './random';
4242
export * from './recentDays';
4343
export * from './time';
4444
export * from './url';
45+
export * from './validateBrackets';
4546
export * from './vNodeToHtml';
4647
export * from './vueHelper';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* 校验语句是否为完整的脚本语句
3+
* @example const isValid = validateBrackets('db.find({"{"})');
4+
* console.log(isValid); // true
5+
* @param input 语句
6+
* @returns boolean
7+
*/
8+
export function validateBrackets(input: string): boolean {
9+
const stack: string[] = [];
10+
const pairs: Record<string, string> = {
11+
')': '(',
12+
']': '[',
13+
'}': '{',
14+
};
15+
16+
// 过滤掉注释内容(包括 // 和 /* */)
17+
const withoutComments = input
18+
.replace(/\/\/.*$/gm, '') // 去掉单行注释
19+
.replace(/\/\*[\s\S]*?\*\//g, ''); // 去掉多行注释
20+
21+
// 过滤掉成对的引号内容
22+
const filteredInput = withoutComments.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, '');
23+
24+
for (const char of filteredInput) {
25+
if (['(', '[', '{'].includes(char)) {
26+
stack.push(char); // 开括号入栈
27+
} else if ([')', ']', '}'].includes(char)) {
28+
if (stack.pop() !== pairs[char]) {
29+
return false; // 括号不匹配
30+
}
31+
}
32+
}
33+
34+
return stack.length === 0; // 栈为空表示所有括号匹配
35+
}

dbm-ui/frontend/src/views/db-manage/common/webconsole/Index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
6363
import ClearScreen from './components/ClearScreen.vue';
6464
import ClusterTabs, { type ClusterItem } from './components/ClusterTabs.vue';
65+
import MongodbConsolePanel from './components/console-panel/mongodb/Index.vue';
6566
import MysqlConsolePanel from './components/console-panel/mysql/Index.vue';
6667
import RedisConsolePanel from './components/console-panel/redis/Index.vue';
6768
import ExportData from './components/ExportData.vue';
@@ -81,6 +82,7 @@
8182
const { t } = useI18n();
8283
8384
const consolePanelMap = {
85+
[DBTypes.MONGODB]: MongodbConsolePanel,
8486
[DBTypes.MYSQL]: MysqlConsolePanel,
8587
[DBTypes.REDIS]: RedisConsolePanel,
8688
[DBTypes.TENDBCLUSTER]: MysqlConsolePanel,

dbm-ui/frontend/src/views/db-manage/common/webconsole/components/ClusterTabs.vue

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
v-for="(clusterId, index) in selectedClusters"
55
:key="clusterId"
66
class="tab-item"
7-
:class="{ 'item-selected': clusterId === modelValue }"
7+
:class="{ 'item-selected': clusterId === localClusterId }"
88
@click="() => handleActiveTab(clusterId)">
99
<div class="active-bar"></div>
1010
<div class="tab-item-content">
@@ -30,8 +30,7 @@
3030
class="add-icon-main">
3131
<DbIcon
3232
class="add-icon"
33-
type="increase"
34-
@click="handleClickAddIcon" />
33+
type="increase" />
3534
</div>
3635
</div>
3736
<div style="display: none">
@@ -40,24 +39,28 @@
4039
class="webconsole-select-clusters"
4140
:style="{ height: clustersPanelHeight }">
4241
<div class="title">{{ t('连接的集群') }}</div>
43-
<BkSelect
44-
ref="clutersRef"
45-
class="clusters-select"
46-
disable-focus-behavior
47-
filterable
48-
:model-value="selectedClusters"
49-
multiple
50-
:popover-options="{ disableTeleport: true }"
51-
@change="handleClusterSelectChange">
52-
<template #trigger>
53-
<span></span>
54-
</template>
55-
<BkOption
56-
v-for="item in clusterList"
57-
:key="item.id"
58-
:name="item.immute_domain"
59-
:value="item.id" />
60-
</BkSelect>
42+
<div class="clusters-select">
43+
<BkInput
44+
v-model="searchValue"
45+
behavior="simplicity"
46+
class="cluster-select-search"
47+
:placeholder="t('请输入关键字')">
48+
<template #prefix>
49+
<DbIcon
50+
class="input-icon"
51+
type="search" />
52+
</template>
53+
</BkInput>
54+
<ul class="cluster-select-warpper">
55+
<li
56+
v-for="item in renderOptions"
57+
:key="item.id"
58+
class="cluster-select-option"
59+
@click="handleClusterSelectChange([item.id])">
60+
{{ item.immute_domain }}
61+
</li>
62+
</ul>
63+
</div>
6164
</div>
6265
</div>
6366
</template>
@@ -96,20 +99,16 @@
9699
const { t } = useI18n();
97100
const route = useRoute();
98101
99-
const modelValue = defineModel({
100-
default: 0 as number,
101-
type: Number,
102-
});
103-
104102
const routeClusterId = route.query.clusterId;
105103
let clustersRaw: ClusterItem[] = [];
106104
let tippyIns: Instance | undefined;
107105
108-
const clutersRef = ref();
109106
const addTabRef = ref();
110107
const popRef = ref();
108+
const localClusterId = ref(0);
111109
const clustersMap = ref<Record<number, ClusterItem>>({});
112110
const selectedClusters = ref<number[]>([]);
111+
const searchValue = ref('');
113112
114113
const clustersPanelHeight = computed(() => {
115114
if (!clusterList.value) {
@@ -122,6 +121,10 @@
122121
return `${height}px`;
123122
});
124123
124+
const renderOptions = computed(() =>
125+
clusterList.value?.filter((item) => item.immute_domain.indexOf(searchValue.value) !== -1),
126+
);
127+
125128
const { data: clusterList } = useRequest(queryAllTypeCluster, {
126129
defaultParams: [
127130
{
@@ -158,7 +161,7 @@
158161
}
159162
const id = ids.pop()!;
160163
selectedClusters.value.push(id);
161-
modelValue.value = id;
164+
localClusterId.value = id;
162165
emits('change', clustersMap.value[id]);
163166
updateClusterSelect();
164167
tippyIns?.hide();
@@ -169,7 +172,7 @@
169172
};
170173
171174
const handleActiveTab = (id: number) => {
172-
modelValue.value = id;
175+
localClusterId.value = id;
173176
emits('change', clustersMap.value[id]);
174177
};
175178
@@ -185,21 +188,15 @@
185188
const currentClusterId = selectedClusters.value[index];
186189
selectedClusters.value.splice(index, 1);
187190
const clusterCount = selectedClusters.value.length;
188-
if (currentClusterId === modelValue.value) {
191+
if (currentClusterId === localClusterId.value) {
189192
emits('removeTab', currentClusterId);
190193
// 关闭当前打开tab
191-
modelValue.value = clusterCount === 0 ? 0 : selectedClusters.value[clusterCount - 1];
192-
emits('change', clustersMap.value[modelValue.value]);
194+
localClusterId.value = clusterCount === 0 ? 0 : selectedClusters.value[clusterCount - 1];
195+
emits('change', clustersMap.value[localClusterId.value]);
193196
}
194197
updateClusterSelect();
195198
};
196199
197-
const handleClickAddIcon = () => {
198-
setTimeout(() => {
199-
clutersRef.value.showPopover();
200-
});
201-
};
202-
203200
onMounted(() => {
204201
tippyIns = tippy(addTabRef.value as SingleTarget, {
205202
appendTo: () => document.body,
@@ -209,14 +206,6 @@
209206
interactive: true,
210207
maxWidth: 'none',
211208
offset: [0, 0],
212-
onHide() {
213-
clutersRef.value.hidePopover();
214-
},
215-
onShow() {
216-
setTimeout(() => {
217-
clutersRef.value.showPopover();
218-
});
219-
},
220209
placement: 'bottom-start',
221210
theme: 'light',
222211
trigger: 'mouseenter click',
@@ -245,10 +234,42 @@
245234
padding: 0 !important;
246235
247236
.clusters-select {
248-
.bk-select-popover {
249-
border: none;
250-
transform: translate3d(0, 41px, 0);
251-
box-shadow: none;
237+
margin: 8px;
238+
239+
.cluster-select-search {
240+
border-bottom: 1px solid #eaebf0;
241+
}
242+
243+
.input-icon {
244+
display: flex;
245+
padding-left: 8px;
246+
font-size: 16px;
247+
color: #c4c6cc;
248+
align-items: center;
249+
justify-content: center;
250+
}
251+
252+
.cluster-select-warpper {
253+
margin-top: 4px;
254+
}
255+
256+
.cluster-select-option {
257+
position: relative;
258+
display: flex;
259+
height: 32px;
260+
overflow: hidden;
261+
font-size: 12px;
262+
color: #63656e;
263+
text-align: left;
264+
text-overflow: ellipsis;
265+
white-space: nowrap;
266+
cursor: pointer;
267+
user-select: none;
268+
align-items: center;
269+
270+
&:hover {
271+
background-color: #f5f7fa;
272+
}
252273
}
253274
}
254275
@@ -367,14 +388,6 @@
367388
font-size: 15px;
368389
color: #c4c6cc;
369390
}
370-
371-
.clusters-select {
372-
.bk-select-popover {
373-
border: none;
374-
transform: translate3d(0, 41px, 0);
375-
box-shadow: none;
376-
}
377-
}
378391
}
379392
}
380393
</style>

0 commit comments

Comments
 (0)