mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08: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
169 lines
6.2 KiB
TypeScript
169 lines
6.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Loader2, Search, ChevronDown, ChevronRight } from 'lucide-react';
|
|
import { format } from 'date-fns';
|
|
import { supabase } from '@/lib/supabaseClient';
|
|
|
|
interface EdgeFunctionLog {
|
|
id: string;
|
|
timestamp: number;
|
|
event_type: string;
|
|
event_message: string;
|
|
function_id: string;
|
|
level: string;
|
|
}
|
|
|
|
const FUNCTION_NAMES = [
|
|
'detect-location',
|
|
'process-selective-approval',
|
|
'process-selective-rejection',
|
|
];
|
|
|
|
export function EdgeFunctionLogs() {
|
|
const [selectedFunction, setSelectedFunction] = useState<string>('all');
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [timeRange, setTimeRange] = useState<'1h' | '24h' | '7d'>('24h');
|
|
const [expandedLog, setExpandedLog] = useState<string | null>(null);
|
|
|
|
const { data: logs, isLoading } = useQuery({
|
|
queryKey: ['edge-function-logs', selectedFunction, timeRange],
|
|
queryFn: async () => {
|
|
// Query Supabase edge function logs
|
|
// Note: This uses the analytics endpoint which requires specific permissions
|
|
const hoursAgo = timeRange === '1h' ? 1 : timeRange === '24h' ? 24 : 168;
|
|
const startTime = Date.now() - (hoursAgo * 60 * 60 * 1000);
|
|
|
|
// For now, return the logs from context as an example
|
|
// In production, this would call the Supabase Management API
|
|
const allLogs: EdgeFunctionLog[] = [];
|
|
|
|
return allLogs;
|
|
},
|
|
refetchInterval: 30000, // Refresh every 30 seconds
|
|
});
|
|
|
|
const filteredLogs = logs?.filter(log => {
|
|
if (searchTerm && !log.event_message.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}) || [];
|
|
|
|
const getLevelColor = (level: string): "default" | "destructive" | "secondary" => {
|
|
switch (level.toLowerCase()) {
|
|
case 'error': return 'destructive';
|
|
case 'warn': return 'destructive';
|
|
case 'info': return 'default';
|
|
default: return 'secondary';
|
|
}
|
|
};
|
|
|
|
const toggleExpand = (logId: string) => {
|
|
setExpandedLog(expandedLog === logId ? null : logId);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex flex-col md:flex-row gap-4">
|
|
<div className="flex-1">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
|
<Input
|
|
placeholder="Search logs..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<Select value={selectedFunction} onValueChange={setSelectedFunction}>
|
|
<SelectTrigger className="w-[200px]">
|
|
<SelectValue placeholder="Select function" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Functions</SelectItem>
|
|
{FUNCTION_NAMES.map(name => (
|
|
<SelectItem key={name} value={name}>{name}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<Select value={timeRange} onValueChange={(v) => setTimeRange(v as any)}>
|
|
<SelectTrigger className="w-[120px]">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="1h">Last Hour</SelectItem>
|
|
<SelectItem value="24h">Last 24h</SelectItem>
|
|
<SelectItem value="7d">Last 7 Days</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : filteredLogs.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<p className="text-center text-muted-foreground">
|
|
No edge function logs found. Logs will appear here when edge functions are invoked.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{filteredLogs.map((log) => (
|
|
<Card key={log.id} className="overflow-hidden">
|
|
<CardHeader
|
|
className="py-3 cursor-pointer hover:bg-muted/50 transition-colors"
|
|
onClick={() => toggleExpand(log.id)}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
{expandedLog === log.id ? (
|
|
<ChevronDown className="w-4 h-4 text-muted-foreground" />
|
|
) : (
|
|
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
|
)}
|
|
<Badge variant={getLevelColor(log.level)}>
|
|
{log.level}
|
|
</Badge>
|
|
<span className="text-sm text-muted-foreground">
|
|
{format(log.timestamp, 'HH:mm:ss.SSS')}
|
|
</span>
|
|
<Badge variant="outline" className="text-xs">
|
|
{log.event_type}
|
|
</Badge>
|
|
</div>
|
|
<span className="text-sm truncate max-w-[400px]">
|
|
{log.event_message}
|
|
</span>
|
|
</div>
|
|
</CardHeader>
|
|
{expandedLog === log.id && (
|
|
<CardContent className="pt-0 pb-4 border-t">
|
|
<div className="space-y-2 mt-4">
|
|
<div>
|
|
<span className="text-xs text-muted-foreground">Full Message:</span>
|
|
<p className="text-sm font-mono mt-1">{log.event_message}</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-xs text-muted-foreground">Timestamp:</span>
|
|
<p className="text-sm">{format(log.timestamp, 'PPpp')}</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|