Skip to main content
Glama

Superglue MCP

Official
by superglue-ai
RunsTable.tsx13 kB
"use client" import { useConfig } from '@/src/app/config-context'; import { Badge } from "@/src/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/src/components/ui/table"; import { RunResult, SuperglueClient } from '@superglue/client'; import { AlertCircle, Calendar, CheckCircle, ChevronRight, Clock, Hash, Loader2 } from 'lucide-react'; import React from 'react'; // Helper function to recursively remove null values from objects const removeNullFields = (obj: any): any => { if (obj === null || obj === undefined) { return undefined; } if (Array.isArray(obj)) { return obj.map(removeNullFields).filter(item => item !== undefined); } if (typeof obj === 'object' && obj !== null) { const cleaned: any = {}; for (const [key, value] of Object.entries(obj)) { const cleanedValue = removeNullFields(value); if (cleanedValue !== undefined && cleanedValue !== null) { cleaned[key] = cleanedValue; } } return Object.keys(cleaned).length > 0 ? cleaned : undefined; } return obj; }; const RunsTable = ({ id }: { id?: string }) => { const [runs, setRuns] = React.useState<RunResult[]>([]); const [expandedRunId, setExpandedRunId] = React.useState<string | null>(null); const [runDetails, setRunDetails] = React.useState<Record<string, any>>({}); const [loadingDetails, setLoadingDetails] = React.useState<Record<string, boolean>>({}); const [loading, setLoading] = React.useState(true); const [currentPage, setCurrentPage] = React.useState(0); const pageSize = 50; const config = useConfig(); React.useEffect(() => { const getRuns = async () => { try { setLoading(true); const superglueClient = new SuperglueClient({ endpoint: config.superglueEndpoint, apiKey: config.superglueApiKey }) const data = await superglueClient.listRuns(pageSize, currentPage * pageSize, id); setRuns(data.items); } catch (error) { console.error('Error fetching runs:', error); } finally { setLoading(false); } }; getRuns(); }, [currentPage]); const handleRunClick = async (run: RunResult) => { // Toggle expansion if (expandedRunId === run.id) { setExpandedRunId(null); return; } setExpandedRunId(run.id); // If we already have details, don't fetch again if (runDetails[run.id]) { return; } setLoadingDetails(prev => ({ ...prev, [run.id]: true })); try { const superglueClient = new SuperglueClient({ endpoint: config.superglueEndpoint, apiKey: config.superglueApiKey }); // Get the detailed run data - try to get more details via getRun const detailedRun = await superglueClient.getRun(run.id); setRunDetails(prev => ({ ...prev, [run.id]: detailedRun || run })); } catch (error) { console.error('Error fetching run details:', error); // Fall back to the basic run data if we can't get details setRunDetails(prev => ({ ...prev, [run.id]: run })); } finally { setLoadingDetails(prev => ({ ...prev, [run.id]: false })); } }; if (loading) { return ( <div className="flex-1 flex items-center justify-center min-h-[400px]"> <div className="flex flex-col items-center gap-3"> <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <p className="text-sm text-muted-foreground">Loading runs...</p> </div> </div> ); } return ( <div className="flex-1 overflow-auto"> <div className="flex justify-between items-center mb-6"> <h1 className="text-2xl font-bold">Tool Runs</h1> </div> <div className="border rounded-lg"> <Table> <TableHeader> <TableRow> <TableHead>Tool Id</TableHead> <TableHead>Status</TableHead> <TableHead>Started At</TableHead> <TableHead>Completed At</TableHead> <TableHead>Duration</TableHead> </TableRow> </TableHeader> <TableBody> {[...runs].sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).map((run) => ( <React.Fragment key={run.id}> <TableRow className="cursor-pointer hover:bg-muted/50 transition-colors" onClick={() => handleRunClick(run)} > <TableCell className="font-medium"> <div className="flex items-center gap-2"> <ChevronRight className={`h-4 w-4 transition-transform ${expandedRunId === run.id ? 'rotate-90' : ''}`} /> {run.config?.id ?? "undefined"} </div> </TableCell> <TableCell> <span className={`px-2 py-1 rounded-full text-sm font-medium ${run.success ? 'bg-emerald-500 text-white' : 'bg-red-600 text-white' }`}> {run.success ? 'Success' : 'Failed'} </span> </TableCell> <TableCell>{new Date(run.startedAt).toLocaleString()}</TableCell> <TableCell>{new Date(run.completedAt).toLocaleString()}</TableCell> <TableCell> {(new Date(run.completedAt).getTime() - new Date(run.startedAt).getTime())}ms </TableCell> </TableRow> {/* Expanded Details Row */} {expandedRunId === run.id && ( <TableRow> <TableCell colSpan={5} className="bg-muted/10 p-0"> {loadingDetails[run.id] ? ( <div className="flex items-center justify-center py-8"> <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /> </div> ) : ( <RunDetails run={runDetails[run.id] || run} /> )} </TableCell> </TableRow> )} </React.Fragment> ))} </TableBody> </Table> </div> <div className="flex justify-center gap-2 mt-4"> <button onClick={() => setCurrentPage(p => Math.max(0, p - 1))} disabled={currentPage === 0} className="px-4 py-2 text-sm font-medium bg-secondary hover:bg-secondary/80 border border-input rounded-md transition-colors disabled:opacity-50" > Previous </button> <span className="px-4 py-2 text-sm font-medium bg-secondary rounded-md"> Page {currentPage + 1} </span> <button onClick={() => setCurrentPage(p => p + 1)} disabled={runs.length < pageSize} className="px-4 py-2 text-sm font-medium bg-secondary hover:bg-secondary/80 border border-input rounded-md transition-colors disabled:opacity-50" > Next </button> </div> </div> ); }; // Separate component for run details const RunDetails = ({ run }: { run: any }) => { if (!run) return null; return ( <div className="p-6 space-y-6 max-h-[600px] overflow-y-auto"> {/* Header with ID */} <div className="flex items-center gap-2 pb-4 border-b"> <Hash className="h-5 w-5 text-muted-foreground" /> <h3 className="text-lg font-semibold">Run Details: {run.id}</h3> </div> {/* Status and Timing */} <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="space-y-3"> <h4 className="text-sm font-medium text-muted-foreground">Status</h4> <div className="flex items-center gap-2"> {run.success ? ( <> <CheckCircle className="h-5 w-5 text-emerald-500" /> <Badge variant="default" className="bg-emerald-500"> Success </Badge> </> ) : ( <> <AlertCircle className="h-5 w-5 text-red-500" /> <Badge variant="destructive"> Failed </Badge> </> )} </div> {run.statusCode && ( <p className="text-sm text-muted-foreground"> Status Code: {run.statusCode} </p> )} </div> <div className="space-y-3"> <h4 className="text-sm font-medium text-muted-foreground">Timing</h4> <div className="space-y-2 text-sm"> <div className="flex items-center gap-2"> <Calendar className="h-4 w-4 text-muted-foreground" /> <span>Started: {new Date(run.startedAt).toLocaleString()}</span> </div> <div className="flex items-center gap-2"> <CheckCircle className="h-4 w-4 text-muted-foreground" /> <span>Completed: {new Date(run.completedAt).toLocaleString()}</span> </div> <div className="flex items-center gap-2"> <Clock className="h-4 w-4 text-muted-foreground" /> <span> Duration: {(new Date(run.completedAt).getTime() - new Date(run.startedAt).getTime())}ms </span> </div> </div> </div> </div> {/* Error Message */} {run.error && ( <div className="space-y-2"> <h4 className="text-sm font-medium text-muted-foreground flex items-center gap-2"> <AlertCircle className="h-4 w-4 text-red-500" /> Error Message </h4> <div className="p-4 bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900 rounded-lg"> <pre className="text-sm text-red-700 dark:text-red-400 whitespace-pre-wrap font-mono"> {run.error} </pre> </div> </div> )} {/* Step Results (for tools) */} {run?.stepResults && run.stepResults.length > 0 && ( <div className="space-y-2"> <h4 className="text-sm font-medium text-muted-foreground">Tool Steps</h4> <div className="space-y-2"> {run.stepResults.map((step: any, index: number) => ( <div key={step.stepId} className="p-4 border rounded-lg space-y-2" > <div className="flex items-center justify-between"> <div className="flex items-center gap-2"> <span className="font-medium">Step {index + 1}: {step.stepId}</span> </div> <Badge variant={step.success ? "default" : "destructive"} className={step.success ? "bg-emerald-500" : ""}> {step.success ? "Success" : "Failed"} </Badge> </div> {step.error && ( <div className="mt-2 p-3 bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900 rounded"> <p className="text-sm font-medium text-red-700 dark:text-red-400">Step Error:</p> <pre className="text-xs text-red-600 dark:text-red-500 mt-1 whitespace-pre-wrap font-mono"> {step.error} </pre> </div> )} </div> ))} </div> </div> )} {/* Response Headers */} {run.headers && Object.keys(run.headers).length > 0 && ( <div className="space-y-2"> <h4 className="text-sm font-medium text-muted-foreground">Response Headers</h4> <div className="p-4 bg-muted/30 rounded-lg"> <pre className="text-xs font-mono whitespace-pre-wrap"> {JSON.stringify(run.headers, null, 2)} </pre> </div> </div> )} {/* Response Data */} {run.data && ( <div className="space-y-2"> <h4 className="text-sm font-medium text-muted-foreground">Response Data</h4> <div className="p-4 bg-muted/30 rounded-lg"> <pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto"> {JSON.stringify(run.data, null, 2)} </pre> </div> </div> )} {/* Configuration */} {run.config && ( <div className="space-y-2"> <h4 className="text-sm font-medium text-muted-foreground">Configuration</h4> <div className="p-4 bg-muted/30 rounded-lg"> <pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto"> {JSON.stringify(removeNullFields(run.config), null, 2)} </pre> </div> </div> )} </div> ); }; export { RunsTable };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/superglue-ai/superglue'

If you have feedback or need assistance with the MCP directory API, please join our Discord server