170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
'use client';
|
|
|
|
import { Button } from '@/ui/Button';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Badge, StatRow, StatusIndicator } from '@/ui/StatusIndicator';
|
|
import { Text } from '@/ui/Text';
|
|
import { Grid } from '@/ui/primitives/Grid';
|
|
import { Stack } from '@/ui/primitives/Stack';
|
|
import { Activity, RefreshCw, Terminal, Wifi } from 'lucide-react';
|
|
|
|
interface APIStatusSectionProps {
|
|
apiStatus: string;
|
|
apiHealth: {
|
|
successfulRequests: number;
|
|
totalRequests: number;
|
|
averageResponseTime: number;
|
|
consecutiveFailures: number;
|
|
lastCheck: number | Date | null;
|
|
};
|
|
circuitBreakers: Record<string, { state: string; failures: number }>;
|
|
checkingHealth: boolean;
|
|
onHealthCheck: () => void;
|
|
onResetStats: () => void;
|
|
onTestError: () => void;
|
|
}
|
|
|
|
export function APIStatusSection({
|
|
apiStatus,
|
|
apiHealth,
|
|
circuitBreakers,
|
|
checkingHealth,
|
|
onHealthCheck,
|
|
onResetStats,
|
|
onTestError
|
|
}: APIStatusSectionProps) {
|
|
const reliability = apiHealth.totalRequests === 0
|
|
? 0
|
|
: (apiHealth.successfulRequests / apiHealth.totalRequests);
|
|
|
|
const reliabilityLabel = apiHealth.totalRequests === 0 ? 'N/A' : 'Calculated';
|
|
|
|
const getReliabilityColor = () => {
|
|
if (apiHealth.totalRequests === 0) return 'text-gray-500';
|
|
if (reliability >= 0.95) return 'text-performance-green';
|
|
if (reliability >= 0.8) return 'text-warning-amber';
|
|
return 'text-red-400';
|
|
};
|
|
|
|
const getStatusVariant = () => {
|
|
if (apiStatus === 'connected') return 'success';
|
|
if (apiStatus === 'degraded') return 'warning';
|
|
return 'danger';
|
|
};
|
|
|
|
const statusLabel = apiStatus.toUpperCase();
|
|
const healthSummary = `${apiHealth.successfulRequests}/${apiHealth.totalRequests} req`;
|
|
const avgResponseLabel = `${apiHealth.averageResponseTime.toFixed(0)}ms`;
|
|
const lastCheckLabel = apiHealth.lastCheck ? 'Recently' : 'Never';
|
|
const consecutiveFailuresLabel = String(apiHealth.consecutiveFailures);
|
|
|
|
return (
|
|
<Stack>
|
|
<Stack direction="row" align="center" gap={2} mb={3}>
|
|
<Icon icon={Activity} size={4} color="rgb(156, 163, 175)" />
|
|
<Text size="xs" weight="semibold" color="text-gray-400" uppercase letterSpacing="wide">
|
|
API Status
|
|
</Text>
|
|
</Stack>
|
|
|
|
{/* Status Indicator */}
|
|
<StatusIndicator
|
|
icon={Wifi}
|
|
label={statusLabel}
|
|
subLabel={healthSummary}
|
|
variant={getStatusVariant()}
|
|
/>
|
|
|
|
<Stack mt={2}>
|
|
{/* Reliability */}
|
|
<StatRow
|
|
label="Reliability"
|
|
value={reliabilityLabel}
|
|
valueColor={getReliabilityColor()}
|
|
/>
|
|
|
|
{/* Response Time */}
|
|
<StatRow
|
|
label="Avg Response"
|
|
value={avgResponseLabel}
|
|
valueColor="text-primary-blue"
|
|
valueFont="mono"
|
|
/>
|
|
</Stack>
|
|
|
|
{/* Consecutive Failures */}
|
|
{apiHealth.consecutiveFailures > 0 && (
|
|
<Stack mt={2}>
|
|
<StatusIndicator
|
|
icon={Activity}
|
|
label="Consecutive Failures"
|
|
subLabel={consecutiveFailuresLabel}
|
|
variant="danger"
|
|
/>
|
|
</Stack>
|
|
)}
|
|
|
|
{/* Circuit Breakers */}
|
|
<Stack mt={2}>
|
|
<Text size="xs" color="text-gray-500" block mb={1}>Circuit Breakers:</Text>
|
|
{Object.keys(circuitBreakers).length === 0 ? (
|
|
<Text size="xs" color="text-gray-500" italic>None active</Text>
|
|
) : (
|
|
<Stack gap={1} maxHeight="4rem" overflow="auto">
|
|
{Object.entries(circuitBreakers).map(([endpoint, status]) => (
|
|
<Stack key={endpoint} direction="row" align="center" justify="between">
|
|
<Text size="xs" color="text-gray-400" truncate flexGrow={1}>
|
|
{endpoint}
|
|
</Text>
|
|
<Badge variant={status.state === 'CLOSED' ? 'success' : status.state === 'OPEN' ? 'danger' : 'warning'}>
|
|
{status.state}
|
|
</Badge>
|
|
{status.failures > 0 && (
|
|
<Text size="xs" color="text-red-400" ml={1}>({status.failures})</Text>
|
|
)}
|
|
</Stack>
|
|
))}
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
|
|
{/* API Actions */}
|
|
<Grid cols={2} gap={2} mt={3}>
|
|
<Button
|
|
variant="primary"
|
|
onClick={onHealthCheck}
|
|
disabled={checkingHealth}
|
|
size="sm"
|
|
icon={<Icon icon={RefreshCw} size={3} animate={checkingHealth ? 'spin' : 'none'} />}
|
|
>
|
|
Health Check
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
onClick={onResetStats}
|
|
size="sm"
|
|
>
|
|
Reset Stats
|
|
</Button>
|
|
</Grid>
|
|
|
|
<Grid cols={1} gap={2} mt={2}>
|
|
<Button
|
|
variant="danger"
|
|
onClick={onTestError}
|
|
size="sm"
|
|
icon={<Icon icon={Terminal} size={3} />}
|
|
>
|
|
Test Error Handler
|
|
</Button>
|
|
</Grid>
|
|
|
|
<Stack mt={2}>
|
|
<Text size="xs" color="text-gray-600">
|
|
Last Check: {lastCheckLabel}
|
|
</Text>
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
}
|