@@ -48,6 +48,7 @@ const eventBus = useEventBus()
4848// State
4949const servers = ref <McpServer []>([])
5050const isLoading = ref (true )
51+ const isSearching = ref (false )
5152const 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
272273const 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
416418const 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