Skip to content

Commit 62753b9

Browse files
committed
refactor(frontend): create reusable InstallationPageHeading with skeleton loading
1 parent 8c02a52 commit 62753b9

File tree

9 files changed

+180
-248
lines changed

9 files changed

+180
-248
lines changed

services/frontend/src/components/mcp-server/installation/InstallationInfo.vue

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { computed } from 'vue'
33
import { useI18n } from 'vue-i18n'
44
import { Badge } from '@/components/ui/badge'
55
import { Github, ExternalLink, Calendar, Tag } from 'lucide-vue-next'
6-
import McpServerAvatar from '@/components/mcp-server/McpServerAvatar.vue'
76
import InstallationStatusBadge from './InstallationStatusBadge.vue'
87
import type { McpInstallation, InstallationStatusData } from '@/types/mcp-installations'
98
@@ -65,17 +64,9 @@ const statusUpdatedAt = computed(() => props.statusData?.status_updated_at || nu
6564

6665
<template>
6766
<div v-if="installation && server">
68-
<div class="px-4 sm:px-0 flex items-center gap-4">
69-
<McpServerAvatar
70-
:icon-url="server.icon_url"
71-
:server-name="server.name"
72-
size="md"
73-
rounded="lg"
74-
/>
75-
<div>
76-
<h3 class="text-base/7 font-semibold text-gray-900">{{ t('mcpInstallations.details.installationDetails.title') }}</h3>
77-
<p class="mt-1 max-w-2xl text-sm/6 text-gray-500">{{ t('mcpInstallations.details.installationDetails.description') }}</p>
78-
</div>
67+
<div class="px-4 sm:px-0">
68+
<h3 class="text-base/7 font-semibold text-gray-900">{{ t('mcpInstallations.details.installationDetails.title') }}</h3>
69+
<p class="mt-1 max-w-2xl text-sm/6 text-gray-500">{{ t('mcpInstallations.details.installationDetails.description') }}</p>
7970
</div>
8071
<div class="mt-6 border-t border-gray-100">
8172
<dl class="divide-y divide-gray-100">
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<script setup lang="ts">
2+
import { DsPageHeading } from '@/components/ui/ds-page-heading'
3+
import { Skeleton } from '@/components/ui/skeleton'
4+
import {
5+
Breadcrumb,
6+
BreadcrumbItem,
7+
BreadcrumbLink,
8+
BreadcrumbList,
9+
BreadcrumbPage,
10+
BreadcrumbSeparator,
11+
} from '@/components/ui/breadcrumb'
12+
import McpServerAvatar from '@/components/mcp-server/McpServerAvatar.vue'
13+
import InstallationStatusBadge from './InstallationStatusBadge.vue'
14+
import { useI18n } from 'vue-i18n'
15+
import type { McpInstallation, InstallationStatusData } from '@/types/mcp-installations'
16+
17+
interface Props {
18+
installation: McpInstallation | null
19+
statusData: InstallationStatusData | null
20+
isLoading?: boolean
21+
}
22+
23+
withDefaults(defineProps<Props>(), {
24+
isLoading: false
25+
})
26+
27+
const { t } = useI18n()
28+
</script>
29+
30+
<template>
31+
<DsPageHeading v-if="installation" :title="installation.installation_name" :show-border="false">
32+
<template #icon>
33+
<Skeleton v-if="!installation.server" class="h-12 w-12 rounded-md" />
34+
<McpServerAvatar
35+
v-else
36+
:icon-url="installation.server.icon_url"
37+
:server-name="installation.server.name"
38+
size="md"
39+
/>
40+
</template>
41+
42+
<Breadcrumb>
43+
<BreadcrumbList>
44+
<BreadcrumbItem>
45+
<BreadcrumbLink as-child>
46+
<RouterLink to="/mcp-server">
47+
{{ t('mcpInstallations.title') }}
48+
</RouterLink>
49+
</BreadcrumbLink>
50+
</BreadcrumbItem>
51+
<BreadcrumbSeparator />
52+
<BreadcrumbItem>
53+
<BreadcrumbPage>{{ installation.installation_name }}</BreadcrumbPage>
54+
</BreadcrumbItem>
55+
</BreadcrumbList>
56+
</Breadcrumb>
57+
58+
<template #actions>
59+
<InstallationStatusBadge :status-data="statusData" size="default" />
60+
</template>
61+
</DsPageHeading>
62+
<DsPageHeading v-else :title="t('mcpInstallations.title')" :show-border="false">
63+
<Breadcrumb>
64+
<BreadcrumbList>
65+
<BreadcrumbItem>
66+
<BreadcrumbLink as-child>
67+
<RouterLink to="/mcp-server">
68+
{{ t('mcpInstallations.title') }}
69+
</RouterLink>
70+
</BreadcrumbLink>
71+
</BreadcrumbItem>
72+
<BreadcrumbSeparator />
73+
<BreadcrumbItem>
74+
<Skeleton class="h-4 w-48" />
75+
</BreadcrumbItem>
76+
</BreadcrumbList>
77+
</Breadcrumb>
78+
</DsPageHeading>
79+
</template>

services/frontend/src/components/mcp-server/installation/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { default as RequestsTab } from './RequestsTab.vue'
77
export { default as RequestDetailSheet } from './RequestDetailSheet.vue'
88
export { default as ToolsMetricsPanel } from './ToolsMetricsPanel.vue'
99
export { default as InstallationStatusBadge } from './InstallationStatusBadge.vue'
10+
export { default as InstallationPageHeading } from './InstallationPageHeading.vue'

services/frontend/src/components/ui/ds-page-heading/DsPageHeading.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ const props = withDefaults(defineProps<Props>(), {
2121
<!-- Left: Title, description, and optional content -->
2222
<div class="flex flex-col items-stretch justify-start flex-1 w-full gap-4">
2323
<div class="flex flex-col gap-1">
24-
<h1 class="text-[32px] tracking-[-0.79px] font-medium leading-10 text-neutral-900">
25-
{{ title }}
26-
</h1>
24+
<div class="flex items-center gap-3">
25+
<slot name="icon" />
26+
<h1 class="text-[32px] tracking-[-0.79px] font-medium leading-10 text-neutral-900">
27+
{{ title }}
28+
</h1>
29+
</div>
2730
<p v-if="description" class="text-muted-foreground">
2831
{{ description }}
2932
</p>

services/frontend/src/views/mcp-server/installation/[id]/config.vue

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
<script setup lang="ts">
2-
import { onMounted, onUnmounted, computed } from 'vue'
3-
import { useI18n } from 'vue-i18n'
4-
import { DsPageHeading } from '@/components/ui/ds-page-heading'
2+
import { onMounted, onUnmounted, computed, watch } from 'vue'
53
import { Skeleton } from '@/components/ui/skeleton'
6-
import {
7-
Breadcrumb,
8-
BreadcrumbItem,
9-
BreadcrumbLink,
10-
BreadcrumbList,
11-
BreadcrumbPage,
12-
BreadcrumbSeparator,
13-
} from '@/components/ui/breadcrumb'
144
import NavbarLayout from '@/components/NavbarLayout.vue'
15-
import { ConfigurationView, InstallationTabs } from '@/components/mcp-server/installation'
16-
import { useMcpInstallationCache } from '@/composables/mcp-server/installation'
5+
import { ConfigurationView, InstallationTabs, InstallationPageHeading } from '@/components/mcp-server/installation'
6+
import { useMcpInstallationCache, useStatusStream } from '@/composables/mcp-server/installation'
177
import { useEventBus } from '@/composables/useEventBus'
188
import type { McpInstallation } from '@/types/mcp-installations'
9+
import { getEnv } from '@/utils/env'
10+
import { useI18n } from 'vue-i18n'
1911
2012
const { t } = useI18n()
2113
const eventBus = useEventBus()
@@ -42,53 +34,37 @@ const handleInstallationUpdated = (updatedInstallation: McpInstallation) => {
4234
eventBus.emit('mcp-installations-updated')
4335
}
4436
37+
const { statusData, connect, disconnect } = useStatusStream()
38+
39+
let currentStreamUrl: string | null = null
40+
4541
onMounted(async () => {
4642
initializeCache()
4743
await loadAndSetInstallation()
4844
setupWatchers()
4945
})
5046
47+
watch(installation, (newInstallation) => {
48+
if (newInstallation?.team_id && newInstallation?.id) {
49+
const baseUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL')
50+
const url = `${baseUrl}/api/teams/${newInstallation.team_id}/mcp/installations/${newInstallation.id}/status/stream`
51+
52+
if (url !== currentStreamUrl) {
53+
currentStreamUrl = url
54+
connect(url)
55+
}
56+
}
57+
})
58+
5159
onUnmounted(() => {
5260
cleanupWatchers()
61+
disconnect()
5362
})
5463
</script>
5564

5665
<template>
5766
<NavbarLayout>
58-
<DsPageHeading v-if="installation" :title="installation.installation_name" :show-border="false">
59-
<Breadcrumb>
60-
<BreadcrumbList>
61-
<BreadcrumbItem>
62-
<BreadcrumbLink as-child>
63-
<RouterLink to="/mcp-server">
64-
{{ t('mcpInstallations.title') }}
65-
</RouterLink>
66-
</BreadcrumbLink>
67-
</BreadcrumbItem>
68-
<BreadcrumbSeparator />
69-
<BreadcrumbItem>
70-
<BreadcrumbPage>{{ installation.installation_name }}</BreadcrumbPage>
71-
</BreadcrumbItem>
72-
</BreadcrumbList>
73-
</Breadcrumb>
74-
</DsPageHeading>
75-
<DsPageHeading v-else :title="t('mcpInstallations.title')" :show-border="false">
76-
<Breadcrumb>
77-
<BreadcrumbList>
78-
<BreadcrumbItem>
79-
<BreadcrumbLink as-child>
80-
<RouterLink to="/mcp-server">
81-
{{ t('mcpInstallations.title') }}
82-
</RouterLink>
83-
</BreadcrumbLink>
84-
</BreadcrumbItem>
85-
<BreadcrumbSeparator />
86-
<BreadcrumbItem>
87-
<Skeleton class="h-4 w-48" />
88-
</BreadcrumbItem>
89-
</BreadcrumbList>
90-
</Breadcrumb>
91-
</DsPageHeading>
67+
<InstallationPageHeading :installation="installation" :status-data="statusData" />
9268

9369
<div class="space-y-6 mt-6">
9470
<InstallationTabs

services/frontend/src/views/mcp-server/installation/[id]/danger-zone.vue

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
<script setup lang="ts">
2-
import { onMounted, onUnmounted, computed } from 'vue'
2+
import { onMounted, onUnmounted, computed, watch } from 'vue'
33
import { useI18n } from 'vue-i18n'
4-
import { DsPageHeading } from '@/components/ui/ds-page-heading'
54
import { Skeleton } from '@/components/ui/skeleton'
6-
import {
7-
Breadcrumb,
8-
BreadcrumbItem,
9-
BreadcrumbLink,
10-
BreadcrumbList,
11-
BreadcrumbPage,
12-
BreadcrumbSeparator,
13-
} from '@/components/ui/breadcrumb'
145
import NavbarLayout from '@/components/NavbarLayout.vue'
15-
import { DangerZone, InstallationTabs } from '@/components/mcp-server/installation'
16-
import { useMcpInstallationCache } from '@/composables/mcp-server/installation'
6+
import { DangerZone, InstallationTabs, InstallationPageHeading } from '@/components/mcp-server/installation'
7+
import { useMcpInstallationCache, useStatusStream } from '@/composables/mcp-server/installation'
8+
import { getEnv } from '@/utils/env'
179
1810
const { t } = useI18n()
1911
@@ -34,53 +26,37 @@ const canEditInstallation = computed(() => {
3426
return userTeamRole.value === 'team_admin'
3527
})
3628
29+
const { statusData, connect, disconnect } = useStatusStream()
30+
31+
let currentStreamUrl: string | null = null
32+
3733
onMounted(async () => {
3834
initializeCache()
3935
await loadAndSetInstallation()
4036
setupWatchers()
4137
})
4238
39+
watch(installation, (newInstallation) => {
40+
if (newInstallation?.team_id && newInstallation?.id) {
41+
const baseUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL')
42+
const url = `${baseUrl}/api/teams/${newInstallation.team_id}/mcp/installations/${newInstallation.id}/status/stream`
43+
44+
if (url !== currentStreamUrl) {
45+
currentStreamUrl = url
46+
connect(url)
47+
}
48+
}
49+
})
50+
4351
onUnmounted(() => {
4452
cleanupWatchers()
53+
disconnect()
4554
})
4655
</script>
4756

4857
<template>
4958
<NavbarLayout>
50-
<DsPageHeading v-if="installation" :title="installation.installation_name" :show-border="false">
51-
<Breadcrumb>
52-
<BreadcrumbList>
53-
<BreadcrumbItem>
54-
<BreadcrumbLink as-child>
55-
<RouterLink to="/mcp-server">
56-
{{ t('mcpInstallations.title') }}
57-
</RouterLink>
58-
</BreadcrumbLink>
59-
</BreadcrumbItem>
60-
<BreadcrumbSeparator />
61-
<BreadcrumbItem>
62-
<BreadcrumbPage>{{ installation.installation_name }}</BreadcrumbPage>
63-
</BreadcrumbItem>
64-
</BreadcrumbList>
65-
</Breadcrumb>
66-
</DsPageHeading>
67-
<DsPageHeading v-else :title="t('mcpInstallations.title')" :show-border="false">
68-
<Breadcrumb>
69-
<BreadcrumbList>
70-
<BreadcrumbItem>
71-
<BreadcrumbLink as-child>
72-
<RouterLink to="/mcp-server">
73-
{{ t('mcpInstallations.title') }}
74-
</RouterLink>
75-
</BreadcrumbLink>
76-
</BreadcrumbItem>
77-
<BreadcrumbSeparator />
78-
<BreadcrumbItem>
79-
<Skeleton class="h-4 w-48" />
80-
</BreadcrumbItem>
81-
</BreadcrumbList>
82-
</Breadcrumb>
83-
</DsPageHeading>
59+
<InstallationPageHeading :installation="installation" :status-data="statusData" />
8460

8561
<div class="space-y-6 mt-6">
8662
<!-- Tabs - Always visible when installation is loaded -->

services/frontend/src/views/mcp-server/installation/[id]/general.vue

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
<script setup lang="ts">
22
import { onMounted, onUnmounted, watch } from 'vue'
33
import { useI18n } from 'vue-i18n'
4-
import { DsPageHeading } from '@/components/ui/ds-page-heading'
54
import { Skeleton } from '@/components/ui/skeleton'
6-
import {
7-
Breadcrumb,
8-
BreadcrumbItem,
9-
BreadcrumbLink,
10-
BreadcrumbList,
11-
BreadcrumbPage,
12-
BreadcrumbSeparator,
13-
} from '@/components/ui/breadcrumb'
145
import NavbarLayout from '@/components/NavbarLayout.vue'
15-
import { InstallationInfo, InstallationTabs, InstallationStatusBadge } from '@/components/mcp-server/installation'
6+
import { InstallationInfo, InstallationTabs, InstallationPageHeading } from '@/components/mcp-server/installation'
167
import { useMcpInstallationCache, useStatusStream } from '@/composables/mcp-server/installation'
178
import { getEnv } from '@/utils/env'
189
@@ -61,44 +52,7 @@ onUnmounted(() => {
6152

6253
<template>
6354
<NavbarLayout>
64-
<DsPageHeading v-if="installation" :title="installation.installation_name" :show-border="false">
65-
<Breadcrumb>
66-
<BreadcrumbList>
67-
<BreadcrumbItem>
68-
<BreadcrumbLink as-child>
69-
<RouterLink to="/mcp-server">
70-
{{ t('mcpInstallations.title') }}
71-
</RouterLink>
72-
</BreadcrumbLink>
73-
</BreadcrumbItem>
74-
<BreadcrumbSeparator />
75-
<BreadcrumbItem>
76-
<BreadcrumbPage>{{ installation.installation_name }}</BreadcrumbPage>
77-
</BreadcrumbItem>
78-
</BreadcrumbList>
79-
</Breadcrumb>
80-
81-
<template #actions>
82-
<InstallationStatusBadge :status-data="statusData" size="default" />
83-
</template>
84-
</DsPageHeading>
85-
<DsPageHeading v-else :title="t('mcpInstallations.title')" :show-border="false">
86-
<Breadcrumb>
87-
<BreadcrumbList>
88-
<BreadcrumbItem>
89-
<BreadcrumbLink as-child>
90-
<RouterLink to="/mcp-server">
91-
{{ t('mcpInstallations.title') }}
92-
</RouterLink>
93-
</BreadcrumbLink>
94-
</BreadcrumbItem>
95-
<BreadcrumbSeparator />
96-
<BreadcrumbItem>
97-
<Skeleton class="h-4 w-48" />
98-
</BreadcrumbItem>
99-
</BreadcrumbList>
100-
</Breadcrumb>
101-
</DsPageHeading>
55+
<InstallationPageHeading :installation="installation" :status-data="statusData" />
10256

10357
<div class="space-y-6 mt-6">
10458
<!-- Tabs - Always visible when installation is loaded -->

0 commit comments

Comments
 (0)