- Created MonitoringDashboard component to visualize system metrics - Added monitoring tab to AdminDashboardPage (/admin route) - Displays validation metrics (total, successful, failed, failure rate) - Shows top 5 endpoints with most validation failures - Displays Sentry error tracking status and configuration - Shows performance monitoring information - Auto-refreshes metrics every 30 seconds - Manual refresh button available - Links to Sentry dashboard when configured - Complete monitoring solution for system health visibility
423 lines
15 KiB
TypeScript
423 lines
15 KiB
TypeScript
/**
|
|
* Monitor 7: Monitoring Dashboard Component
|
|
* Visualizes validation metrics, API performance, and error tracking
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Activity,
|
|
AlertTriangle,
|
|
CheckCircle,
|
|
XCircle,
|
|
TrendingUp,
|
|
Clock,
|
|
ExternalLink,
|
|
RefreshCw,
|
|
} from 'lucide-react';
|
|
import { validationMetrics } from '@/services/api/client';
|
|
import { env } from '@/config/env';
|
|
import { logger } from '@/utils/logger';
|
|
|
|
interface MonitoringMetrics {
|
|
validation: {
|
|
total: number;
|
|
successful: number;
|
|
failed: number;
|
|
failureRate: number;
|
|
lastFailureTime?: string;
|
|
lastSuccessTime?: string;
|
|
failuresByEndpoint: Record<string, number>;
|
|
};
|
|
sentry: {
|
|
enabled: boolean;
|
|
dsnConfigured: boolean;
|
|
};
|
|
}
|
|
|
|
export function MonitoringDashboard() {
|
|
const [metrics, setMetrics] = useState<MonitoringMetrics | null>(null);
|
|
const [lastUpdate, setLastUpdate] = useState<Date>(new Date());
|
|
|
|
const loadMetrics = () => {
|
|
try {
|
|
const validationMetricsData = validationMetrics.getMetrics();
|
|
|
|
setMetrics({
|
|
validation: {
|
|
total: validationMetricsData.totalValidations,
|
|
successful: validationMetricsData.successfulValidations,
|
|
failed: validationMetricsData.failedValidations,
|
|
failureRate: validationMetricsData.failureRate,
|
|
lastFailureTime: validationMetricsData.lastFailureTime,
|
|
lastSuccessTime: validationMetricsData.lastSuccessTime,
|
|
failuresByEndpoint: validationMetricsData.failuresByEndpoint,
|
|
},
|
|
sentry: {
|
|
enabled: !!env.SENTRY_DSN && import.meta.env.PROD,
|
|
dsnConfigured: !!env.SENTRY_DSN,
|
|
},
|
|
});
|
|
setLastUpdate(new Date());
|
|
} catch (error) {
|
|
logger.error('[MonitoringDashboard] Failed to load metrics', {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadMetrics();
|
|
// Refresh metrics every 30 seconds
|
|
const interval = setInterval(loadMetrics, 30000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
if (!metrics) {
|
|
return (
|
|
<div className="flex items-center justify-center h-[400px]">
|
|
<div className="text-center">
|
|
<RefreshCw className="h-8 w-8 animate-spin mx-auto mb-4 text-muted-foreground" />
|
|
<p className="text-muted-foreground">Chargement des métriques...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const getFailureRateColor = (rate: number): string => {
|
|
if (rate === 0) return 'text-green-600';
|
|
if (rate < 1) return 'text-yellow-600';
|
|
if (rate < 5) return 'text-orange-600';
|
|
return 'text-red-600';
|
|
};
|
|
|
|
const getFailureRateBadge = (rate: number) => {
|
|
if (rate === 0) return 'success';
|
|
if (rate < 1) return 'default';
|
|
if (rate < 5) return 'warning';
|
|
return 'error';
|
|
};
|
|
|
|
const topFailureEndpoints = Object.entries(metrics.validation.failuresByEndpoint)
|
|
.sort(([, a], [, b]) => b - a)
|
|
.slice(0, 5);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-2xl font-bold flex items-center gap-2">
|
|
<Activity className="h-6 w-6" />
|
|
Monitoring du Système
|
|
</h2>
|
|
<p className="text-muted-foreground text-sm mt-1">
|
|
Métriques de validation, performance API et suivi des erreurs
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<p className="text-xs text-muted-foreground">
|
|
Dernière mise à jour: {lastUpdate.toLocaleTimeString('fr-FR')}
|
|
</p>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={loadMetrics}
|
|
className="gap-2"
|
|
>
|
|
<RefreshCw className="h-4 w-4" />
|
|
Actualiser
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Validation Metrics */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<Activity className="h-4 w-4" />
|
|
Total Validations
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">
|
|
{metrics.validation.total.toLocaleString()}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
Validations depuis le démarrage
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<CheckCircle className="h-4 w-4 text-green-600" />
|
|
Validations Réussies
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-green-600">
|
|
{metrics.validation.successful.toLocaleString()}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{metrics.validation.total > 0
|
|
? (
|
|
(metrics.validation.successful / metrics.validation.total) *
|
|
100
|
|
).toFixed(1)
|
|
: 0}
|
|
% de réussite
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<XCircle className="h-4 w-4 text-red-600" />
|
|
Validations Échouées
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-red-600">
|
|
{metrics.validation.failed.toLocaleString()}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{metrics.validation.failureRate.toFixed(2)}% d'échec
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<TrendingUp className="h-4 w-4" />
|
|
Taux d'Échec
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div
|
|
className={`text-2xl font-bold ${getFailureRateColor(
|
|
metrics.validation.failureRate,
|
|
)}`}
|
|
>
|
|
{metrics.validation.failureRate.toFixed(2)}%
|
|
</div>
|
|
<Badge
|
|
variant={getFailureRateBadge(metrics.validation.failureRate)}
|
|
className="mt-2"
|
|
>
|
|
{metrics.validation.failureRate === 0
|
|
? 'Parfait'
|
|
: metrics.validation.failureRate < 1
|
|
? 'Bon'
|
|
: metrics.validation.failureRate < 5
|
|
? 'Attention'
|
|
: 'Critique'}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Detailed Metrics */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Validation Details */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Activity className="h-5 w-5" />
|
|
Détails des Validations
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Statistiques détaillées des validations API
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-muted-foreground">
|
|
Dernière validation réussie:
|
|
</span>
|
|
<span className="text-sm font-medium">
|
|
{metrics.validation.lastSuccessTime
|
|
? new Date(
|
|
metrics.validation.lastSuccessTime,
|
|
).toLocaleString('fr-FR')
|
|
: 'Aucune'}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-muted-foreground">
|
|
Dernière validation échouée:
|
|
</span>
|
|
<span className="text-sm font-medium text-red-600">
|
|
{metrics.validation.lastFailureTime
|
|
? new Date(
|
|
metrics.validation.lastFailureTime,
|
|
).toLocaleString('fr-FR')
|
|
: 'Aucune'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{topFailureEndpoints.length > 0 && (
|
|
<div className="mt-4">
|
|
<h4 className="text-sm font-medium mb-2">
|
|
Endpoints avec le plus d'échecs:
|
|
</h4>
|
|
<div className="space-y-1">
|
|
{topFailureEndpoints.map(([endpoint, count]) => (
|
|
<div
|
|
key={endpoint}
|
|
className="flex justify-between items-center p-2 bg-muted rounded text-sm"
|
|
>
|
|
<code className="text-xs">{endpoint}</code>
|
|
<Badge variant="error">{count}</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Error Tracking Status */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<AlertTriangle className="h-5 w-5" />
|
|
Suivi des Erreurs
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Configuration et statut du suivi des erreurs
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between p-3 bg-muted rounded">
|
|
<div className="flex items-center gap-2">
|
|
{metrics.sentry.enabled ? (
|
|
<CheckCircle className="h-5 w-5 text-green-600" />
|
|
) : (
|
|
<XCircle className="h-5 w-5 text-muted-foreground" />
|
|
)}
|
|
<span className="text-sm font-medium">Sentry Error Tracking</span>
|
|
</div>
|
|
<Badge
|
|
variant={metrics.sentry.enabled ? 'success' : 'secondary'}
|
|
>
|
|
{metrics.sentry.enabled ? 'Actif' : 'Inactif'}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-muted rounded">
|
|
<div className="flex items-center gap-2">
|
|
{metrics.sentry.dsnConfigured ? (
|
|
<CheckCircle className="h-5 w-5 text-green-600" />
|
|
) : (
|
|
<XCircle className="h-5 w-5 text-muted-foreground" />
|
|
)}
|
|
<span className="text-sm font-medium">DSN Configuré</span>
|
|
</div>
|
|
<Badge
|
|
variant={metrics.sentry.dsnConfigured ? 'success' : 'secondary'}
|
|
>
|
|
{metrics.sentry.dsnConfigured ? 'Oui' : 'Non'}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{metrics.sentry.enabled && (
|
|
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-950 rounded border border-blue-200 dark:border-blue-800">
|
|
<p className="text-xs text-blue-900 dark:text-blue-100 mb-2">
|
|
Les erreurs sont automatiquement envoyées à Sentry pour le suivi
|
|
et l'analyse.
|
|
</p>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="w-full gap-2"
|
|
onClick={() => {
|
|
// Open Sentry dashboard (if URL is known)
|
|
// This would typically be configured in env vars
|
|
const sentryDsn = env.SENTRY_DSN;
|
|
const sentryUrl = sentryDsn
|
|
? `https://sentry.io/organizations/${sentryDsn.split('@')[1]?.split('/')[0]}/issues/`
|
|
: 'https://sentry.io';
|
|
window.open(sentryUrl, '_blank');
|
|
}}
|
|
>
|
|
<ExternalLink className="h-4 w-4" />
|
|
Ouvrir Sentry Dashboard
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{!metrics.sentry.enabled && (
|
|
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-950 rounded border border-yellow-200 dark:border-yellow-800">
|
|
<p className="text-xs text-yellow-900 dark:text-yellow-100">
|
|
Le suivi des erreurs Sentry n'est pas activé. Configurez{' '}
|
|
<code className="bg-yellow-100 dark:bg-yellow-900 px-1 rounded">
|
|
VITE_SENTRY_DSN
|
|
</code>{' '}
|
|
pour activer le suivi des erreurs en production.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Performance Monitoring Info */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Clock className="h-5 w-5" />
|
|
Performance Monitoring
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Les métriques de performance sont suivies automatiquement
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="p-3 bg-muted rounded">
|
|
<div className="text-sm font-medium mb-1">
|
|
Temps de Rendu
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Suivi via Sentry Browser Tracing
|
|
</p>
|
|
</div>
|
|
<div className="p-3 bg-muted rounded">
|
|
<div className="text-sm font-medium mb-1">
|
|
Temps de Réponse API
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Suivi dans le client API avec détection des requêtes lentes
|
|
</p>
|
|
</div>
|
|
<div className="p-3 bg-muted rounded">
|
|
<div className="text-sm font-medium mb-1">
|
|
Erreurs Réseau
|
|
</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Détection des pannes partielles vs complètes
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|