Skip to content

Commit 0fb2c8e

Browse files
author
Lasim
committed
feat(frontend): add search button and manual execution for server search
1 parent 2f63f81 commit 0fb2c8e

File tree

2 files changed

+45
-32
lines changed

2 files changed

+45
-32
lines changed

services/frontend/src/i18n/locales/en/mcp-catalog.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export default {
1111
noCategory: 'No category assigned',
1212
openMenu: 'Open menu',
1313
search: {
14-
placeholder: 'Search servers by name, description, or tags...'
14+
placeholder: 'Search servers by name, description, or tags...',
15+
button: 'Search'
1516
},
1617
columns: {
1718
name: 'Name',

services/frontend/src/views/admin/mcp-server-catalog/index.vue

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const eventBus = useEventBus()
4848
// State
4949
const servers = ref<McpServer[]>([])
5050
const isLoading = ref(true)
51+
const isSearching = ref(false)
5152
const error = ref<string | null>(null)
5253
5354
// Search and filter state
@@ -239,19 +240,19 @@ const startProgressPolling = (batchId: string) => {
239240
if (pollInterval) clearInterval(pollInterval)
240241
syncPhase.value = 'completed'
241242
isSyncModalOpen.value = false
242-
243+
243244
toast.success(t('mcpCatalog.registrySync.messages.completed'), {
244245
description: t('mcpCatalog.registrySync.messages.completedDescription', {
245246
count: progress.completed
246247
})
247248
})
248-
249+
249250
// Refresh catalog
250251
setTimeout(() => fetchServers(), 1000)
251252
} else if (batch.status === 'failed') {
252253
if (pollInterval) clearInterval(pollInterval)
253254
syncPhase.value = 'failed'
254-
255+
255256
toast.error(t('mcpCatalog.registrySync.messages.failed'))
256257
}
257258
} catch (error) {
@@ -270,11 +271,11 @@ onUnmounted(() => {
270271
271272
// Check if any filters are active
272273
const hasActiveFilters = () => {
273-
return !!searchQuery.value ||
274-
selectedStatus.value !== 'all' ||
275-
selectedLanguage.value !== 'all' ||
276-
selectedRuntime.value !== 'all' ||
277-
selectedFeatured.value !== 'all' ||
274+
return !!searchQuery.value ||
275+
selectedStatus.value !== 'all' ||
276+
selectedLanguage.value !== 'all' ||
277+
selectedRuntime.value !== 'all' ||
278+
selectedFeatured.value !== 'all' ||
278279
selectedAutoInstall.value !== 'all'
279280
}
280281
@@ -290,7 +291,7 @@ const searchServers = async (): Promise<void> => {
290291
error.value = null
291292
292293
const offset = (currentPage.value - 1) * pageSize.value
293-
294+
294295
const searchParams: McpServerSearchParams = {
295296
q: searchQuery.value.trim(), // Use actual search query
296297
limit: pageSize.value,
@@ -334,12 +335,12 @@ const fetchServers = async (): Promise<void> => {
334335
error.value = null
335336
336337
const offset = (currentPage.value - 1) * pageSize.value
337-
338+
338339
// Build filters object
339340
const filters: Record<string, string> = {
340341
visibility: 'global'
341342
}
342-
343+
343344
// Add active filters
344345
if (selectedStatus.value !== 'all') {
345346
filters.status = selectedStatus.value
@@ -356,7 +357,7 @@ const fetchServers = async (): Promise<void> => {
356357
if (selectedAutoInstall.value !== 'all') {
357358
filters.auto_install_new_default_team = selectedAutoInstall.value
358359
}
359-
360+
360361
const response = await McpCatalogService.getGlobalServersPaginated(
361362
filters,
362363
{ limit: pageSize.value, offset }
@@ -374,43 +375,44 @@ const fetchServers = async (): Promise<void> => {
374375
}
375376
}
376377
377-
// Debounced search execution
378-
let searchTimeout: ReturnType<typeof setTimeout> | null = null
379378
380-
const executeSearch = () => {
381-
if (searchTimeout) {
382-
clearTimeout(searchTimeout)
383-
}
379+
const executeSearch = async () => {
380+
isSearching.value = true
381+
currentPage.value = 1 // Reset to first page on new search
384382
385-
searchTimeout = setTimeout(() => {
386-
currentPage.value = 1 // Reset to first page on new search
387-
383+
try {
388384
// Use search API only when there's a text query
389385
// Otherwise use regular list API with filters
390386
if (hasTextSearch()) {
391-
searchServers()
387+
await searchServers()
392388
} else {
393-
fetchServers()
389+
await fetchServers()
394390
}
395-
}, 300) // 300ms debounce
391+
} finally {
392+
isSearching.value = false
393+
}
396394
}
397395
398396
// Clear all filters
399-
const clearFilters = () => {
397+
const clearFilters = async () => {
398+
isSearching.value = true
400399
searchQuery.value = ''
401400
selectedStatus.value = 'all'
402401
selectedLanguage.value = 'all'
403402
selectedRuntime.value = 'all'
404403
selectedFeatured.value = 'all'
405404
selectedAutoInstall.value = 'all'
406405
currentPage.value = 1
407-
fetchServers()
406+
407+
try {
408+
await fetchServers()
409+
} finally {
410+
isSearching.value = false
411+
}
408412
}
409413
410-
// Watch for filter changes
411-
watch([searchQuery, selectedStatus, selectedLanguage, selectedRuntime, selectedFeatured, selectedAutoInstall], () => {
412-
executeSearch()
413-
})
414+
// Note: Removed automatic watch-based search execution
415+
// Search now requires explicit button click via executeSearch()
414416
415417
// Pagination event handlers
416418
const handlePageChange = async (page: number) => {
@@ -547,12 +549,22 @@ onUnmounted(() => {
547549
:placeholder="t('mcpCatalog.table.search.placeholder')"
548550
v-model="searchQuery"
549551
class="max-w-sm"
552+
@keyup.enter="executeSearch"
550553
/>
554+
<Button
555+
@click="executeSearch"
556+
:loading="isSearching"
557+
loading-text="Searching..."
558+
class="flex items-center gap-2"
559+
>
560+
{{ t('mcpCatalog.table.search.button') }}
561+
</Button>
551562
<Button
552563
v-if="hasActiveFilters()"
553564
variant="ghost"
554565
size="sm"
555566
@click="clearFilters"
567+
:disabled="isSearching"
556568
class="flex items-center gap-2"
557569
>
558570
<X class="h-4 w-4" />
@@ -789,7 +801,7 @@ onUnmounted(() => {
789801
<p class="text-sm text-muted-foreground">{{ syncProgress.completed }} / {{ syncProgress.total }}</p>
790802
</div>
791803
<div class="w-full bg-muted rounded-full h-2">
792-
<div
804+
<div
793805
class="bg-primary h-2 rounded-full transition-all duration-300"
794806
:style="{ width: `${(syncProgress.completed / syncProgress.total * 100)}%` }"
795807
></div>

0 commit comments

Comments
 (0)