Skip to content

Commit 5206466

Browse files
author
Lasim
committed
feat(frontend): implement pagination controls and enhance search results
1 parent fbf9a9c commit 5206466

File tree

4 files changed

+94
-103
lines changed

4 files changed

+94
-103
lines changed

services/frontend/src/components/mcp-server/wizard/McpServerSelectionStep.vue

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button'
77
import { Alert, AlertDescription } from '@/components/ui/alert'
88
import McpServerSquareCard from '@/components/mcp-server/McpServerSquareCard.vue'
99
import FeaturedMcpServers from '@/components/mcp-server/FeaturedMcpServers.vue'
10+
import PaginationControls from '@/components/ui/pagination/PaginationControls.vue'
1011
import { McpCatalogService } from '@/services/mcpCatalogService'
1112
import { McpCategoriesService } from '@/services/mcpCategoriesService'
1213
import type { McpServerSearchParams, McpServerSearchResponse } from '@/types/mcp-catalog'
@@ -36,26 +37,23 @@ const selectedServerId = ref<string | null>(null)
3637
const selectedCategory = ref('all')
3738
const searchResults = ref<McpServerSearchResponse | null>(null)
3839
39-
// Constants
40-
const MAX_RESULTS_TO_SHOW = 50
40+
// Pagination state
41+
const currentPage = ref(1)
42+
const pageSize = ref(15)
43+
const totalItems = ref(0)
44+
const pageSizeOptions = ref([15, 30, 45, 60])
4145
4246
// Computed
4347
const filteredServers = computed(() => {
4448
return searchResults.value?.servers || []
4549
})
4650
47-
const showTooManyResultsWarning = computed(() => {
48-
const pagination = searchResults.value?.pagination
49-
return pagination && pagination.total > MAX_RESULTS_TO_SHOW
51+
const hasSearched = computed(() => {
52+
return searchQuery.value.trim().length > 0
5053
})
5154
5255
const shouldShowResults = computed(() => {
53-
const pagination = searchResults.value?.pagination
54-
return pagination && pagination.total <= MAX_RESULTS_TO_SHOW && filteredServers.value.length > 0
55-
})
56-
57-
const hasSearched = computed(() => {
58-
return searchQuery.value.trim().length > 0
56+
return hasSearched.value && filteredServers.value.length > 0
5957
})
6058
6159
// Methods
@@ -101,10 +99,12 @@ const performSearch = async () => {
10199
error.value = null
102100
searchQuery.value = searchTerm.value
103101
102+
const offset = (currentPage.value - 1) * pageSize.value
103+
104104
const searchParams: McpServerSearchParams = {
105105
q: searchTerm.value.trim(),
106-
limit: MAX_RESULTS_TO_SHOW + 1, // Get one extra to detect if there are more
107-
offset: 0
106+
limit: pageSize.value,
107+
offset: offset
108108
}
109109
110110
// Add category filter if selected
@@ -113,21 +113,13 @@ const performSearch = async () => {
113113
}
114114
115115
const response = await McpCatalogService.searchServers(searchParams)
116-
117-
// Check if we got too many results
118-
if (response.pagination.total > MAX_RESULTS_TO_SHOW) {
119-
searchResults.value = {
120-
servers: [],
121-
pagination: response.pagination,
122-
filters: response.filters
123-
}
124-
} else {
125-
searchResults.value = response
126-
}
116+
searchResults.value = response
117+
totalItems.value = response.pagination.total
127118
128119
} catch (err) {
129120
error.value = err instanceof Error ? err.message : 'Failed to search servers'
130121
searchResults.value = null
122+
totalItems.value = 0
131123
} finally {
132124
isLoading.value = false
133125
}
@@ -138,6 +130,20 @@ const clearSearch = () => {
138130
searchQuery.value = ''
139131
searchResults.value = null
140132
error.value = null
133+
currentPage.value = 1
134+
totalItems.value = 0
135+
}
136+
137+
// Pagination event handlers
138+
const handlePageChange = async (page: number) => {
139+
currentPage.value = page
140+
await performSearch()
141+
}
142+
143+
const handlePageSizeChange = async (newPageSize: number) => {
144+
pageSize.value = newPageSize
145+
currentPage.value = 1
146+
await performSearch()
141147
}
142148
143149
onMounted(() => {
@@ -147,9 +153,10 @@ onMounted(() => {
147153
</script>
148154

149155
<template>
150-
<div class="pt-10">
156+
<div :class="shouldShowResults ? 'pt-2' : 'pt-10'">
151157
<div class="mx-auto max-w-2xl">
152-
<div class="text-center">
158+
<!-- Header - hide when we have search results -->
159+
<div v-if="!shouldShowResults" class="text-center">
153160
<div class="mx-auto size-16 text-gray-400">
154161
<PackagePlus class="w-full h-full" stroke-width="1.25" aria-hidden="true" />
155162
</div>
@@ -253,31 +260,8 @@ onMounted(() => {
253260
<span class="text-gray-600">{{ t('messages.loading') }}</span>
254261
</div>
255262

256-
<!-- Too Many Results Warning -->
257-
<div v-else-if="showTooManyResultsWarning" class="mt-14">
258-
<Alert class="border-orange-200 bg-orange-50 text-orange-800">
259-
<AlertTriangle class="h-4 w-4" />
260-
<AlertDescription>
261-
{{ t('mcpInstallations.wizard.server.tooManyResults', { total: searchResults?.pagination.total }) }}
262-
</AlertDescription>
263-
</Alert>
264-
<div class="mt-4 text-center">
265-
<Button variant="outline" @click="clearSearch">
266-
{{ t('actions.clearSearch') }}
267-
</Button>
268-
</div>
269-
</div>
270-
271-
<!-- Server Grid (only show when there's a search query and results fit criteria) -->
263+
<!-- Server Grid (only show when there's a search query and results) -->
272264
<div v-else-if="shouldShowResults" class="mt-14 space-y-6">
273-
<!-- Results info -->
274-
<div v-if="searchResults?.pagination" class="text-sm text-gray-600 text-center">
275-
{{ t('mcpInstallations.wizard.server.maxResultsReached', {
276-
shown: filteredServers.length,
277-
total: searchResults.pagination.total
278-
}) }}
279-
</div>
280-
281265
<!-- 3-tier grid layout -->
282266
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
283267
<McpServerSquareCard
@@ -288,10 +272,22 @@ onMounted(() => {
288272
@click="handleDetailsClick"
289273
/>
290274
</div>
275+
276+
<!-- Pagination Controls -->
277+
<PaginationControls
278+
v-if="totalItems > 0"
279+
:current-page="currentPage"
280+
:page-size="pageSize"
281+
:total-items="totalItems"
282+
:is-loading="isLoading"
283+
:page-size-options="pageSizeOptions"
284+
@page-change="handlePageChange"
285+
@page-size-change="handlePageSizeChange"
286+
/>
291287
</div>
292288

293289
<!-- No Results -->
294-
<div v-else-if="hasSearched && filteredServers.length === 0 && !showTooManyResultsWarning && !isLoading" class="mt-14 text-center py-8">
290+
<div v-else-if="hasSearched && filteredServers.length === 0 && !isLoading" class="mt-14 text-center py-8">
295291
<p class="text-gray-500">{{ t('mcpInstallations.wizard.server.noServersFound') }}</p>
296292
<div class="mt-4">
297293
<Button variant="outline" @click="clearSearch">

services/frontend/src/components/ui/pagination/PaginationControls.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function handlePageSizeChange(newPageSize: any) {
9797
@update:model-value="handlePageSizeChange"
9898
:disabled="isLoading"
9999
>
100-
<SelectTrigger class="h-8 w-[70px]">
100+
<SelectTrigger class="h-8 w-[80px]">
101101
<SelectValue />
102102
</SelectTrigger>
103103
<SelectContent side="top">

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

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import { ref, onMounted } from 'vue'
32
import { useI18n } from 'vue-i18n'
43
import {
54
Table,
@@ -11,7 +10,6 @@ import {
1110
} from '@/components/ui/table'
1211
import { Badge } from '@/components/ui/badge'
1312
import { Button } from '@/components/ui/button'
14-
import { DynamicIcon } from '@/components/ui/dynamic-icon'
1513
import {
1614
Edit,
1715
Github,
@@ -20,7 +18,6 @@ import {
2018
ExternalLink
2119
} from 'lucide-vue-next'
2220
import type { McpServer } from './types'
23-
import { McpCategoriesService, type McpCategory } from '@/services/mcpCategoriesService'
2421
2522
interface Props {
2623
servers: McpServer[]
@@ -30,22 +27,6 @@ interface Props {
3027
const props = defineProps<Props>()
3128
const { t } = useI18n()
3229
33-
// Categories state
34-
const categories = ref<McpCategory[]>([])
35-
const categoriesLoading = ref(false)
36-
37-
// Fetch categories on mount
38-
onMounted(async () => {
39-
try {
40-
categoriesLoading.value = true
41-
categories.value = await McpCategoriesService.getCategories()
42-
} catch (error) {
43-
console.error('Failed to fetch categories:', error)
44-
} finally {
45-
categoriesLoading.value = false
46-
}
47-
})
48-
4930
// Get status badge variant
5031
const getStatusVariant = (status: string) => {
5132
switch (status) {
@@ -60,10 +41,20 @@ const getStatusVariant = (status: string) => {
6041
}
6142
}
6243
63-
// Get category by ID
64-
const getCategoryById = (categoryId?: string): McpCategory | null => {
65-
if (!categoryId) return null
66-
return categories.value.find(cat => cat.id === categoryId) || null
44+
// Get runtime badge color
45+
const getRuntimeBadgeClass = (runtime: string) => {
46+
const colors: Record<string, string> = {
47+
'node.js': 'bg-green-100 text-green-800',
48+
node: 'bg-green-100 text-green-800',
49+
python: 'bg-blue-100 text-blue-800',
50+
docker: 'bg-cyan-100 text-cyan-800',
51+
go: 'bg-cyan-100 text-cyan-800',
52+
rust: 'bg-orange-100 text-orange-800',
53+
java: 'bg-red-100 text-red-800',
54+
'.net': 'bg-purple-100 text-purple-800',
55+
dotnet: 'bg-purple-100 text-purple-800',
56+
}
57+
return colors[runtime.toLowerCase()] || 'bg-gray-100 text-gray-800'
6758
}
6859
6960
// Get repository icon based on platform
@@ -101,7 +92,7 @@ const getRepositoryLabel = (platform?: string) => {
10192
<TableRow>
10293
<TableHead>{{ t('mcpCatalog.table.columns.name') }}</TableHead>
10394
<TableHead>{{ t('mcpCatalog.table.columns.description') }}</TableHead>
104-
<TableHead>{{ t('mcpCatalog.table.columns.category') }}</TableHead>
95+
<TableHead>{{ t('mcpCatalog.table.columns.runtime') }}</TableHead>
10596
<TableHead>{{ t('mcpCatalog.table.columns.status') }}</TableHead>
10697
<TableHead class="w-[100px]">{{ t('mcpCatalog.table.columns.actions') }}</TableHead>
10798
</TableRow>
@@ -167,18 +158,14 @@ const getRepositoryLabel = (platform?: string) => {
167158
</div>
168159
</TableCell>
169160

170-
<!-- Category -->
161+
<!-- Runtime -->
171162
<TableCell>
172-
<div v-if="getCategoryById(server.category_id)" class="flex items-center gap-2">
173-
<DynamicIcon
174-
:name="getCategoryById(server.category_id)?.icon || ''"
175-
class="h-4 w-4 text-muted-foreground"
176-
/>
177-
<span class="text-sm">{{ getCategoryById(server.category_id)?.name }}</span>
178-
</div>
179-
<span v-else class="text-sm text-muted-foreground italic">
180-
{{ t('mcpCatalog.table.noCategory') }}
181-
</span>
163+
<Badge
164+
variant="outline"
165+
:class="getRuntimeBadgeClass(server.runtime)"
166+
>
167+
{{ server.runtime }}
168+
</Badge>
182169
</TableCell>
183170

184171
<!-- Status -->

0 commit comments

Comments
 (0)