|
| 1 | +<script setup lang="ts"> |
| 2 | +import { ref, onMounted, onUnmounted, computed } from 'vue' |
| 3 | +import { useRouter } from 'vue-router' |
| 4 | +import { Skeleton } from '@/components/ui/skeleton' |
| 5 | +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' |
| 6 | +import { Badge } from '@/components/ui/badge' |
| 7 | +import { Alert, AlertDescription } from '@/components/ui/alert' |
| 8 | +import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty' |
| 9 | +import { AlertCircle, Server } from 'lucide-vue-next' |
| 10 | +import NavbarLayout from '@/components/NavbarLayout.vue' |
| 11 | +import { TeamManageHeader, TeamManageTabs } from '@/components/teams/manage' |
| 12 | +import { useTeamCache } from '@/composables/teams/useTeamCache' |
| 13 | +import { useEventBus } from '@/composables/useEventBus' |
| 14 | +import { TeamService, type TeamMcpInstallation } from '@/services/teamService' |
| 15 | +
|
| 16 | +const router = useRouter() |
| 17 | +const eventBus = useEventBus() |
| 18 | +
|
| 19 | +const { |
| 20 | + team, |
| 21 | + isLoading: isLoadingTeam, |
| 22 | + error: teamError, |
| 23 | + teamId, |
| 24 | + loadAndSetTeam, |
| 25 | + initializeCache, |
| 26 | + setupWatchers, |
| 27 | + cleanupWatchers |
| 28 | +} = useTeamCache() |
| 29 | +
|
| 30 | +// MCP installations state |
| 31 | +const installations = ref<TeamMcpInstallation[]>([]) |
| 32 | +const isLoadingInstallations = ref(true) |
| 33 | +const installationsError = ref<string | null>(null) |
| 34 | +
|
| 35 | +// Computed loading state |
| 36 | +const isLoading = computed(() => isLoadingTeam.value || isLoadingInstallations.value) |
| 37 | +const error = computed(() => teamError.value || installationsError.value) |
| 38 | +
|
| 39 | +// Handle team selection from sidebar |
| 40 | +const handleTeamSelected = (data: { teamId: string; teamName: string }) => { |
| 41 | + if (data.teamId !== teamId) { |
| 42 | + router.push(`/teams/manage/${data.teamId}/mcp-servers`) |
| 43 | + } |
| 44 | +} |
| 45 | +
|
| 46 | +// Load MCP installations |
| 47 | +async function loadInstallations() { |
| 48 | + isLoadingInstallations.value = true |
| 49 | + installationsError.value = null |
| 50 | +
|
| 51 | + try { |
| 52 | + installations.value = await TeamService.getTeamMcpInstallations(teamId) |
| 53 | + } catch (err) { |
| 54 | + installationsError.value = err instanceof Error ? err.message : 'Failed to load MCP servers' |
| 55 | + installations.value = [] |
| 56 | + } finally { |
| 57 | + isLoadingInstallations.value = false |
| 58 | + } |
| 59 | +} |
| 60 | +
|
| 61 | +// Get status badge variant |
| 62 | +function getStatusBadgeVariant(status: string) { |
| 63 | + switch (status) { |
| 64 | + case 'online': |
| 65 | + return 'default' |
| 66 | + case 'offline': |
| 67 | + return 'secondary' |
| 68 | + case 'error': |
| 69 | + return 'destructive' |
| 70 | + case 'provisioning': |
| 71 | + return 'outline' |
| 72 | + default: |
| 73 | + return 'outline' |
| 74 | + } |
| 75 | +} |
| 76 | +
|
| 77 | +// Get status badge class |
| 78 | +function getStatusBadgeClass(status: string) { |
| 79 | + switch (status) { |
| 80 | + case 'online': |
| 81 | + return 'bg-green-50 text-green-700 border-green-200' |
| 82 | + case 'offline': |
| 83 | + return 'bg-neutral-50 text-neutral-700 border-neutral-200' |
| 84 | + case 'error': |
| 85 | + return '' |
| 86 | + case 'provisioning': |
| 87 | + return 'bg-yellow-50 text-yellow-700 border-yellow-200' |
| 88 | + default: |
| 89 | + return '' |
| 90 | + } |
| 91 | +} |
| 92 | +
|
| 93 | +// Format date |
| 94 | +function formatDate(dateString: string | null): string { |
| 95 | + if (!dateString) return 'Never' |
| 96 | + return new Date(dateString).toLocaleDateString() |
| 97 | +} |
| 98 | +
|
| 99 | +// Navigate to installation details |
| 100 | +function navigateToInstallation(installationId: string) { |
| 101 | + router.push(`/mcp-server/view/${installationId}`) |
| 102 | +} |
| 103 | +
|
| 104 | +// Load data on component mount |
| 105 | +onMounted(async () => { |
| 106 | + initializeCache() |
| 107 | + await loadAndSetTeam() |
| 108 | + setupWatchers() |
| 109 | + await loadInstallations() |
| 110 | +
|
| 111 | + // Listen for team selection events from sidebar |
| 112 | + eventBus.on('team-selected', handleTeamSelected) |
| 113 | +}) |
| 114 | +
|
| 115 | +onUnmounted(() => { |
| 116 | + cleanupWatchers() |
| 117 | + eventBus.off('team-selected', handleTeamSelected) |
| 118 | +}) |
| 119 | +</script> |
| 120 | + |
| 121 | +<template> |
| 122 | + <NavbarLayout> |
| 123 | + <TeamManageHeader :team="team" :is-loading="isLoadingTeam" /> |
| 124 | + |
| 125 | + <div class="space-y-6 mt-6"> |
| 126 | + <!-- Tabs - Always visible when team is loaded --> |
| 127 | + <TeamManageTabs v-if="team" :team="team" :team-id="teamId"> |
| 128 | + <!-- Error State --> |
| 129 | + <Alert v-if="error" variant="destructive" class="mb-6"> |
| 130 | + <AlertCircle class="h-4 w-4" /> |
| 131 | + <AlertDescription> |
| 132 | + {{ error }} |
| 133 | + </AlertDescription> |
| 134 | + </Alert> |
| 135 | + |
| 136 | + <!-- Loading State for Content --> |
| 137 | + <div v-else-if="isLoading" class="space-y-4"> |
| 138 | + <Skeleton class="h-32 w-full rounded-lg" /> |
| 139 | + <Skeleton class="h-32 w-full rounded-lg" /> |
| 140 | + <Skeleton class="h-32 w-full rounded-lg" /> |
| 141 | + </div> |
| 142 | + |
| 143 | + <!-- Empty State --> |
| 144 | + <Empty v-else-if="installations.length === 0"> |
| 145 | + <EmptyHeader> |
| 146 | + <EmptyMedia variant="icon"> |
| 147 | + <Server /> |
| 148 | + </EmptyMedia> |
| 149 | + <EmptyTitle>No MCP servers installed</EmptyTitle> |
| 150 | + <EmptyDescription> |
| 151 | + This team has not installed any MCP servers yet. |
| 152 | + </EmptyDescription> |
| 153 | + </EmptyHeader> |
| 154 | + </Empty> |
| 155 | + |
| 156 | + <!-- MCP Installations Table --> |
| 157 | + <div v-else class="space-y-4"> |
| 158 | + <div class="rounded-md border"> |
| 159 | + <Table> |
| 160 | + <TableHeader> |
| 161 | + <TableRow> |
| 162 | + <TableHead>Name</TableHead> |
| 163 | + <TableHead>Type</TableHead> |
| 164 | + <TableHead>Status</TableHead> |
| 165 | + <TableHead>Runtime</TableHead> |
| 166 | + <TableHead>Created</TableHead> |
| 167 | + <TableHead>Last Used</TableHead> |
| 168 | + </TableRow> |
| 169 | + </TableHeader> |
| 170 | + <TableBody> |
| 171 | + <TableRow |
| 172 | + v-for="installation in installations" |
| 173 | + :key="installation.id" |
| 174 | + class="cursor-pointer hover:bg-muted/50" |
| 175 | + @click="navigateToInstallation(installation.id)" |
| 176 | + > |
| 177 | + <!-- Name with Icon --> |
| 178 | + <TableCell> |
| 179 | + <div class="flex items-center gap-2"> |
| 180 | + <img |
| 181 | + v-if="installation.server.icon_url" |
| 182 | + :src="installation.server.icon_url" |
| 183 | + :alt="installation.installation_name" |
| 184 | + class="h-6 w-6 rounded" |
| 185 | + /> |
| 186 | + <Server v-else class="h-6 w-6 text-muted-foreground" /> |
| 187 | + <span class="font-medium">{{ installation.installation_name }}</span> |
| 188 | + </div> |
| 189 | + </TableCell> |
| 190 | + |
| 191 | + <!-- Type --> |
| 192 | + <TableCell> |
| 193 | + <Badge variant="outline" class="text-xs"> |
| 194 | + {{ installation.installation_type }} |
| 195 | + </Badge> |
| 196 | + </TableCell> |
| 197 | + |
| 198 | + <!-- Status --> |
| 199 | + <TableCell> |
| 200 | + <Badge |
| 201 | + :variant="getStatusBadgeVariant(installation.status)" |
| 202 | + :class="getStatusBadgeClass(installation.status)" |
| 203 | + > |
| 204 | + {{ installation.status }} |
| 205 | + </Badge> |
| 206 | + </TableCell> |
| 207 | + |
| 208 | + <!-- Runtime --> |
| 209 | + <TableCell> |
| 210 | + <span class="text-sm text-muted-foreground">{{ installation.server.runtime }}</span> |
| 211 | + </TableCell> |
| 212 | + |
| 213 | + <!-- Created --> |
| 214 | + <TableCell> |
| 215 | + <span class="text-sm">{{ formatDate(installation.created_at) }}</span> |
| 216 | + </TableCell> |
| 217 | + |
| 218 | + <!-- Last Used --> |
| 219 | + <TableCell> |
| 220 | + <span class="text-sm text-muted-foreground">{{ formatDate(installation.last_used_at) }}</span> |
| 221 | + </TableCell> |
| 222 | + </TableRow> |
| 223 | + </TableBody> |
| 224 | + </Table> |
| 225 | + </div> |
| 226 | + |
| 227 | + <!-- Results Counter --> |
| 228 | + <div class="flex items-center justify-between px-4 py-4"> |
| 229 | + <div class="flex-1 text-sm text-muted-foreground"> |
| 230 | + {{ installations.length }} {{ installations.length === 1 ? 'server' : 'servers' }} installed |
| 231 | + </div> |
| 232 | + </div> |
| 233 | + </div> |
| 234 | + </TeamManageTabs> |
| 235 | + </div> |
| 236 | + </NavbarLayout> |
| 237 | +</template> |
0 commit comments