dev setup

This commit is contained in:
2025-12-31 21:24:42 +01:00
parent 167e82a52b
commit 16e0bdaec1
26 changed files with 4076 additions and 646 deletions

View File

@@ -0,0 +1,347 @@
'use client';
import { ApiError } from '@/lib/api/base/ApiError';
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
import { useState, useEffect } from 'react';
import { X, RefreshCw, Copy, Terminal, Activity, AlertTriangle } from 'lucide-react';
interface DevErrorPanelProps {
error: ApiError;
onReset: () => void;
}
/**
* Developer-focused error panel with detailed debugging information
*/
export function DevErrorPanel({ error, onReset }: DevErrorPanelProps) {
const [connectionStatus, setConnectionStatus] = useState(connectionMonitor.getHealth());
const [circuitBreakers, setCircuitBreakers] = useState(CircuitBreakerRegistry.getInstance().getStatus());
const [copied, setCopied] = useState(false);
useEffect(() => {
// Update status on mount
const health = connectionMonitor.getHealth();
setConnectionStatus(health);
setCircuitBreakers(CircuitBreakerRegistry.getInstance().getStatus());
// Listen for status changes
const handleStatusChange = () => {
setConnectionStatus(connectionMonitor.getHealth());
setCircuitBreakers(CircuitBreakerRegistry.getInstance().getStatus());
};
connectionMonitor.on('success', handleStatusChange);
connectionMonitor.on('failure', handleStatusChange);
connectionMonitor.on('connected', handleStatusChange);
connectionMonitor.on('disconnected', handleStatusChange);
connectionMonitor.on('degraded', handleStatusChange);
return () => {
connectionMonitor.off('success', handleStatusChange);
connectionMonitor.off('failure', handleStatusChange);
connectionMonitor.off('connected', handleStatusChange);
connectionMonitor.off('disconnected', handleStatusChange);
connectionMonitor.off('degraded', handleStatusChange);
};
}, []);
const copyToClipboard = async () => {
const debugInfo = {
error: {
type: error.type,
message: error.message,
context: error.context,
stack: error.stack,
},
connection: connectionStatus,
circuitBreakers,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
};
try {
await navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2));
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
// Silent failure for clipboard operations
}
};
const triggerHealthCheck = async () => {
await connectionMonitor.performHealthCheck();
setConnectionStatus(connectionMonitor.getHealth());
};
const resetCircuitBreakers = () => {
CircuitBreakerRegistry.getInstance().resetAll();
setCircuitBreakers(CircuitBreakerRegistry.getInstance().getStatus());
};
const getSeverityColor = (type: string) => {
switch (error.getSeverity()) {
case 'error': return 'bg-red-500/20 text-red-400 border-red-500/40';
case 'warn': return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/40';
case 'info': return 'bg-blue-500/20 text-blue-400 border-blue-500/40';
default: return 'bg-gray-500/20 text-gray-400 border-gray-500/40';
}
};
const reliability = connectionMonitor.getReliability();
return (
<div className="fixed inset-0 z-50 overflow-auto bg-deep-graphite p-4 font-mono text-sm">
<div className="max-w-6xl mx-auto space-y-4">
{/* Header */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<Terminal className="w-5 h-5 text-primary-blue" />
<h2 className="text-lg font-bold text-white">API Error Debug Panel</h2>
<span className={`px-2 py-1 rounded border text-xs ${getSeverityColor(error.type)}`}>
{error.type}
</span>
</div>
<div className="flex gap-2">
<button
onClick={copyToClipboard}
className="px-3 py-1 bg-iron-gray hover:bg-charcoal-outline border border-charcoal-outline rounded text-gray-300 flex items-center gap-2"
title="Copy debug info"
>
<Copy className="w-4 h-4" />
{copied ? 'Copied!' : 'Copy'}
</button>
<button
onClick={onReset}
className="px-3 py-1 bg-primary-blue hover:bg-primary-blue/80 text-white rounded flex items-center gap-2"
>
<X className="w-4 h-4" />
Close
</button>
</div>
</div>
{/* Error Details */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className="space-y-4">
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white flex items-center gap-2">
<AlertTriangle className="w-4 h-4" />
Error Details
</div>
<div className="p-4 space-y-2 text-xs">
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Type:</span>
<span className="col-span-2 text-red-400 font-bold">{error.type}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Message:</span>
<span className="col-span-2 text-gray-300">{error.message}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Endpoint:</span>
<span className="col-span-2 text-blue-400">{error.context.endpoint || 'N/A'}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Method:</span>
<span className="col-span-2 text-yellow-400">{error.context.method || 'N/A'}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Status:</span>
<span className="col-span-2">{error.context.statusCode || 'N/A'}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Retry Count:</span>
<span className="col-span-2">{error.context.retryCount || 0}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Timestamp:</span>
<span className="col-span-2 text-gray-500">{error.context.timestamp}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Retryable:</span>
<span className={`col-span-2 ${error.isRetryable() ? 'text-green-400' : 'text-red-400'}`}>
{error.isRetryable() ? 'Yes' : 'No'}
</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Connectivity:</span>
<span className={`col-span-2 ${error.isConnectivityIssue() ? 'text-red-400' : 'text-green-400'}`}>
{error.isConnectivityIssue() ? 'Yes' : 'No'}
</span>
</div>
{error.context.troubleshooting && (
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Troubleshoot:</span>
<span className="col-span-2 text-yellow-400">{error.context.troubleshooting}</span>
</div>
)}
</div>
</div>
{/* Connection Status */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white flex items-center gap-2">
<Activity className="w-4 h-4" />
Connection Health
</div>
<div className="p-4 space-y-2 text-xs">
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Status:</span>
<span className={`col-span-2 font-bold ${
connectionStatus.status === 'connected' ? 'text-green-400' :
connectionStatus.status === 'degraded' ? 'text-yellow-400' :
'text-red-400'
}`}>
{connectionStatus.status.toUpperCase()}
</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Reliability:</span>
<span className="col-span-2">{reliability.toFixed(2)}%</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Total Requests:</span>
<span className="col-span-2">{connectionStatus.totalRequests}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Successful:</span>
<span className="col-span-2 text-green-400">{connectionStatus.successfulRequests}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Failed:</span>
<span className="col-span-2 text-red-400">{connectionStatus.failedRequests}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Consecutive Failures:</span>
<span className="col-span-2">{connectionStatus.consecutiveFailures}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Avg Response:</span>
<span className="col-span-2">{connectionStatus.averageResponseTime.toFixed(2)}ms</span>
</div>
<div className="grid grid-cols-3 gap-2">
<span className="text-gray-500">Last Check:</span>
<span className="col-span-2 text-gray-500">
{connectionStatus.lastCheck?.toLocaleTimeString() || 'Never'}
</span>
</div>
</div>
</div>
</div>
{/* Right Column */}
<div className="space-y-4">
{/* Circuit Breakers */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white flex items-center gap-2">
<span className="text-lg"></span>
Circuit Breakers
</div>
<div className="p-4">
{Object.keys(circuitBreakers).length === 0 ? (
<div className="text-gray-500 text-center py-4">No circuit breakers active</div>
) : (
<div className="space-y-2 text-xs max-h-48 overflow-auto">
{Object.entries(circuitBreakers).map(([endpoint, status]) => (
<div key={endpoint} className="flex items-center justify-between p-2 bg-deep-graphite rounded border border-charcoal-outline">
<span className="text-blue-400 truncate flex-1">{endpoint}</span>
<span className={`px-2 py-1 rounded ${
status.state === 'CLOSED' ? 'bg-green-500/20 text-green-400' :
status.state === 'OPEN' ? 'bg-red-500/20 text-red-400' :
'bg-yellow-500/20 text-yellow-400'
}`}>
{status.state}
</span>
<span className="text-gray-500 ml-2">{status.failures} failures</span>
</div>
))}
</div>
)}
</div>
</div>
{/* Actions */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white">
Actions
</div>
<div className="p-4 space-y-2">
<button
onClick={triggerHealthCheck}
className="w-full px-3 py-2 bg-primary-blue hover:bg-primary-blue/80 text-white rounded flex items-center justify-center gap-2"
>
<RefreshCw className="w-4 h-4" />
Run Health Check
</button>
<button
onClick={resetCircuitBreakers}
className="w-full px-3 py-2 bg-yellow-600 hover:bg-yellow-700 text-white rounded flex items-center justify-center gap-2"
>
<span className="text-lg">🔄</span>
Reset Circuit Breakers
</button>
<button
onClick={() => {
connectionMonitor.reset();
setConnectionStatus(connectionMonitor.getHealth());
}}
className="w-full px-3 py-2 bg-red-600 hover:bg-red-700 text-white rounded flex items-center justify-center gap-2"
>
<span className="text-lg">🗑</span>
Reset Connection Stats
</button>
</div>
</div>
{/* Quick Fixes */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white">
Quick Fixes
</div>
<div className="p-4 space-y-2 text-xs">
<div className="text-gray-400">Common solutions:</div>
<ul className="list-disc list-inside space-y-1 text-gray-300">
<li>Check API server is running</li>
<li>Verify CORS configuration</li>
<li>Check environment variables</li>
<li>Review network connectivity</li>
<li>Check API rate limits</li>
</ul>
</div>
</div>
{/* Raw Error */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white">
Raw Error
</div>
<div className="p-4">
<pre className="text-xs text-gray-400 overflow-auto max-h-32 bg-deep-graphite p-2 rounded">
{JSON.stringify({
type: error.type,
message: error.message,
context: error.context,
}, null, 2)}
</pre>
</div>
</div>
</div>
</div>
{/* Console Output */}
<div className="bg-iron-gray border border-charcoal-outline rounded-lg overflow-hidden">
<div className="bg-charcoal-outline px-4 py-2 font-semibold text-white flex items-center gap-2">
<Terminal className="w-4 h-4" />
Console Output
</div>
<div className="p-4 bg-deep-graphite font-mono text-xs">
<div className="text-gray-500 mb-2">{'>'} {error.getDeveloperMessage()}</div>
<div className="text-gray-600">Check browser console for full stack trace and additional debug info.</div>
</div>
</div>
</div>
</div>
);
}