website refactor
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Activity, Wifi, RefreshCw, Terminal } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
|
||||
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
|
||||
import { useNotifications } from '@/components/notifications/NotificationProvider';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { StatusIndicator, StatRow, Badge } from '@/ui/StatusIndicator';
|
||||
|
||||
interface APIStatusSectionProps {
|
||||
apiStatus: string;
|
||||
apiHealth: any;
|
||||
circuitBreakers: Record<string, any>;
|
||||
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;
|
||||
@@ -25,121 +34,137 @@ export function APIStatusSection({
|
||||
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 (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Activity className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wide">
|
||||
<Box>
|
||||
<Box display="flex" alignItems="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
|
||||
</span>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Status Indicator */}
|
||||
<div className={`flex items-center justify-between p-2 rounded-lg mb-2 ${
|
||||
apiStatus === 'connected' ? 'bg-green-500/10 border border-green-500/30' :
|
||||
apiStatus === 'degraded' ? 'bg-yellow-500/10 border border-yellow-500/30' :
|
||||
'bg-red-500/10 border border-red-500/30'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Wifi className={`w-4 h-4 ${
|
||||
apiStatus === 'connected' ? 'text-green-400' :
|
||||
apiStatus === 'degraded' ? 'text-yellow-400' :
|
||||
'text-red-400'
|
||||
}`} />
|
||||
<span className="text-sm font-semibold text-white">{apiStatus.toUpperCase()}</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400">
|
||||
{apiHealth.successfulRequests}/{apiHealth.totalRequests} req
|
||||
</span>
|
||||
</div>
|
||||
<StatusIndicator
|
||||
icon={Wifi}
|
||||
label={statusLabel}
|
||||
subLabel={healthSummary}
|
||||
variant={getStatusVariant()}
|
||||
/>
|
||||
|
||||
{/* Reliability */}
|
||||
<div className="flex items-center justify-between text-xs mb-2">
|
||||
<span className="text-gray-500">Reliability</span>
|
||||
<span className={`font-bold ${
|
||||
apiHealth.totalRequests === 0 ? 'text-gray-500' :
|
||||
(apiHealth.successfulRequests / apiHealth.totalRequests) >= 0.95 ? 'text-green-400' :
|
||||
(apiHealth.successfulRequests / apiHealth.totalRequests) >= 0.8 ? 'text-yellow-400' :
|
||||
'text-red-400'
|
||||
}`}>
|
||||
{apiHealth.totalRequests === 0 ? 'N/A' :
|
||||
((apiHealth.successfulRequests / apiHealth.totalRequests) * 100).toFixed(1) + '%'}
|
||||
</span>
|
||||
</div>
|
||||
<Box mt={2}>
|
||||
{/* Reliability */}
|
||||
<StatRow
|
||||
label="Reliability"
|
||||
value={reliabilityLabel}
|
||||
valueColor={getReliabilityColor()}
|
||||
/>
|
||||
|
||||
{/* Response Time */}
|
||||
<div className="flex items-center justify-between text-xs mb-2">
|
||||
<span className="text-gray-500">Avg Response</span>
|
||||
<span className="text-blue-400 font-mono">
|
||||
{apiHealth.averageResponseTime.toFixed(0)}ms
|
||||
</span>
|
||||
</div>
|
||||
{/* Response Time */}
|
||||
<StatRow
|
||||
label="Avg Response"
|
||||
value={avgResponseLabel}
|
||||
valueColor="text-primary-blue"
|
||||
valueFont="mono"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Consecutive Failures */}
|
||||
{apiHealth.consecutiveFailures > 0 && (
|
||||
<div className="flex items-center justify-between text-xs mb-2 bg-red-500/10 rounded px-2 py-1">
|
||||
<span className="text-red-400">Consecutive Failures</span>
|
||||
<span className="text-red-400 font-bold">{apiHealth.consecutiveFailures}</span>
|
||||
</div>
|
||||
<Box mt={2}>
|
||||
<StatusIndicator
|
||||
icon={Activity}
|
||||
label="Consecutive Failures"
|
||||
subLabel={consecutiveFailuresLabel}
|
||||
variant="danger"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Circuit Breakers */}
|
||||
<div className="mt-2">
|
||||
<div className="text-[10px] text-gray-500 mb-1">Circuit Breakers:</div>
|
||||
<Box mt={2}>
|
||||
<Text size="xs" color="text-gray-500" block mb={1}>Circuit Breakers:</Text>
|
||||
{Object.keys(circuitBreakers).length === 0 ? (
|
||||
<div className="text-[10px] text-gray-500 italic">None active</div>
|
||||
<Text size="xs" color="text-gray-500" italic>None active</Text>
|
||||
) : (
|
||||
<div className="space-y-1 max-h-16 overflow-auto">
|
||||
{Object.entries(circuitBreakers).map(([endpoint, status]: [string, any]) => (
|
||||
<div key={endpoint} className="flex items-center justify-between text-[10px]">
|
||||
<span className="text-gray-400 truncate flex-1">{endpoint.split('/').pop() || endpoint}</span>
|
||||
<span className={`px-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'
|
||||
}`}>
|
||||
<Stack gap={1} maxHeight="4rem" overflow="auto">
|
||||
{Object.entries(circuitBreakers).map(([endpoint, status]) => (
|
||||
<Box key={endpoint} display="flex" alignItems="center" justifyContent="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}
|
||||
</span>
|
||||
</Badge>
|
||||
{status.failures > 0 && (
|
||||
<span className="text-red-400 ml-1">({status.failures})</span>
|
||||
<Text size="xs" color="text-red-400" ml={1}>({status.failures})</Text>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* API Actions */}
|
||||
<div className="grid grid-cols-2 gap-2 mt-3">
|
||||
<button
|
||||
<Box display="grid" gridCols={2} gap={2} mt={3}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onHealthCheck}
|
||||
disabled={checkingHealth}
|
||||
className="px-2 py-1.5 bg-primary-blue hover:bg-primary-blue/80 text-white text-xs rounded transition-colors disabled:opacity-50 flex items-center justify-center gap-1"
|
||||
size="sm"
|
||||
icon={<Icon icon={RefreshCw} size={3} animate={checkingHealth ? 'spin' : 'none'} />}
|
||||
>
|
||||
<RefreshCw className={`w-3 h-3 ${checkingHealth ? 'animate-spin' : ''}`} />
|
||||
Health Check
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onResetStats}
|
||||
className="px-2 py-1.5 bg-iron-gray hover:bg-charcoal-outline text-gray-300 text-xs rounded transition-colors border border-charcoal-outline"
|
||||
size="sm"
|
||||
>
|
||||
Reset Stats
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 mt-2">
|
||||
<button
|
||||
<Box display="grid" gridCols={1} gap={2} mt={2}>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={onTestError}
|
||||
className="px-2 py-1.5 bg-red-600 hover:bg-red-700 text-white text-xs rounded transition-colors flex items-center justify-center gap-1"
|
||||
size="sm"
|
||||
icon={<Icon icon={Terminal} size={3} />}
|
||||
>
|
||||
<Terminal className="w-3 h-3" />
|
||||
Test Error Handler
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<div className="text-[10px] text-gray-600 mt-2">
|
||||
Last Check: {apiHealth.lastCheck ? new Date(apiHealth.lastCheck).toLocaleTimeString() : 'Never'}
|
||||
</div>
|
||||
</div>
|
||||
<Box mt={2}>
|
||||
<Text size="xs" color="text-gray-600">
|
||||
Last Check: {lastCheckLabel}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user