Skip to content

Commit c563ebc

Browse files
committed
feat(frontend): add logs tab and access control for team roles
1 parent d796783 commit c563ebc

File tree

11 files changed

+224
-0
lines changed

11 files changed

+224
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { McpInstallation } from '@/types/mcp-installations'
88
interface Props {
99
installation: McpInstallation
1010
installationId: string
11+
userTeamRole?: 'team_admin' | 'team_user' | null
1112
}
1213
1314
const props = defineProps<Props>()
@@ -20,6 +21,7 @@ const routeToTabMap: Record<string, string> = {
2021
'McpServerInstallationGeneral': 'general',
2122
'McpServerInstallationTools': 'tools',
2223
'McpServerInstallationRequests': 'requests',
24+
'McpServerInstallationLogs': 'logs',
2325
'McpServerInstallationConfig': 'config',
2426
'McpServerInstallationDangerZone': 'danger-zone',
2527
}
@@ -29,6 +31,7 @@ const tabToRouteMap: Record<string, string> = {
2931
'general': 'McpServerInstallationGeneral',
3032
'tools': 'McpServerInstallationTools',
3133
'requests': 'McpServerInstallationRequests',
34+
'logs': 'McpServerInstallationLogs',
3235
'config': 'McpServerInstallationConfig',
3336
'danger-zone': 'McpServerInstallationDangerZone',
3437
}
@@ -53,6 +56,7 @@ const activeTab = computed({
5356
<DsTabsItem value="general" label="General" />
5457
<DsTabsItem value="tools" :label="t('mcpInstallations.details.tools.title')" />
5558
<DsTabsItem value="requests" label="Requests" />
59+
<DsTabsItem v-if="userTeamRole === 'team_admin'" value="logs" label="Logs" />
5660
<DsTabsItem value="config" label="Configuration" />
5761
<DsTabsItem value="danger-zone" label="Danger Zone" />
5862
</DsTabs>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script setup lang="ts">
2+
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty'
3+
import { ShieldX } from 'lucide-vue-next'
4+
</script>
5+
6+
<template>
7+
<Empty>
8+
<EmptyHeader>
9+
<EmptyMedia variant="icon">
10+
<ShieldX />
11+
</EmptyMedia>
12+
<EmptyTitle>Access Denied</EmptyTitle>
13+
<EmptyDescription>
14+
You don't have permission to view logs. Only team administrators can access this section.
15+
</EmptyDescription>
16+
</EmptyHeader>
17+
</Empty>
18+
</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 { Card } from '@/components/ui/card'
3+
import { LogsNoAccess } from '@/components/mcp-server/installation'
4+
import type { McpInstallation } from '@/types/mcp-installations'
5+
6+
interface Props {
7+
installation: McpInstallation
8+
teamId: string
9+
userTeamRole?: 'team_admin' | 'team_user' | null
10+
}
11+
12+
defineProps<Props>()
13+
</script>
14+
15+
<template>
16+
<div>
17+
<!-- No Access State for Non-Admin Users -->
18+
<LogsNoAccess v-if="userTeamRole !== 'team_admin'" />
19+
20+
<!-- Logs Content for Admin Users -->
21+
<Card v-else class="rounded-lg border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950 p-6">
22+
<div class="space-y-6">
23+
<!-- Title -->
24+
<div>
25+
<h3 class="text-lg font-semibold">Logs</h3>
26+
<p class="text-sm text-muted-foreground mt-1">
27+
View and analyze MCP server logs
28+
</p>
29+
</div>
30+
31+
<!-- Lorem Ipsum Content -->
32+
<div class="space-y-4">
33+
<p class="text-neutral-800 dark:text-neutral-100">
34+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
35+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
36+
</p>
37+
38+
<p class="text-neutral-800 dark:text-neutral-100">
39+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
40+
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
41+
</p>
42+
43+
<div class="border-l-4 border-neutral-300 dark:border-neutral-700 pl-4 py-2 bg-neutral-50 dark:bg-neutral-900">
44+
<p class="text-sm text-neutral-600 dark:text-neutral-400 italic">
45+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
46+
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
47+
</p>
48+
</div>
49+
50+
<p class="text-neutral-800 dark:text-neutral-100">
51+
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos
52+
qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur,
53+
adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
54+
</p>
55+
56+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
57+
<div class="p-4 border border-neutral-200 dark:border-neutral-800 rounded-lg">
58+
<h4 class="font-medium text-neutral-800 dark:text-neutral-100 mb-2">Sample Log Entry</h4>
59+
<p class="text-sm text-neutral-600 dark:text-neutral-400">
60+
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti.
61+
</p>
62+
</div>
63+
64+
<div class="p-4 border border-neutral-200 dark:border-neutral-800 rounded-lg">
65+
<h4 class="font-medium text-neutral-800 dark:text-neutral-100 mb-2">Another Log Entry</h4>
66+
<p class="text-sm text-neutral-600 dark:text-neutral-400">
67+
Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio.
68+
</p>
69+
</div>
70+
</div>
71+
72+
<p class="text-neutral-800 dark:text-neutral-100">
73+
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates
74+
repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut
75+
reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
76+
</p>
77+
</div>
78+
</div>
79+
</Card>
80+
</div>
81+
</template>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export { default as ConfigurationView } from './ConfigurationView.vue'
44
export { default as DangerZone } from './DangerZone.vue'
55
export { default as InstallationTabs } from './InstallationTabs.vue'
66
export { default as RequestsTab } from './RequestsTab.vue'
7+
export { default as LogsTab } from './LogsTab.vue'
8+
export { default as LogsNoAccess } from './LogsNoAccess.vue'
79
export { default as RequestDetailSheet } from './RequestDetailSheet.vue'
810
export { default as ToolsMetricsPanel } from './ToolsMetricsPanel.vue'
911
export { default as GeneralMetricsPanel } from './GeneralMetricsPanel.vue'

services/frontend/src/router/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ const routes: RouteRecordRaw[] = [
176176
component: () => import('../views/mcp-server/installation/[id]/requests.vue'),
177177
meta: { requiresSetup: true },
178178
},
179+
{
180+
path: '/mcp-server/installation/:id/logs',
181+
name: 'McpServerInstallationLogs',
182+
component: () => import('../views/mcp-server/installation/[id]/logs.vue'),
183+
meta: { requiresSetup: true },
184+
},
179185
{
180186
path: '/mcp-server/installation/:id/config',
181187
name: 'McpServerInstallationConfig',

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ onUnmounted(() => {
7171
v-if="installation"
7272
:installation="installation"
7373
:installation-id="installationId"
74+
:user-team-role="userTeamRole"
7475
/>
7576

7677
<div v-if="error" class="text-red-500">

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ onUnmounted(() => {
6464
v-if="installation"
6565
:installation="installation"
6666
:installation-id="installationId"
67+
:user-team-role="userTeamRole"
6768
/>
6869

6970
<!-- Error State -->

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const { t } = useI18n()
1111
1212
const {
1313
installation,
14+
userTeamRole,
1415
isLoading,
1516
error,
1617
installationId,
@@ -78,6 +79,7 @@ onUnmounted(() => {
7879
v-if="installation"
7980
:installation="installation"
8081
:installation-id="installationId"
82+
:user-team-role="userTeamRole"
8183
/>
8284

8385
<!-- Error State -->
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<script setup lang="ts">
2+
import { ref, onMounted, onUnmounted, watch } from 'vue'
3+
import { useI18n } from 'vue-i18n'
4+
import { Skeleton } from '@/components/ui/skeleton'
5+
import NavbarLayout from '@/components/NavbarLayout.vue'
6+
import { LogsTab, InstallationTabs, InstallationPageHeading } from '@/components/mcp-server/installation'
7+
import { useMcpInstallationCache, useStatusStream } from '@/composables/mcp-server/installation'
8+
import { getEnv } from '@/utils/env'
9+
10+
const { t } = useI18n()
11+
12+
const {
13+
installation,
14+
currentTeam,
15+
userTeamRole,
16+
isLoading,
17+
error,
18+
installationId,
19+
loadAndSetInstallation,
20+
initializeCache,
21+
setupWatchers,
22+
cleanupWatchers
23+
} = useMcpInstallationCache()
24+
25+
const { statusData, connect, disconnect } = useStatusStream()
26+
27+
let currentStreamUrl: string | null = null
28+
const refreshKey = ref(0)
29+
30+
const handleRefresh = async () => {
31+
// Reload installation data
32+
await loadAndSetInstallation()
33+
34+
// Reconnect status stream if we have installation data
35+
if (installation.value?.team_id && installation.value?.id) {
36+
const baseUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL')
37+
const url = `${baseUrl}/api/teams/${installation.value.team_id}/mcp/installations/${installation.value.id}/status/stream`
38+
disconnect()
39+
currentStreamUrl = url
40+
connect(url)
41+
}
42+
43+
// Force component refresh by incrementing key
44+
refreshKey.value++
45+
}
46+
47+
onMounted(async () => {
48+
initializeCache()
49+
await loadAndSetInstallation()
50+
setupWatchers()
51+
})
52+
53+
watch(installation, (newInstallation) => {
54+
if (newInstallation?.team_id && newInstallation?.id) {
55+
const baseUrl = getEnv('VITE_DEPLOYSTACK_BACKEND_URL')
56+
const url = `${baseUrl}/api/teams/${newInstallation.team_id}/mcp/installations/${newInstallation.id}/status/stream`
57+
58+
if (url !== currentStreamUrl) {
59+
currentStreamUrl = url
60+
connect(url)
61+
}
62+
}
63+
})
64+
65+
onUnmounted(() => {
66+
cleanupWatchers()
67+
disconnect()
68+
})
69+
</script>
70+
71+
<template>
72+
<NavbarLayout>
73+
<InstallationPageHeading :installation="installation" :status-data="statusData" @refresh="handleRefresh" />
74+
75+
<div class="space-y-6 mt-6">
76+
<!-- Tabs - Always visible when installation is loaded -->
77+
<InstallationTabs
78+
v-if="installation"
79+
:installation="installation"
80+
:installation-id="installationId"
81+
:user-team-role="userTeamRole"
82+
/>
83+
84+
<!-- Error State -->
85+
<div v-if="error" class="text-red-500">
86+
{{ t('mcpInstallations.view.errorLoading', { error }) }}
87+
</div>
88+
89+
<!-- Loading State for Content -->
90+
<div v-else-if="isLoading" class="space-y-4">
91+
<Skeleton class="h-32 w-full rounded-lg" />
92+
<Skeleton class="h-32 w-full rounded-lg" />
93+
<Skeleton class="h-32 w-full rounded-lg" />
94+
</div>
95+
96+
<!-- Logs Content -->
97+
<LogsTab
98+
v-else-if="installation && currentTeam"
99+
:key="refreshKey"
100+
:installation="installation"
101+
:team-id="currentTeam.id"
102+
:user-team-role="userTeamRole"
103+
/>
104+
</div>
105+
</NavbarLayout>
106+
</template>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const { t } = useI18n()
1212
const {
1313
installation,
1414
currentTeam,
15+
userTeamRole,
1516
isLoading,
1617
error,
1718
installationId,
@@ -77,6 +78,7 @@ onUnmounted(() => {
7778
v-if="installation"
7879
:installation="installation"
7980
:installation-id="installationId"
81+
:user-team-role="userTeamRole"
8082
/>
8183

8284
<!-- Error State -->

0 commit comments

Comments
 (0)