@@ -4,20 +4,13 @@ import { useI18n } from 'vue-i18n'
44import { toast } from ' vue-sonner'
55import { useRequestsStream } from ' @/composables/mcp-server/installation'
66import { McpRequestLogsService } from ' @/services/mcpRequestLogsService'
7+ import { RequestDetailSheet } from ' @/components/mcp-server/installation'
78import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from ' @/components/ui/table'
8- import { Button } from ' @/components/ui/button'
99import { Alert , AlertDescription } from ' @/components/ui/alert'
1010import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from ' @/components/ui/select'
11- import {
12- Dialog ,
13- DialogContent ,
14- DialogDescription ,
15- DialogHeader ,
16- DialogTitle ,
17- } from ' @/components/ui/dialog'
1811import { HoverCard , HoverCardContent , HoverCardTrigger } from ' @/components/ui/hover-card'
1912import { Card } from ' @/components/ui/card'
20- import { AlertCircle , AlertTriangle , Eye , Radio , Copy , Check } from ' lucide-vue-next'
13+ import { AlertTriangle , Eye , Radio } from ' lucide-vue-next'
2114import type { McpInstallation } from ' @/types/mcp-installations'
2215import type { McpRequestLog } from ' @/types/mcp-request-logs'
2316
@@ -36,8 +29,7 @@ type ViewMode = 'live' | 'api'
3629const filter = ref <FilterType >(' all' )
3730const viewMode = ref <ViewMode >(' live' )
3831const selectedRequest = ref <McpRequestLog | null >(null )
39- const showDetailDialog = ref (false )
40- const copiedField = ref <string | null >(null )
32+ const showDetailSheet = ref (false )
4133
4234// Filtered requests based on filter selection
4335const filteredRequests = computed (() => {
@@ -115,33 +107,10 @@ function getUserTimezone(): string {
115107 return Intl .DateTimeFormat ().resolvedOptions ().timeZone
116108}
117109
118- // Format JSON for display
119- function formatJson(value : unknown ): string {
120- if (value === null || value === undefined ) return ' null'
121- try {
122- return JSON .stringify (value , null , 2 )
123- } catch {
124- return String (value )
125- }
126- }
127-
128- // Copy to clipboard
129- async function copyToClipboard(value : unknown , field : string ) {
130- try {
131- await navigator .clipboard .writeText (formatJson (value ))
132- copiedField .value = field
133- setTimeout (() => {
134- copiedField .value = null
135- }, 2000 )
136- } catch {
137- console .error (' Failed to copy to clipboard' )
138- }
139- }
140-
141- // Open detail dialog
110+ // Open detail sheet
142111function openDetail(request : McpRequestLog ) {
143112 selectedRequest .value = request
144- showDetailDialog .value = true
113+ showDetailSheet .value = true
145114}
146115
147116// Connect to SSE stream
@@ -261,7 +230,12 @@ onUnmounted(() => {
261230 </TableRow >
262231 </TableHeader >
263232 <TableBody >
264- <TableRow v-for =" request in filteredRequests" :key =" request.id" >
233+ <TableRow
234+ v-for =" request in filteredRequests"
235+ :key =" request.id"
236+ class =" cursor-pointer"
237+ @click =" openDetail(request)"
238+ >
265239 <TableCell class =" w-10 pr-0" >
266240 <AlertTriangle
267241 v-if =" !request.success"
@@ -315,104 +289,19 @@ onUnmounted(() => {
315289 {{ request.response_time_ms }}ms
316290 </TableCell >
317291 <TableCell class =" w-12" >
318- <Button variant =" ghost" size =" sm" @click =" openDetail(request)" >
319- <Eye class =" h-4 w-4" />
320- </Button >
292+ <Eye class =" h-4 w-4 text-muted-foreground" />
321293 </TableCell >
322294 </TableRow >
323295 </TableBody >
324296 </Table >
325297 </div >
326298 </Card >
327299
328- <!-- Detail Dialog -->
329- <Dialog v-model:open =" showDetailDialog" >
330- <DialogContent class =" max-w-3xl max-h-[80vh] overflow-y-auto" >
331- <DialogHeader >
332- <DialogTitle >{{ t('mcpInstallations.details.requests.detail.title') }}</DialogTitle >
333- <DialogDescription >
334- {{ selectedRequest?.tool_name }}
335- </DialogDescription >
336- </DialogHeader >
337-
338- <div v-if =" selectedRequest" class =" space-y-4 mt-4" >
339- <!-- Status and Timing -->
340- <div class =" grid grid-cols-2 gap-4" >
341- <div >
342- <div class =" text-sm font-medium text-muted-foreground mb-1" >
343- {{ t('mcpInstallations.details.requests.detail.status') }}
344- </div >
345- <div class =" flex items-center gap-2 text-sm" >
346- <AlertTriangle v-if =" !selectedRequest.success" class =" h-4 w-4 text-amber-500" />
347- <span >{{ selectedRequest.success ? t('mcpInstallations.details.requests.table.values.success') : t('mcpInstallations.details.requests.table.values.failed') }}</span >
348- </div >
349- </div >
350- <div >
351- <div class =" text-sm font-medium text-muted-foreground mb-1" >
352- {{ t('mcpInstallations.details.requests.detail.responseTime') }}
353- </div >
354- <div class =" text-sm tabular-nums" >{{ selectedRequest.response_time_ms }}ms</div >
355- </div >
356- </div >
357-
358- <!-- User and Timestamp -->
359- <div class =" grid grid-cols-2 gap-4" >
360- <div >
361- <div class =" text-sm font-medium text-muted-foreground mb-1" >
362- {{ t('mcpInstallations.details.requests.detail.user') }}
363- </div >
364- <div class =" text-sm" >
365- <div v-if =" selectedRequest.user" >{{ selectedRequest.user.user_name }}</div >
366- <div v-else class =" text-muted-foreground italic" >Unknown</div >
367- </div >
368- </div >
369- <div >
370- <div class =" text-sm font-medium text-muted-foreground mb-1" >
371- {{ t('mcpInstallations.details.requests.detail.timestamp') }}
372- </div >
373- <div class =" text-sm font-mono tabular-nums" >{{ formatLocalTimestamp(selectedRequest.created_at) }}</div >
374- </div >
375- </div >
376-
377- <!-- Error Message (if failed) -->
378- <div v-if =" !selectedRequest.success && selectedRequest.error_message" >
379- <div class =" text-sm font-medium text-muted-foreground mb-1" >
380- {{ t('mcpInstallations.details.requests.detail.error') }}
381- </div >
382- <div class =" bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-3 text-sm text-red-800 dark:text-red-300" >
383- {{ selectedRequest.error_message }}
384- </div >
385- </div >
386-
387- <!-- Parameters -->
388- <div >
389- <div class =" flex items-center justify-between mb-1" >
390- <div class =" text-sm font-medium text-muted-foreground" >
391- {{ t('mcpInstallations.details.requests.detail.parameters') }}
392- </div >
393- <Button variant =" ghost" size =" sm" @click =" copyToClipboard(selectedRequest.tool_params, 'params')" >
394- <Check v-if =" copiedField === 'params'" class =" h-3 w-3" />
395- <Copy v-else class =" h-3 w-3" />
396- </Button >
397- </div >
398- <pre class =" bg-muted rounded-md p-3 text-sm overflow-x-auto max-h-48" >{{ formatJson(selectedRequest.tool_params) }}</pre >
399- </div >
400-
401- <!-- Response -->
402- <div >
403- <div class =" flex items-center justify-between mb-1" >
404- <div class =" text-sm font-medium text-muted-foreground" >
405- {{ t('mcpInstallations.details.requests.detail.response') }}
406- </div >
407- <Button variant =" ghost" size =" sm" @click =" copyToClipboard(selectedRequest.tool_response, 'response')" >
408- <Check v-if =" copiedField === 'response'" class =" h-3 w-3" />
409- <Copy v-else class =" h-3 w-3" />
410- </Button >
411- </div >
412- <pre class =" bg-muted rounded-md p-3 text-sm overflow-x-auto max-h-64" >{{ formatJson(selectedRequest.tool_response) }}</pre >
413- </div >
414- </div >
415- </DialogContent >
416- </Dialog >
300+ <!-- Detail Sheet -->
301+ <RequestDetailSheet
302+ :request =" selectedRequest"
303+ :open =" showDetailSheet"
304+ @update:open =" showDetailSheet = $event"
305+ />
417306 </div >
418307</template >
0 commit comments