mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 12:31:12 -05:00
- Implement new unified monitoring hub by adding EdgeFunctionLogs, DatabaseLogs, CorrelatedLogsView, and UnifiedLogSearch components - Integrate new tabs (edge-functions, database, traces) into ErrorMonitoring and expose TraceViewer route - Update admin sidebar link to reflect Monitoring hub and extend error modals with log-correlation actions - Wire up app to include trace viewer route and adjust related components for unified log correlation
162 lines
5.2 KiB
TypeScript
162 lines
5.2 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Loader2, Clock } from 'lucide-react';
|
|
import { format } from 'date-fns';
|
|
import { supabase } from '@/lib/supabaseClient';
|
|
|
|
interface CorrelatedLogsViewProps {
|
|
requestId: string;
|
|
traceId?: string;
|
|
}
|
|
|
|
interface TimelineEvent {
|
|
timestamp: Date;
|
|
type: 'error' | 'edge' | 'database' | 'approval';
|
|
message: string;
|
|
severity?: string;
|
|
metadata?: Record<string, any>;
|
|
}
|
|
|
|
export function CorrelatedLogsView({ requestId, traceId }: CorrelatedLogsViewProps) {
|
|
const { data: events, isLoading } = useQuery({
|
|
queryKey: ['correlated-logs', requestId, traceId],
|
|
queryFn: async () => {
|
|
const events: TimelineEvent[] = [];
|
|
|
|
// Fetch application error
|
|
const { data: error } = await supabase
|
|
.from('request_metadata')
|
|
.select('*')
|
|
.eq('request_id', requestId)
|
|
.single();
|
|
|
|
if (error) {
|
|
events.push({
|
|
timestamp: new Date(error.created_at),
|
|
type: 'error',
|
|
message: error.error_message || 'Unknown error',
|
|
severity: error.error_type || undefined,
|
|
metadata: {
|
|
endpoint: error.endpoint,
|
|
method: error.method,
|
|
status_code: error.status_code,
|
|
},
|
|
});
|
|
}
|
|
|
|
// Fetch approval metrics
|
|
const { data: approval } = await supabase
|
|
.from('approval_transaction_metrics')
|
|
.select('*')
|
|
.eq('request_id', requestId)
|
|
.maybeSingle();
|
|
|
|
if (approval && approval.created_at) {
|
|
events.push({
|
|
timestamp: new Date(approval.created_at),
|
|
type: 'approval',
|
|
message: approval.success ? 'Approval successful' : (approval.error_message || 'Approval failed'),
|
|
severity: approval.success ? 'success' : 'error',
|
|
metadata: {
|
|
items_count: approval.items_count,
|
|
duration_ms: approval.duration_ms || undefined,
|
|
},
|
|
});
|
|
}
|
|
|
|
// TODO: Fetch edge function logs (requires Management API access)
|
|
// TODO: Fetch database logs (requires analytics API access)
|
|
|
|
// Sort chronologically
|
|
events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
|
|
return events;
|
|
},
|
|
});
|
|
|
|
const getTypeColor = (type: string): "default" | "destructive" | "outline" | "secondary" => {
|
|
switch (type) {
|
|
case 'error': return 'destructive';
|
|
case 'approval': return 'destructive';
|
|
case 'edge': return 'default';
|
|
case 'database': return 'secondary';
|
|
default: return 'outline';
|
|
}
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!events || events.length === 0) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<p className="text-center text-muted-foreground">
|
|
No correlated logs found for this request.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Clock className="w-5 h-5" />
|
|
Timeline for Request {requestId.slice(0, 8)}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="relative space-y-4">
|
|
{/* Timeline line */}
|
|
<div className="absolute left-6 top-0 bottom-0 w-0.5 bg-border" />
|
|
|
|
{events.map((event, index) => (
|
|
<div key={index} className="relative pl-14">
|
|
{/* Timeline dot */}
|
|
<div className="absolute left-[18px] top-2 w-4 h-4 rounded-full bg-background border-2 border-primary" />
|
|
|
|
<Card>
|
|
<CardContent className="pt-4">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant={getTypeColor(event.type)}>
|
|
{event.type.toUpperCase()}
|
|
</Badge>
|
|
{event.severity && (
|
|
<Badge variant="outline" className="text-xs">
|
|
{event.severity}
|
|
</Badge>
|
|
)}
|
|
<span className="text-xs text-muted-foreground">
|
|
{format(event.timestamp, 'HH:mm:ss.SSS')}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm">{event.message}</p>
|
|
{event.metadata && Object.keys(event.metadata).length > 0 && (
|
|
<div className="text-xs text-muted-foreground space-y-1">
|
|
{Object.entries(event.metadata).map(([key, value]) => (
|
|
<div key={key}>
|
|
<span className="font-medium">{key}:</span> {String(value)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|