website refactor
This commit is contained in:
160
apps/website/ui/DevErrorPanel.tsx
Normal file
160
apps/website/ui/DevErrorPanel.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { StatGrid } from '@/ui/StatGrid';
|
||||
import { Activity, AlertTriangle, Copy, RefreshCw, Terminal, X } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface DevErrorPanelProps {
|
||||
error: ApiError;
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
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(() => {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
const getSeverityVariant = (): 'critical' | 'warning' | 'primary' | 'default' => {
|
||||
switch (error.getSeverity()) {
|
||||
case 'error': return 'critical';
|
||||
case 'warn': return 'warning';
|
||||
case 'info': return 'primary';
|
||||
default: return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="fixed" inset={0} zIndex={100} bg="var(--ui-color-bg-base)" padding={4} style={{ overflowY: 'auto' }}>
|
||||
<Box maxWidth="80rem" marginX="auto" fullWidth>
|
||||
<Box display="flex" flexDirection="col" gap={4}>
|
||||
{/* Header */}
|
||||
<Card variant="dark" padding={4}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Icon icon={Terminal} size={5} intent="primary" />
|
||||
<Heading level={2}>API Error Debug Panel</Heading>
|
||||
<Badge variant={getSeverityVariant() as any}>
|
||||
{error.type}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box display="flex" gap={2}>
|
||||
<Button variant="secondary" onClick={copyToClipboard} icon={<Icon icon={Copy} size={4} />}>
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onReset} icon={<Icon icon={X} size={4} />}>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/* Details Grid */}
|
||||
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={4}>
|
||||
<Box display="flex" flexDirection="col" gap={4}>
|
||||
<Card title="Error Details" variant="outline">
|
||||
<Box display="flex" flexDirection="col" gap={2}>
|
||||
<DetailRow label="Type" value={error.type} intent="critical" />
|
||||
<DetailRow label="Message" value={error.message} />
|
||||
<DetailRow label="Endpoint" value={error.context.endpoint || 'N/A'} intent="primary" />
|
||||
<DetailRow label="Method" value={error.context.method || 'N/A'} intent="warning" />
|
||||
<DetailRow label="Status" value={error.context.statusCode || 'N/A'} />
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Card title="Connection Health" variant="outline">
|
||||
<Box display="flex" flexDirection="col" gap={2}>
|
||||
<DetailRow label="Status" value={connectionStatus.status.toUpperCase()} intent={connectionStatus.status === 'connected' ? 'success' : 'critical'} />
|
||||
<DetailRow label="Reliability" value={`${connectionMonitor.getReliability().toFixed(2)}%`} />
|
||||
<DetailRow label="Total Requests" value={connectionStatus.totalRequests} />
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" flexDirection="col" gap={4}>
|
||||
<Card title="Actions" variant="outline">
|
||||
<Box display="flex" flexDirection="col" gap={2}>
|
||||
<Button variant="primary" onClick={() => connectionMonitor.performHealthCheck()} fullWidth icon={<Icon icon={RefreshCw} size={4} />}>
|
||||
Run Health Check
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => CircuitBreakerRegistry.getInstance().resetAll()} fullWidth>
|
||||
Reset Circuit Breakers
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Card title="Raw Error" variant="outline">
|
||||
<Box padding={2} bg="var(--ui-color-bg-surface-muted)" rounded="md" style={{ maxHeight: '10rem', overflow: 'auto' }}>
|
||||
<Text size="xs" variant="low" font="mono">
|
||||
{JSON.stringify(error.context, null, 2)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const DetailRow = ({ label, value, intent = 'low' }: { label: string; value: any; intent?: any }) => (
|
||||
<Box display="grid" gridCols={3} gap={2}>
|
||||
<Text size="xs" variant="low" weight="bold">{label}:</Text>
|
||||
<Box style={{ gridColumn: 'span 2' }}>
|
||||
<Text size="xs" variant={intent} weight="bold">{String(value)}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
Reference in New Issue
Block a user