Skip to content

Commit c24e962

Browse files
committed
feat(frontend): redesign MCP catalog detail page with tab navigation and card layout
- Restructure /admin/mcp-server-catalog/view/[id] to match team detail page pattern - Create tab-based navigation with general tab as default - Move server logo from content to page header using DsPageHeading icon slot - Replace ContentWrapper with three DsCard components for better information hierarchy - Add route redirect for backward compatibility - Create reusable McpServerCatalogDetailPageHeading and McpServerCatalogDetailTabs components - Fix TypeScript type safety issues by replacing 'as any' with 'as string' in JSON.parse calls
1 parent 7efa7c3 commit c24e962

File tree

5 files changed

+1307
-2
lines changed

5 files changed

+1307
-2
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<script setup lang="ts">
2+
import { useI18n } from 'vue-i18n'
3+
import { DsPageHeading } from '@/components/ui/ds-page-heading'
4+
import { Skeleton } from '@/components/ui/skeleton'
5+
import {
6+
Breadcrumb,
7+
BreadcrumbItem,
8+
BreadcrumbLink,
9+
BreadcrumbList,
10+
BreadcrumbPage,
11+
BreadcrumbSeparator,
12+
} from '@/components/ui/breadcrumb'
13+
import McpServerAvatar from '@/components/mcp-server/McpServerAvatar.vue'
14+
import type { McpServer } from '@/views/admin/mcp-server-catalog/types'
15+
16+
interface Props {
17+
server: McpServer | null
18+
isLoading?: boolean
19+
}
20+
21+
defineProps<Props>()
22+
const { t } = useI18n()
23+
</script>
24+
25+
<template>
26+
<!-- When server is loaded -->
27+
<DsPageHeading v-if="server" :title="server.name" :show-border="false">
28+
<template #icon>
29+
<McpServerAvatar
30+
:icon-url="server.icon_url"
31+
:server-name="server.name"
32+
size="md"
33+
rounded="lg"
34+
class="mr-3"
35+
/>
36+
</template>
37+
38+
<Breadcrumb>
39+
<BreadcrumbList>
40+
<BreadcrumbItem>
41+
<BreadcrumbLink as-child>
42+
<RouterLink to="/admin/mcp-server-catalog">
43+
{{ t('mcpCatalog.title') }}
44+
</RouterLink>
45+
</BreadcrumbLink>
46+
</BreadcrumbItem>
47+
<BreadcrumbSeparator />
48+
<BreadcrumbItem>
49+
<BreadcrumbPage>{{ server.name }}</BreadcrumbPage>
50+
</BreadcrumbItem>
51+
</BreadcrumbList>
52+
</Breadcrumb>
53+
54+
<template #actions>
55+
<slot name="actions" />
56+
</template>
57+
</DsPageHeading>
58+
59+
<!-- Loading state (shows skeleton breadcrumb) -->
60+
<DsPageHeading v-else :title="t('mcpCatalog.edit.titleLoading')" :show-border="false">
61+
<Breadcrumb>
62+
<BreadcrumbList>
63+
<BreadcrumbItem>
64+
<BreadcrumbLink as-child>
65+
<RouterLink to="/admin/mcp-server-catalog">
66+
{{ t('mcpCatalog.title') }}
67+
</RouterLink>
68+
</BreadcrumbLink>
69+
</BreadcrumbItem>
70+
<BreadcrumbSeparator />
71+
<BreadcrumbItem>
72+
<Skeleton class="h-4 w-48" />
73+
</BreadcrumbItem>
74+
</BreadcrumbList>
75+
</Breadcrumb>
76+
</DsPageHeading>
77+
</template>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useRoute, useRouter } from 'vue-router'
4+
import { SettingsMenu, SettingsMenuGroup, SettingsMenuItem } from '@/components/ui/settings-menu'
5+
import type { McpServer } from '@/views/admin/mcp-server-catalog/types'
6+
7+
interface Props {
8+
server: McpServer
9+
serverId: string
10+
}
11+
12+
const props = defineProps<Props>()
13+
const route = useRoute()
14+
const router = useRouter()
15+
16+
// Navigation menu items
17+
const menuItems = computed(() => [
18+
{ id: 'general', label: 'General', path: `/admin/mcp-server-catalog/view/${props.serverId}/general` }
19+
])
20+
21+
// Map route names to section IDs
22+
const routeToSectionMap: Record<string, string> = {
23+
'AdminMcpCatalogViewGeneral': 'general',
24+
}
25+
26+
// Get current section from route name
27+
const currentSection = computed(() => {
28+
const routeName = route.name as string
29+
return routeToSectionMap[routeName] || 'general'
30+
})
31+
32+
// Navigate to a section (for mobile buttons)
33+
function navigateToSection(sectionId: string) {
34+
const item = menuItems.value.find(item => item.id === sectionId)
35+
if (item) {
36+
router.push(item.path)
37+
}
38+
}
39+
</script>
40+
41+
<template>
42+
<div class="flex flex-col space-y-8 md:flex-row md:space-x-12 md:space-y-0">
43+
<!-- Desktop Sidebar Navigation -->
44+
<aside class="hidden md:block w-56 shrink-0">
45+
<SettingsMenu>
46+
<SettingsMenuGroup>
47+
<SettingsMenuItem
48+
v-for="item in menuItems"
49+
:key="item.id"
50+
:to="item.path"
51+
:active="currentSection === item.id"
52+
>
53+
{{ item.label }}
54+
</SettingsMenuItem>
55+
</SettingsMenuGroup>
56+
</SettingsMenu>
57+
</aside>
58+
59+
<!-- Mobile Navigation -->
60+
<div class="block md:hidden">
61+
<nav class="flex space-x-1 p-1 bg-muted/50 rounded-lg">
62+
<button
63+
v-for="item in menuItems"
64+
:key="item.id"
65+
@click="navigateToSection(item.id)"
66+
class="flex-1 px-3 py-2 text-sm font-medium rounded-md transition-colors"
67+
:class="currentSection === item.id
68+
? 'bg-background text-foreground shadow-sm'
69+
: 'text-muted-foreground hover:text-foreground'"
70+
>
71+
{{ item.label }}
72+
</button>
73+
</nav>
74+
</div>
75+
76+
<!-- Content Area Slot -->
77+
<div class="flex-1">
78+
<slot />
79+
</div>
80+
</div>
81+
</template>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as McpServerCatalogDetailPageHeading } from './McpServerCatalogDetailPageHeading.vue'
2+
export { default as McpServerCatalogDetailTabs } from './McpServerCatalogDetailTabs.vue'

services/frontend/src/router/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,15 @@ const routes: RouteRecordRaw[] = [
355355
},
356356
{
357357
path: 'mcp-server-catalog/view/:id',
358-
name: 'AdminMcpServerCatalogView',
359-
component: () => import('../views/admin/mcp-server-catalog/view/[id].vue'),
358+
redirect: (to) => ({
359+
name: 'AdminMcpCatalogViewGeneral',
360+
params: { id: to.params.id }
361+
}),
362+
},
363+
{
364+
path: 'mcp-server-catalog/view/:id/general',
365+
name: 'AdminMcpCatalogViewGeneral',
366+
component: () => import('../views/admin/mcp-server-catalog/view/[id]/general.vue'),
360367
},
361368
{
362369
path: 'mcp-server-catalog/edit/:id',

0 commit comments

Comments
 (0)