fix: add go.sum and fixes
This commit is contained in:
184
dashboard/src/app/traces/page.tsx
Normal file
184
dashboard/src/app/traces/page.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Search, Clock, AlertCircle, ExternalLink } from 'lucide-react';
|
||||
import { api } from '@/lib/api';
|
||||
import { TraceTimeline } from '@/components/traces/TraceTimeline';
|
||||
import { formatDuration, formatTime } from '@/lib/utils';
|
||||
|
||||
interface Trace {
|
||||
trace_id: string;
|
||||
services: string[];
|
||||
start_time: string;
|
||||
duration_ns: number;
|
||||
span_count: number;
|
||||
has_error: boolean;
|
||||
root_span?: {
|
||||
operation: string;
|
||||
service: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function TracesPage() {
|
||||
const [service, setService] = useState('');
|
||||
const [operation, setOperation] = useState('');
|
||||
const [minDuration, setMinDuration] = useState('');
|
||||
const [onlyErrors, setOnlyErrors] = useState(false);
|
||||
const [selectedTrace, setSelectedTrace] = useState<string | null>(null);
|
||||
|
||||
const { data: traces, isLoading, refetch } = useQuery({
|
||||
queryKey: ['traces', service, operation, minDuration, onlyErrors],
|
||||
queryFn: () => api.get('/api/v1/traces', {
|
||||
service,
|
||||
operation,
|
||||
min_duration_ms: minDuration,
|
||||
error: onlyErrors ? 'true' : '',
|
||||
from: new Date(Date.now() - 3600000).toISOString(),
|
||||
}),
|
||||
});
|
||||
|
||||
const { data: traceDetail } = useQuery({
|
||||
queryKey: ['trace', selectedTrace],
|
||||
queryFn: () => api.get(`/api/v1/traces/${selectedTrace}`),
|
||||
enabled: !!selectedTrace,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-white">Traces</h1>
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-wrap gap-4 p-4 bg-gray-900/50 rounded-lg border border-gray-800">
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<label className="block text-sm text-gray-400 mb-1">Service</label>
|
||||
<input
|
||||
type="text"
|
||||
value={service}
|
||||
onChange={(e) => setService(e.target.value)}
|
||||
placeholder="All services"
|
||||
className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<label className="block text-sm text-gray-400 mb-1">Operation</label>
|
||||
<input
|
||||
type="text"
|
||||
value={operation}
|
||||
onChange={(e) => setOperation(e.target.value)}
|
||||
placeholder="All operations"
|
||||
className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-32">
|
||||
<label className="block text-sm text-gray-400 mb-1">Min Duration</label>
|
||||
<input
|
||||
type="text"
|
||||
value={minDuration}
|
||||
onChange={(e) => setMinDuration(e.target.value)}
|
||||
placeholder="ms"
|
||||
className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<label className="flex items-center gap-2 px-3 py-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={onlyErrors}
|
||||
onChange={(e) => setOnlyErrors(e.target.checked)}
|
||||
className="rounded border-gray-600 bg-gray-800 text-indigo-500 focus:ring-indigo-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-300">Errors only</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Trace List */}
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg font-semibold text-white">
|
||||
{isLoading ? 'Loading...' : `${traces?.traces?.length ?? 0} traces`}
|
||||
</h2>
|
||||
<div className="space-y-2 max-h-[600px] overflow-auto">
|
||||
{traces?.traces?.map((trace: Trace) => (
|
||||
<div
|
||||
key={trace.trace_id}
|
||||
onClick={() => setSelectedTrace(trace.trace_id)}
|
||||
className={`p-4 bg-gray-900/50 rounded-lg border cursor-pointer transition-colors ${
|
||||
selectedTrace === trace.trace_id
|
||||
? 'border-indigo-500'
|
||||
: 'border-gray-800 hover:border-gray-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
{trace.has_error && (
|
||||
<AlertCircle className="h-4 w-4 text-red-400" />
|
||||
)}
|
||||
<span className="font-medium text-white">
|
||||
{trace.root_span?.operation || trace.trace_id.slice(0, 16)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{trace.services?.slice(0, 3).map((svc) => (
|
||||
<span
|
||||
key={svc}
|
||||
className="px-2 py-0.5 text-xs bg-gray-800 text-gray-300 rounded"
|
||||
>
|
||||
{svc}
|
||||
</span>
|
||||
))}
|
||||
{(trace.services?.length ?? 0) > 3 && (
|
||||
<span className="text-xs text-gray-500">
|
||||
+{trace.services.length - 3} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right text-sm">
|
||||
<div className="flex items-center gap-1 text-gray-400">
|
||||
<Clock className="h-3 w-3" />
|
||||
{formatDuration(trace.duration_ns)}
|
||||
</div>
|
||||
<div className="text-gray-500">
|
||||
{trace.span_count} spans
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
{formatTime(trace.start_time)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{!isLoading && (!traces?.traces || traces.traces.length === 0) && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
No traces found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Trace Detail */}
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg font-semibold text-white">Trace Timeline</h2>
|
||||
{selectedTrace && traceDetail ? (
|
||||
<TraceTimeline trace={traceDetail} />
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-64 bg-gray-900/50 rounded-lg border border-gray-800 text-gray-500">
|
||||
Select a trace to view timeline
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user