mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 02:51: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
173 lines
6.6 KiB
TypeScript
173 lines
6.6 KiB
TypeScript
import { useState } from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { Card, CardContent, CardHeader } 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 DatabaseLog {
|
|
id: string;
|
|
timestamp: number;
|
|
identifier: string;
|
|
error_severity: string;
|
|
event_message: string;
|
|
}
|
|
|
|
export function DatabaseLogs() {
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [severity, setSeverity] = useState<string>('all');
|
|
const [timeRange, setTimeRange] = useState<'1h' | '24h' | '7d'>('24h');
|
|
const [expandedLog, setExpandedLog] = useState<string | null>(null);
|
|
|
|
const { data: logs, isLoading } = useQuery({
|
|
queryKey: ['database-logs', severity, timeRange],
|
|
queryFn: async () => {
|
|
// For now, return empty array as we need proper permissions for analytics query
|
|
// In production, this would use Supabase Analytics API
|
|
// const hoursAgo = timeRange === '1h' ? 1 : timeRange === '24h' ? 24 : 168;
|
|
// const startTime = Date.now() * 1000 - (hoursAgo * 60 * 60 * 1000 * 1000);
|
|
|
|
return [] as DatabaseLog[];
|
|
},
|
|
refetchInterval: 30000,
|
|
});
|
|
|
|
const filteredLogs = logs?.filter(log => {
|
|
if (searchTerm && !log.event_message.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}) || [];
|
|
|
|
const getSeverityColor = (severity: string): "default" | "destructive" | "outline" | "secondary" => {
|
|
switch (severity.toUpperCase()) {
|
|
case 'ERROR': return 'destructive';
|
|
case 'WARNING': return 'destructive';
|
|
case 'NOTICE': return 'default';
|
|
case 'LOG': return 'secondary';
|
|
default: return 'outline';
|
|
}
|
|
};
|
|
|
|
const isSpanLog = (message: string) => {
|
|
return message.includes('SPAN:') || message.includes('SPAN_EVENT:');
|
|
};
|
|
|
|
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 database logs..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<Select value={severity} onValueChange={setSeverity}>
|
|
<SelectTrigger className="w-[150px]">
|
|
<SelectValue placeholder="Severity" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Levels</SelectItem>
|
|
<SelectItem value="ERROR">Error</SelectItem>
|
|
<SelectItem value="WARNING">Warning</SelectItem>
|
|
<SelectItem value="NOTICE">Notice</SelectItem>
|
|
<SelectItem value="LOG">Log</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 database logs found for the selected criteria.
|
|
</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={getSeverityColor(log.error_severity)}>
|
|
{log.error_severity}
|
|
</Badge>
|
|
{isSpanLog(log.event_message) && (
|
|
<Badge variant="outline" className="text-xs">
|
|
TRACE
|
|
</Badge>
|
|
)}
|
|
<span className="text-sm text-muted-foreground">
|
|
{format(log.timestamp / 1000, 'HH:mm:ss.SSS')}
|
|
</span>
|
|
</div>
|
|
<span className="text-sm truncate max-w-[500px]">
|
|
{log.event_message.slice(0, 100)}
|
|
{log.event_message.length > 100 && '...'}
|
|
</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>
|
|
<pre className="text-xs font-mono mt-1 whitespace-pre-wrap break-all">
|
|
{log.event_message}
|
|
</pre>
|
|
</div>
|
|
<div>
|
|
<span className="text-xs text-muted-foreground">Timestamp:</span>
|
|
<p className="text-sm">{format(log.timestamp / 1000, 'PPpp')}</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-xs text-muted-foreground">Identifier:</span>
|
|
<p className="text-sm font-mono">{log.identifier}</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|