Skip to content

Commit 703416c

Browse files
author
Lasim
committed
feat(frontend): add website URL display in MCP server card
1 parent 7356c2b commit 703416c

File tree

11 files changed

+286
-84
lines changed

11 files changed

+286
-84
lines changed

services/frontend/src/components/mcp-server/McpServerSquareCard.vue

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
HoverCardContent,
99
HoverCardTrigger,
1010
} from '@/components/ui/hover-card'
11-
import { Github, Star } from 'lucide-vue-next'
11+
import { Github, Star, Globe } from 'lucide-vue-next'
1212
import type { McpServer } from '@/views/admin/mcp-server-catalog/types'
1313
import McpServerAvatar from './McpServerAvatar.vue'
1414
@@ -104,14 +104,14 @@ const truncateServerName = (name: string, maxLength: number = 30) => {
104104
{{ server.runtime }}
105105
</Badge>
106106
</div>
107-
<div class="mt-6 flex w-full flex-none gap-x-4 items-center border-t border-gray-900/5 px-6 pt-6">
107+
<!-- Show repository URL if available -->
108+
<div v-if="server.repository_url" class="mt-6 flex w-full flex-none gap-x-4 items-center border-t border-gray-900/5 px-6 pt-6">
108109
<dt class="flex-none">
109110
<span class="sr-only">{{ t('mcpInstallations.view.fields.repository') }}</span>
110111
<Github class="h-4 w-4 text-gray-400" aria-hidden="true" />
111112
</dt>
112113
<dd class="text-sm/6 font-medium text-gray-900 min-w-0 flex-1">
113114
<a
114-
v-if="server.repository_url"
115115
:href="server.repository_url"
116116
target="_blank"
117117
rel="noopener noreferrer"
@@ -120,13 +120,30 @@ const truncateServerName = (name: string, maxLength: number = 30) => {
120120
>
121121
{{ server.repository_url.replace('https://github.com/', '').replace('https://gitlab.com/', '').replace('https://bitbucket.org/', '') }}
122122
</a>
123-
<span v-else class="text-gray-500 truncate block">{{ t('mcpInstallations.view.values.notProvided') }}</span>
124123
</dd>
125124
<dd v-if="server.github_stars !== null && server.github_stars !== undefined" class="flex items-center gap-1 text-sm/6 text-gray-600">
126125
<Star class="h-4 w-4 text-yellow-500 fill-yellow-500" aria-hidden="true" />
127126
<span>{{ server.github_stars.toLocaleString() }}</span>
128127
</dd>
129128
</div>
129+
<!-- Show website URL if no repository URL but website URL is available -->
130+
<div v-else-if="server.website_url" class="mt-6 flex w-full flex-none gap-x-4 items-center border-t border-gray-900/5 px-6 pt-6">
131+
<dt class="flex-none">
132+
<span class="sr-only">Website</span>
133+
<Globe class="h-4 w-4 text-gray-400" aria-hidden="true" />
134+
</dt>
135+
<dd class="text-sm/6 font-medium text-gray-900 min-w-0 flex-1">
136+
<a
137+
:href="server.website_url"
138+
target="_blank"
139+
rel="noopener noreferrer"
140+
class="hover:underline truncate block"
141+
:title="server.website_url"
142+
>
143+
{{ server.website_url.replace('https://', '').replace('http://', '') }}
144+
</a>
145+
</dd>
146+
</div>
130147
<div class="mt-4 flex w-full flex-none gap-x-4 px-6">
131148
<dd class="text-sm text-gray-600 line-clamp-3 min-h-[3.75rem]">
132149
{{ getServerDescription(server) }}

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

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
44
import { useI18n } from 'vue-i18n'
55
import { useRoute, useRouter } from 'vue-router'
66
import { Button } from '@/components/ui/button'
7+
import {
8+
Sheet,
9+
SheetContent,
10+
SheetFooter,
11+
SheetHeader,
12+
SheetTitle,
13+
} from '@/components/ui/sheet'
14+
import { Badge } from '@/components/ui/badge'
15+
import { Globe, Github, Package, Code, User, Layers, Shield } from 'lucide-vue-next'
716
import { DsProgressSteps, type ProgressStep } from '@/components/ui/ds-progress-steps'
817
import { toast } from 'vue-sonner'
918
import { McpInstallationService } from '@/services/mcpInstallationService'
@@ -64,6 +73,7 @@ interface InstallationFormData {
6473
// State
6574
const currentStep = ref(0)
6675
const isSubmitting = ref(false)
76+
const isDetailsSheetOpen = ref(false)
6777
6878
const environmentValidation = ref({
6979
isValid: true,
@@ -655,7 +665,7 @@ onUnmounted(() => {
655665
>
656666
<div class="px-6 md:flex md:items-center md:justify-between md:space-x-6 lg:space-x-8">
657667
<!-- Avatar Image -->
658-
<div class="flex-shrink-0 mb-4 md:mb-0">
668+
<div class="flex-shrink-0 mb-4 md:mb-0 flex justify-center md:justify-start">
659669
<McpServerAvatar
660670
:icon-url="formData.server.server_data.icon_url"
661671
:server-name="formData.server.server_data.name"
@@ -687,6 +697,17 @@ onUnmounted(() => {
687697
{{ formData.server.server_data.description }}
688698
</p>
689699
</div>
700+
701+
<!-- Details Button -->
702+
<div class="flex items-center w-full md:w-auto mt-4 md:mt-0">
703+
<Button
704+
variant="outline"
705+
@click="isDetailsSheetOpen = true"
706+
class="bg-white w-full md:w-auto"
707+
>
708+
Details
709+
</Button>
710+
</div>
690711
</div>
691712
</div>
692713

@@ -838,5 +859,157 @@ onUnmounted(() => {
838859
</template>
839860
</DsProgressSteps>
840861
</template>
862+
863+
<!-- Details Sheet -->
864+
<Sheet v-model:open="isDetailsSheetOpen">
865+
<SheetContent class="overflow-y-auto">
866+
<SheetHeader>
867+
<SheetTitle>Server Details</SheetTitle>
868+
</SheetHeader>
869+
870+
<div v-if="formData.server.server_data" class="pb-4 px-4 space-y-6">
871+
<!-- Basic Information Section -->
872+
<div class="space-y-4">
873+
874+
<!-- Description -->
875+
<div class="space-y-1">
876+
<dt class="text-xs font-medium text-gray-500">Description</dt>
877+
<dd class="text-sm text-gray-900">
878+
{{ formData.server.server_data.description || 'No description available' }}
879+
</dd>
880+
</div>
881+
882+
<!-- Website URL -->
883+
<div v-if="formData.server.server_data.website_url" class="space-y-1">
884+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
885+
<Globe class="h-3 w-3" />
886+
Website
887+
</dt>
888+
<dd class="text-sm">
889+
<a
890+
:href="formData.server.server_data.website_url"
891+
target="_blank"
892+
rel="noopener noreferrer"
893+
class="text-blue-600 hover:underline"
894+
>
895+
{{ formData.server.server_data.website_url }}
896+
</a>
897+
</dd>
898+
</div>
899+
900+
<!-- GitHub URL -->
901+
<div v-if="formData.server.server_data.repository_url" class="space-y-1">
902+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
903+
<Github class="h-3 w-3" />
904+
Repository
905+
</dt>
906+
<dd class="text-sm">
907+
<a
908+
:href="formData.server.server_data.repository_url"
909+
target="_blank"
910+
rel="noopener noreferrer"
911+
class="text-blue-600 hover:underline"
912+
>
913+
{{ formData.server.server_data.repository_url }}
914+
</a>
915+
</dd>
916+
</div>
917+
918+
<!-- Runtime -->
919+
<div class="space-y-1">
920+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
921+
<Layers class="h-3 w-3" />
922+
Runtime
923+
</dt>
924+
<dd class="text-sm">
925+
<Badge variant="secondary" class="font-mono text-xs">
926+
{{ formData.server.server_data.runtime }}
927+
</Badge>
928+
</dd>
929+
</div>
930+
931+
<!-- Language -->
932+
<div v-if="formData.server.server_data.language && formData.server.server_data.language.toLowerCase() !== 'http'" class="space-y-1">
933+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
934+
<Code class="h-3 w-3" />
935+
Language
936+
</dt>
937+
<dd class="text-sm">
938+
<Badge variant="outline" class="text-xs">
939+
{{ formData.server.server_data.language }}
940+
</Badge>
941+
</dd>
942+
</div>
943+
944+
<!-- Author Name -->
945+
<div v-if="formData.server.server_data.author_name" class="space-y-1">
946+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
947+
<User class="h-3 w-3" />
948+
Author
949+
</dt>
950+
<dd class="text-sm text-gray-900">
951+
{{ formData.server.server_data.author_name }}
952+
</dd>
953+
</div>
954+
955+
<!-- Transport Type -->
956+
<div class="space-y-1">
957+
<dt class="text-xs font-medium text-gray-500">Transport Type</dt>
958+
<dd class="text-sm">
959+
<Badge variant="outline" class="text-xs uppercase">
960+
{{ formData.server.server_data.transport_type }}
961+
</Badge>
962+
</dd>
963+
</div>
964+
</div>
965+
966+
<!-- Specifications Section -->
967+
<div class="space-y-4 pt-4 border-t border-gray-200">
968+
<h3 class="text-sm font-semibold text-gray-900">Specifications</h3>
969+
970+
<!-- Requires OAuth -->
971+
<div v-if="formData.server.server_data.requires_oauth" class="space-y-1">
972+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
973+
<Shield class="h-3 w-3" />
974+
Authentication
975+
</dt>
976+
<dd class="text-sm">
977+
<Badge variant="default" class="text-xs">
978+
Requires OAuth
979+
</Badge>
980+
</dd>
981+
</div>
982+
983+
<!-- Packages (if runtime !== 'http') -->
984+
<div v-if="formData.server.server_data.runtime !== 'http' && formData.server.server_data.packages" class="space-y-1">
985+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
986+
<Package class="h-3 w-3" />
987+
Packages
988+
</dt>
989+
<dd class="text-sm">
990+
<pre class="bg-gray-50 border border-gray-200 rounded p-3 text-xs overflow-x-auto">{{ JSON.stringify(formData.server.server_data.packages, null, 2) }}</pre>
991+
</dd>
992+
</div>
993+
994+
<!-- Remotes (if runtime === 'http') -->
995+
<div v-if="formData.server.server_data.runtime === 'http' && formData.server.server_data.remotes" class="space-y-1">
996+
<dt class="text-xs font-medium text-gray-500 flex items-center gap-1">
997+
<Globe class="h-3 w-3" />
998+
Remotes
999+
</dt>
1000+
<dd class="text-sm">
1001+
<pre class="bg-gray-50 border border-gray-200 rounded p-3 text-xs overflow-x-auto">{{ JSON.stringify(formData.server.server_data.remotes, null, 2) }}</pre>
1002+
</dd>
1003+
</div>
1004+
</div>
1005+
</div>
1006+
1007+
<SheetFooter>
1008+
<Button @click="isDetailsSheetOpen = false" variant="outline">
1009+
Close
1010+
</Button>
1011+
</SheetFooter>
1012+
</SheetContent>
1013+
</Sheet>
8411014
</div>
8421015
</template>
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
2-
import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'reka-ui'
2+
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
3+
import { DialogRoot, useForwardPropsEmits } from "reka-ui"
34
45
const props = defineProps<DialogRootProps>()
56
const emits = defineEmits<DialogRootEmits>()
@@ -8,10 +9,7 @@ const forwarded = useForwardPropsEmits(props, emits)
89
</script>
910

1011
<template>
11-
<DialogRoot
12-
data-slot="sheet"
13-
v-bind="forwarded"
14-
>
12+
<DialogRoot v-bind="forwarded">
1513
<slot />
1614
</DialogRoot>
1715
</template>
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
<script setup lang="ts">
2-
import { DialogClose, type DialogCloseProps } from 'reka-ui'
2+
import type { DialogCloseProps } from "reka-ui"
3+
import { DialogClose } from "reka-ui"
34
45
const props = defineProps<DialogCloseProps>()
56
</script>
67

78
<template>
8-
<DialogClose
9-
data-slot="sheet-close"
10-
v-bind="props"
11-
>
9+
<DialogClose v-bind="props">
1210
<slot />
1311
</DialogClose>
1412
</template>

services/frontend/src/components/ui/sheet/SheetContent.vue

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,52 @@
11
<script setup lang="ts">
2-
import type { HTMLAttributes } from 'vue'
3-
import { reactiveOmit } from '@vueuse/core'
4-
import { X } from 'lucide-vue-next'
2+
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
3+
import type { HTMLAttributes } from "vue"
4+
import type { SheetVariants } from "."
5+
import { reactiveOmit } from "@vueuse/core"
6+
import { X } from "lucide-vue-next"
57
import {
68
DialogClose,
79
DialogContent,
8-
type DialogContentEmits,
9-
type DialogContentProps,
10+
DialogOverlay,
1011
DialogPortal,
1112
useForwardPropsEmits,
12-
} from 'reka-ui'
13-
import { cn } from '@/lib/utils'
14-
import SheetOverlay from './SheetOverlay.vue'
13+
} from "reka-ui"
14+
import { cn } from "@/lib/utils"
15+
import { sheetVariants } from "."
1516
1617
interface SheetContentProps extends DialogContentProps {
17-
class?: HTMLAttributes['class']
18-
side?: 'top' | 'right' | 'bottom' | 'left'
18+
class?: HTMLAttributes["class"]
19+
side?: SheetVariants["side"]
1920
}
2021
2122
defineOptions({
2223
inheritAttrs: false,
2324
})
2425
25-
const props = withDefaults(defineProps<SheetContentProps>(), {
26-
side: 'right',
27-
})
26+
const props = defineProps<SheetContentProps>()
27+
2828
const emits = defineEmits<DialogContentEmits>()
2929
30-
const delegatedProps = reactiveOmit(props, 'class', 'side')
30+
const delegatedProps = reactiveOmit(props, "class", "side")
3131
3232
const forwarded = useForwardPropsEmits(delegatedProps, emits)
3333
</script>
3434

3535
<template>
3636
<DialogPortal>
37-
<SheetOverlay />
37+
<DialogOverlay
38+
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
39+
/>
3840
<DialogContent
39-
data-slot="sheet-content"
40-
:class="cn(
41-
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
42-
side === 'right'
43-
&& 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
44-
side === 'left'
45-
&& 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
46-
side === 'top'
47-
&& 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
48-
side === 'bottom'
49-
&& 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
50-
props.class)"
41+
:class="cn(sheetVariants({ side }), props.class)"
5142
v-bind="{ ...forwarded, ...$attrs }"
5243
>
5344
<slot />
5445

5546
<DialogClose
56-
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
47+
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"
5748
>
58-
<X class="size-4" />
59-
<span class="sr-only">Close</span>
49+
<X class="w-4 h-4 text-muted-foreground" />
6050
</DialogClose>
6151
</DialogContent>
6252
</DialogPortal>

0 commit comments

Comments
 (0)