import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../shared/design_system/tokens/color_tokens.dart'; import '../../../../shared/design_system/tokens/spacing_tokens.dart'; import '../../../../shared/design_system/tokens/typography_tokens.dart'; import '../../../../shared/design_system/components/cards/uf_metric_card.dart'; import '../../../../shared/design_system/components/cards/uf_info_card.dart'; import '../../../../shared/design_system/components/inputs/uf_switch_tile.dart'; import '../../../../shared/design_system/components/inputs/uf_dropdown_tile.dart'; import '../../../../core/di/injection_container.dart'; import '../bloc/logs_monitoring_bloc.dart'; /// Page Logs & Monitoring - UnionFlow Mobile /// /// Page complète de consultation des logs système avec monitoring en temps réel, /// alertes, métriques système et gestion avancée des journaux. class LogsPage extends StatefulWidget { const LogsPage({super.key}); @override State createState() => _LogsPageState(); } class _LogsPageState extends State with TickerProviderStateMixin { late TabController _tabController; late Timer _refreshTimer; final TextEditingController _searchController = TextEditingController(); // États de filtrage String _selectedLevel = 'Tous'; String _selectedTimeRange = 'Dernières 24h'; String _selectedSource = 'Tous'; String _searchQuery = ''; bool _autoRefresh = true; bool _isLiveMode = false; // Données de configuration final List _levels = ['Tous', 'CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE']; final List _timeRanges = ['Temps réel', 'Dernière heure', 'Dernières 24h', 'Dernière semaine', 'Dernier mois']; final List _sources = ['Tous', 'API', 'Auth', 'Database', 'Cache', 'Security', 'Performance', 'System']; // Métriques système final Map _systemMetrics = { 'cpu': 23.5, 'memory': 67.2, 'disk': 45.8, 'network': 12.3, 'activeConnections': 1247, 'errorRate': 0.02, 'responseTime': 127, 'uptime': '15j 7h 23m', }; // Alertes actives final List> _activeAlerts = [ { 'id': 'alert_001', 'level': 'WARNING', 'title': 'CPU élevé', 'message': 'Utilisation CPU > 80% pendant 5 minutes', 'timestamp': DateTime.now().subtract(const Duration(minutes: 12)), 'acknowledged': false, }, { 'id': 'alert_002', 'level': 'INFO', 'title': 'Sauvegarde terminée', 'message': 'Sauvegarde automatique réussie (2.3 GB)', 'timestamp': DateTime.now().subtract(const Duration(hours: 2)), 'acknowledged': true, }, ]; @override void initState() { super.initState(); _tabController = TabController(length: 5, vsync: this); _startAutoRefresh(); } @override void dispose() { _tabController.dispose(); _searchController.dispose(); if (_autoRefresh) { _refreshTimer.cancel(); } super.dispose(); } void _startAutoRefresh() { if (_autoRefresh) { _refreshTimer = Timer.periodic(const Duration(seconds: 5), (timer) { if (mounted && _isLiveMode) { setState(() { // Simuler l'arrivée de nouveaux logs _updateSystemMetrics(); }); } }); } } void _updateSystemMetrics() { setState(() { _systemMetrics['cpu'] = 20 + (DateTime.now().millisecond % 40); _systemMetrics['memory'] = 60 + (DateTime.now().millisecond % 20); _systemMetrics['network'] = 10 + (DateTime.now().millisecond % 15); _systemMetrics['activeConnections'] = 1200 + (DateTime.now().millisecond % 100); }); } @override Widget build(BuildContext context) { return BlocProvider( create: (_) => sl()..add(LoadMetrics()), child: BlocConsumer( listener: (context, state) { if (state is LogsMonitoringSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: ColorTokens.success, behavior: SnackBarBehavior.floating, ), ); } else if (state is LogsMonitoringError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.error), backgroundColor: ColorTokens.error, behavior: SnackBarBehavior.floating, ), ); } }, builder: (context, state) { // Mettre à jour les métriques avec les données du state if (state is MetricsLoaded) { _updateSystemMetricsFromState(state.metrics); } return Scaffold( backgroundColor: ColorTokens.background, body: Column( children: [ _buildHeader(), _buildTabBar(), Expanded( child: TabBarView( controller: _tabController, children: [ _buildDashboardTab(), _buildLogsTab(), _buildAlertsTab(), _buildMetricsTab(), _buildSettingsTab(), ], ), ), ], ), ); }, ), ); } /// Header avec métriques système en temps réel Widget _buildHeader() { return Container( margin: const EdgeInsets.symmetric(horizontal: SpacingTokens.sm, vertical: SpacingTokens.xs), padding: const EdgeInsets.all(SpacingTokens.md), decoration: BoxDecoration( gradient: const LinearGradient( colors: ColorTokens.primaryGradient, begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), boxShadow: [ BoxShadow( color: ColorTokens.primary.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Column( children: [ Row( children: [ Container( padding: const EdgeInsets.all(SpacingTokens.sm), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), child: const Icon(Icons.monitor_heart, color: Colors.white, size: 20), ), const SizedBox(width: SpacingTokens.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Logs & Monitoring', style: TypographyTokens.headlineSmall.copyWith(color: Colors.white), ), Text( 'Surveillance système en temps réel', style: TypographyTokens.bodyMedium.copyWith(color: Colors.white.withOpacity(0.8)), ), ], ), ), Row( children: [ Container( decoration: BoxDecoration( color: _isLiveMode ? ColorTokens.success.withOpacity(0.3) : Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), child: IconButton( onPressed: () => _toggleLiveMode(), icon: Icon( _isLiveMode ? Icons.stop : Icons.play_arrow, color: Colors.white, ), tooltip: _isLiveMode ? 'Arrêter le mode temps réel' : 'Mode temps réel', ), ), const SizedBox(width: SpacingTokens.md), Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), child: IconButton( onPressed: () => _showExportDialog(), icon: const Icon(Icons.download, color: Colors.white), tooltip: 'Exporter les données', ), ), ], ), ], ), const SizedBox(height: SpacingTokens.sm), // Métriques système en temps réel Row( children: [ Expanded(child: UFMetricCard(label: 'CPU', value: '${_systemMetrics['cpu']?.toStringAsFixed(1)}%', icon: Icons.memory, color: _getCpuColor())), const SizedBox(width: SpacingTokens.md), Expanded(child: UFMetricCard(label: 'RAM', value: '${_systemMetrics['memory']?.toStringAsFixed(1)}%', icon: Icons.storage, color: _getMemoryColor())), const SizedBox(width: SpacingTokens.md), Expanded(child: UFMetricCard(label: 'Réseau', value: '${_systemMetrics['network']?.toStringAsFixed(1)} MB/s', icon: Icons.network_check, color: ColorTokens.info)), ], ), const SizedBox(height: SpacingTokens.sm), Row( children: [ Expanded(child: UFMetricCard(label: 'Connexions', value: '${_systemMetrics['activeConnections']}', icon: Icons.people, color: ColorTokens.success)), const SizedBox(width: SpacingTokens.md), Expanded(child: UFMetricCard(label: 'Erreurs/min', value: '${(_systemMetrics['errorRate']! * 100).toStringAsFixed(1)}', icon: Icons.error, color: ColorTokens.error)), const SizedBox(width: SpacingTokens.md), Expanded(child: UFMetricCard(label: 'Uptime', value: _systemMetrics['uptime'], icon: Icons.schedule, color: ColorTokens.secondary)), ], ), ], ), ); } Color _getCpuColor() { final cpu = _systemMetrics['cpu'] as double; if (cpu > 80) return ColorTokens.error; if (cpu > 60) return ColorTokens.warning; return ColorTokens.success; } Color _getMemoryColor() { final memory = _systemMetrics['memory'] as double; if (memory > 85) return ColorTokens.error; if (memory > 70) return ColorTokens.warning; return ColorTokens.success; } /// Barre d'onglets réorganisée Widget _buildTabBar() { return Container( margin: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg), decoration: BoxDecoration( color: ColorTokens.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: TabBar( controller: _tabController, labelColor: ColorTokens.primary, unselectedLabelColor: ColorTokens.onSurfaceVariant, indicatorColor: ColorTokens.primary, indicatorWeight: 3, labelStyle: TypographyTokens.labelSmall.copyWith(fontWeight: FontWeight.w600), isScrollable: true, tabs: const [ Tab(icon: Icon(Icons.dashboard, size: 16), text: 'Dashboard'), Tab(icon: Icon(Icons.list_alt, size: 16), text: 'Logs'), Tab(icon: Icon(Icons.notification_important, size: 16), text: 'Alertes'), Tab(icon: Icon(Icons.analytics, size: 16), text: 'Métriques'), Tab(icon: Icon(Icons.settings, size: 16), text: 'Config'), ], ), ); } // ==================== MÉTHODES D'ACTION ==================== void _toggleLiveMode() { setState(() { _isLiveMode = !_isLiveMode; if (_isLiveMode) { _selectedTimeRange = 'Temps réel'; _startAutoRefresh(); } else { if (_autoRefresh) { _refreshTimer.cancel(); } } }); _showSuccessSnackBar(_isLiveMode ? 'Mode temps réel activé' : 'Mode temps réel désactivé'); } void _showExportDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Exporter les données'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Sélectionnez les données à exporter :'), const SizedBox(height: 16), CheckboxListTile( title: const Text('Logs système'), value: true, onChanged: (value) {}, ), CheckboxListTile( title: const Text('Métriques'), value: true, onChanged: (value) {}, ), CheckboxListTile( title: const Text('Alertes'), value: false, onChanged: (value) {}, ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); _exportLogs(); }, style: ElevatedButton.styleFrom( backgroundColor: ColorTokens.primary, foregroundColor: Colors.white, ), child: const Text('Exporter'), ), ], ), ); } void _exportLogs() { _showSuccessSnackBar('Export des données lancé - Vous recevrez un email'); } // ==================== ONGLETS PRINCIPAUX ==================== /// Onglet Dashboard - Vue d'ensemble Widget _buildDashboardTab() { return SingleChildScrollView( padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( children: [ const SizedBox(height: SpacingTokens.xl), _buildSystemStatus(), const SizedBox(height: SpacingTokens.xl), _buildQuickStats(), const SizedBox(height: SpacingTokens.xl), _buildRecentAlerts(), const SizedBox(height: 80), ], ), ); } /// Statut système Widget _buildSystemStatus() { return UFInfoCard( title: 'État du système', icon: Icons.health_and_safety, trailing: Container( padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg, vertical: SpacingTokens.sm), decoration: BoxDecoration( color: ColorTokens.success, borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular), ), child: Text( 'OPÉRATIONNEL', style: TypographyTokens.labelSmall.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ), ), ), child: Column( children: [ Row( children: [ Expanded(child: _buildServiceStatus('API Server', true)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildServiceStatus('Database', true)), ], ), const SizedBox(height: SpacingTokens.lg), Row( children: [ Expanded(child: _buildServiceStatus('Keycloak', true)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildServiceStatus('CDN', false)), ], ), ], ), ); } Widget _buildServiceStatus(String service, bool isOnline) { return Container( padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( color: isOnline ? ColorTokens.success.withOpacity(0.05) : ColorTokens.error.withOpacity(0.05), borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), border: Border.all( color: isOnline ? ColorTokens.success.withOpacity(0.2) : ColorTokens.error.withOpacity(0.2), ), ), child: Row( children: [ Icon( Icons.circle, color: isOnline ? ColorTokens.success : ColorTokens.error, size: 12, ), const SizedBox(width: SpacingTokens.md), Expanded( child: Text( service, style: TypographyTokens.bodySmall.copyWith( fontWeight: FontWeight.w600, color: ColorTokens.onSurface, ), ), ), Text( isOnline ? 'OK' : 'DOWN', style: TypographyTokens.labelSmall.copyWith( color: isOnline ? ColorTokens.success : ColorTokens.error, fontWeight: FontWeight.w600, ), ), ], ), ); } /// Statistiques rapides Widget _buildQuickStats() { return UFInfoCard( title: 'Statistiques (dernières 24h)', icon: Icons.speed, child: Column( children: [ Row( children: [ Expanded(child: _buildStatItem('Logs totaux', '15,247', Icons.list_alt, ColorTokens.info)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildStatItem('Erreurs', '23', Icons.error, ColorTokens.error)), ], ), const SizedBox(height: SpacingTokens.lg), Row( children: [ Expanded(child: _buildStatItem('Warnings', '156', Icons.warning, ColorTokens.warning)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildStatItem('Temps réponse', '127ms', Icons.timer, ColorTokens.success)), ], ), ], ), ); } Widget _buildStatItem(String label, String value, IconData icon, Color color) { return Container( padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), border: Border.all(color: color.withOpacity(0.1)), ), child: Column( children: [ Icon(icon, color: color, size: 20), const SizedBox(height: SpacingTokens.md), Text( value, style: TypographyTokens.titleMedium.copyWith( fontWeight: FontWeight.bold, color: color, ), ), Text( label, style: TypographyTokens.bodySmall.copyWith( color: ColorTokens.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ); } /// Alertes récentes Widget _buildRecentAlerts() { return UFInfoCard( title: 'Alertes récentes', icon: Icons.notification_important, trailing: TextButton( onPressed: () => _tabController.animateTo(2), child: const Text('Voir tout'), ), child: Column( children: _activeAlerts.take(3).map((alert) => _buildAlertItem(alert)).toList(), ), ); } Widget _buildAlertItem(Map alert) { final color = _getAlertColor(alert['level']); return Container( margin: const EdgeInsets.only(bottom: SpacingTokens.lg), padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), border: Border.all(color: color.withOpacity(0.2)), ), child: Row( children: [ Icon( _getAlertIcon(alert['level']), color: color, size: 20, ), const SizedBox(width: SpacingTokens.lg), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( alert['title'], style: TypographyTokens.bodyMedium.copyWith( fontWeight: FontWeight.w600, color: color, ), ), Text( alert['message'], style: TypographyTokens.bodySmall.copyWith( color: ColorTokens.onSurfaceVariant, ), ), ], ), ), Text( _formatTimestamp(alert['timestamp']), style: TypographyTokens.labelSmall.copyWith( color: ColorTokens.onSurfaceVariant, ), ), ], ), ); } /// Onglet Logs - Consultation détaillée Widget _buildLogsTab() { return Column( children: [ _buildLogsFilters(), Expanded(child: _buildLogsList()), ], ); } Widget _buildLogsFilters() { return Container( margin: const EdgeInsets.all(SpacingTokens.lg), padding: const EdgeInsets.all(SpacingTokens.xl), decoration: BoxDecoration( color: ColorTokens.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Row( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: TextField( controller: _searchController, onChanged: (value) => setState(() => _searchQuery = value), decoration: const InputDecoration( hintText: 'Rechercher dans les logs...', prefixIcon: Icon(Icons.search), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), ), ), ), const SizedBox(width: 12), _buildFilterChip(_selectedLevel, _levels), ], ), const SizedBox(height: 12), Row( children: [ Expanded(child: _buildFilterChip(_selectedTimeRange, _timeRanges)), const SizedBox(width: 12), Expanded(child: _buildFilterChip(_selectedSource, _sources)), const SizedBox(width: 12), ElevatedButton.icon( onPressed: () => _clearFilters(), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[100], foregroundColor: Colors.grey[700], elevation: 0, ), icon: const Icon(Icons.clear, size: 16), label: const Text('Reset'), ), ], ), ], ), ); } Widget _buildFilterChip(String value, List options) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[300]!), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: value, isExpanded: true, onChanged: (newValue) { setState(() { if (options == _levels) _selectedLevel = newValue!; if (options == _timeRanges) _selectedTimeRange = newValue!; if (options == _sources) _selectedSource = newValue!; }); }, items: options.map((option) => DropdownMenuItem(value: option, child: Text(option))).toList(), ), ), ); } Widget _buildLogsList() { final logs = _getFilteredLogs(); return ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: logs.length, itemBuilder: (context, index) => _buildLogEntry(logs[index]), ); } Widget _buildLogEntry(Map log) { final color = _getLogColor(log['level']); return Container( margin: const EdgeInsets.only(bottom: SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( color: ColorTokens.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), border: Border.all(color: color.withOpacity(0.2)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.md, vertical: SpacingTokens.sm), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), ), child: Text( log['level'], style: TypographyTokens.labelSmall.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ), ), ), const SizedBox(width: SpacingTokens.md), Text( log['timestamp'], style: TypographyTokens.bodySmall.copyWith( color: ColorTokens.onSurfaceVariant, ), ), const Spacer(), Text( log['source'], style: TypographyTokens.bodySmall.copyWith( color: ColorTokens.onSurfaceVariant, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: SpacingTokens.md), Text( log['message'], style: TypographyTokens.bodyMedium.copyWith( color: ColorTokens.onSurface, ), ), if (log['details'] != null) ...[ const SizedBox(height: SpacingTokens.sm), Text( log['details'], style: TypographyTokens.bodySmall.copyWith( color: ColorTokens.onSurfaceVariant, ), ), ], ], ), ); } /// Onglet Alertes Widget _buildAlertsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), _buildAlertsConfiguration(), const SizedBox(height: 16), _buildActiveAlerts(), const SizedBox(height: 80), ], ), ); } Widget _buildAlertsConfiguration() { return UFInfoCard( title: 'Configuration des alertes', icon: Icons.tune, child: Column( children: [ UFSwitchTile( title: 'CPU élevé', subtitle: 'Alerte si CPU > 80% pendant 5 min', value: true, onChanged: (value) => _showSuccessSnackBar('Alerte ${value ? 'activée' : 'désactivée'}'), ), UFSwitchTile( title: 'Mémoire faible', subtitle: 'Alerte si RAM < 20%', value: true, onChanged: (value) => _showSuccessSnackBar('Alerte ${value ? 'activée' : 'désactivée'}'), ), UFSwitchTile( title: 'Erreurs critiques', subtitle: 'Alerte pour toute erreur CRITICAL', value: true, onChanged: (value) => _showSuccessSnackBar('Alerte ${value ? 'activée' : 'désactivée'}'), ), UFSwitchTile( title: 'Connexions échouées', subtitle: 'Alerte si > 100 échecs/min', value: false, onChanged: (value) => _showSuccessSnackBar('Alerte ${value ? 'activée' : 'désactivée'}'), ), ], ), ); } Widget _buildActiveAlerts() { return UFInfoCard( title: 'Alertes actives', icon: Icons.notifications_active, child: Column( children: _activeAlerts.map((alert) => _buildDetailedAlertItem(alert)).toList(), ), ); } Widget _buildDetailedAlertItem(Map alert) { final color = _getAlertColor(alert['level']); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(_getAlertIcon(alert['level']), color: color, size: 20), const SizedBox(width: 8), Expanded( child: Text( alert['title'], style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: color, ), ), ), if (!alert['acknowledged']) ElevatedButton( onPressed: () => _acknowledgeAlert(alert['id']), style: ElevatedButton.styleFrom( backgroundColor: color, foregroundColor: Colors.white, minimumSize: const Size(80, 32), ), child: const Text('Acquitter', style: TextStyle(fontSize: 12)), ), ], ), const SizedBox(height: 8), Text( alert['message'], style: TextStyle( fontSize: 12, color: Colors.grey[700], ), ), const SizedBox(height: 4), Text( 'Il y a ${_formatTimestamp(alert['timestamp'])}', style: TextStyle( fontSize: 11, color: Colors.grey[500], ), ), ], ), ); } /// Onglet Métriques Widget _buildMetricsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), _buildSystemMetrics(), const SizedBox(height: 16), _buildPerformanceMetrics(), const SizedBox(height: 80), ], ), ); } Widget _buildSystemMetrics() { return UFInfoCard( title: 'Métriques système', icon: Icons.computer, child: Column( children: [ Row( children: [ Expanded(child: _buildMetricProgress('CPU', _systemMetrics['cpu'], '%', _getCpuColor())), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildMetricProgress('Mémoire', _systemMetrics['memory'], '%', _getMemoryColor())), ], ), const SizedBox(height: SpacingTokens.lg), Row( children: [ Expanded(child: _buildMetricProgress('Disque', _systemMetrics['disk'], '%', ColorTokens.warning)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildMetricValue('Uptime', _systemMetrics['uptime'], '', ColorTokens.secondary)), ], ), ], ), ); } Widget _buildMetricProgress(String label, double value, String unit, Color color) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TypographyTokens.bodyMedium.copyWith( fontWeight: FontWeight.w600, color: ColorTokens.onSurface, ), ), Text( '${value.toStringAsFixed(1)}$unit', style: TypographyTokens.bodyMedium.copyWith( fontWeight: FontWeight.w600, color: color, ), ), ], ), const SizedBox(height: SpacingTokens.md), LinearProgressIndicator( value: value / 100, backgroundColor: ColorTokens.surfaceVariant, valueColor: AlwaysStoppedAnimation(color), ), ], ); } Widget _buildMetricValue(String label, dynamic value, String unit, Color color) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TypographyTokens.bodyMedium.copyWith( fontWeight: FontWeight.w600, color: ColorTokens.onSurface, ), ), const SizedBox(height: SpacingTokens.md), Text( '$value$unit', style: TypographyTokens.headlineSmall.copyWith( fontWeight: FontWeight.bold, color: color, ), ), ], ); } Widget _buildPerformanceMetrics() { return UFInfoCard( title: 'Métriques de performance', icon: Icons.speed, child: Column( children: [ Row( children: [ Expanded(child: _buildMetricValue('Connexions actives', _systemMetrics['activeConnections'], '', ColorTokens.success)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildMetricValue('Temps de réponse', '${_systemMetrics['responseTime']}', 'ms', ColorTokens.info)), ], ), const SizedBox(height: SpacingTokens.lg), Row( children: [ Expanded(child: _buildMetricValue('Taux d\'erreur', '${(_systemMetrics['errorRate']! * 100).toStringAsFixed(2)}', '%', ColorTokens.error)), const SizedBox(width: SpacingTokens.lg), Expanded(child: _buildMetricValue('Réseau', '${_systemMetrics['network']?.toStringAsFixed(1)}', ' MB/s', ColorTokens.warning)), ], ), ], ), ); } /// Onglet Configuration Widget _buildSettingsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), _buildLoggingSettings(), const SizedBox(height: 16), _buildMonitoringSettings(), const SizedBox(height: 80), ], ), ); } Widget _buildLoggingSettings() { return UFInfoCard( title: 'Configuration des logs', icon: Icons.settings, child: Column( children: [ UFDropdownTile( title: 'Niveau de log', value: 'INFO', items: const ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL'], onChanged: (value) => _showSuccessSnackBar('Paramètre mis à jour'), ), UFDropdownTile( title: 'Rétention', value: '30 jours', items: const ['7 jours', '30 jours', '90 jours', '1 an'], onChanged: (value) => _showSuccessSnackBar('Paramètre mis à jour'), ), UFDropdownTile( title: 'Format', value: 'JSON', items: const ['JSON', 'Plain Text', 'Structured'], onChanged: (value) => _showSuccessSnackBar('Paramètre mis à jour'), ), UFSwitchTile( title: 'Logs détaillés', subtitle: 'Inclure les stack traces', value: true, onChanged: (value) => _showSuccessSnackBar('Paramètre ${value ? 'activé' : 'désactivé'}'), ), UFSwitchTile( title: 'Compression', subtitle: 'Compresser les anciens logs', value: true, onChanged: (value) => _showSuccessSnackBar('Paramètre ${value ? 'activé' : 'désactivé'}'), ), ], ), ); } Widget _buildMonitoringSettings() { return UFInfoCard( title: 'Configuration du monitoring', icon: Icons.monitor, child: Column( children: [ UFDropdownTile( title: 'Intervalle de collecte', value: '5 secondes', items: const ['1 seconde', '5 secondes', '30 secondes', '1 minute'], onChanged: (value) => _showSuccessSnackBar('Paramètre mis à jour'), ), UFDropdownTile( title: 'Historique des métriques', value: '7 jours', items: const ['1 jour', '7 jours', '30 jours', '90 jours'], onChanged: (value) => _showSuccessSnackBar('Paramètre mis à jour'), ), UFSwitchTile( title: 'Monitoring temps réel', subtitle: 'Mise à jour automatique', value: _autoRefresh, onChanged: (value) { setState(() => _autoRefresh = value); if (value) { _startAutoRefresh(); } else { _refreshTimer.cancel(); } _showSuccessSnackBar('Paramètre ${value ? 'activé' : 'désactivé'}'); }, ), UFSwitchTile( title: 'Alertes email', subtitle: 'Notifications par email', value: true, onChanged: (value) => _showSuccessSnackBar('Paramètre ${value ? 'activé' : 'désactivé'}'), ), UFSwitchTile( title: 'Alertes push', subtitle: 'Notifications push mobile', value: false, onChanged: (value) => _showSuccessSnackBar('Paramètre ${value ? 'activé' : 'désactivé'}'), ), ], ), ); } // ==================== MÉTHODES UTILITAIRES ==================== List> _getFilteredLogs() { List> allLogs = [ {'level': 'CRITICAL', 'timestamp': '16:15:42', 'source': 'Database', 'message': 'Connexion à la base de données perdue', 'details': 'Pool de connexions épuisé'}, {'level': 'ERROR', 'timestamp': '16:12:33', 'source': 'API', 'message': 'Erreur 500 sur /api/members', 'details': 'NullPointerException dans MemberService.findAll()'}, {'level': 'WARN', 'timestamp': '16:10:15', 'source': 'Auth', 'message': 'Tentative de connexion avec mot de passe incorrect', 'details': 'IP: 192.168.1.100 - Utilisateur: admin@test.com'}, {'level': 'INFO', 'timestamp': '16:08:22', 'source': 'System', 'message': 'Sauvegarde automatique terminée', 'details': 'Taille: 2.3 GB - Durée: 45 secondes'}, {'level': 'DEBUG', 'timestamp': '16:05:45', 'source': 'Cache', 'message': 'Cache invalidé pour user_sessions', 'details': 'Raison: Expiration automatique'}, {'level': 'TRACE', 'timestamp': '16:03:12', 'source': 'Performance', 'message': 'Requête SQL exécutée', 'details': 'SELECT * FROM members WHERE active = true - Durée: 23ms'}, ]; // Filtrage par niveau if (_selectedLevel != 'Tous') { allLogs = allLogs.where((log) => log['level'] == _selectedLevel).toList(); } // Filtrage par source if (_selectedSource != 'Tous') { allLogs = allLogs.where((log) => log['source'] == _selectedSource).toList(); } // Filtrage par recherche if (_searchQuery.isNotEmpty) { allLogs = allLogs.where((log) => log['message'].toLowerCase().contains(_searchQuery.toLowerCase()) || log['source'].toLowerCase().contains(_searchQuery.toLowerCase()) ).toList(); } return allLogs; } Color _getLogColor(String level) { switch (level) { case 'CRITICAL': return ColorTokens.secondary; case 'ERROR': return ColorTokens.error; case 'WARN': return ColorTokens.warning; case 'INFO': return ColorTokens.info; case 'DEBUG': return ColorTokens.success; case 'TRACE': return ColorTokens.onSurfaceVariant; default: return ColorTokens.onSurfaceVariant; } } Color _getAlertColor(String level) { switch (level) { case 'CRITICAL': return ColorTokens.secondary; case 'ERROR': return ColorTokens.error; case 'WARNING': return ColorTokens.warning; case 'INFO': return ColorTokens.info; default: return ColorTokens.onSurfaceVariant; } } IconData _getAlertIcon(String level) { switch (level) { case 'CRITICAL': return Icons.dangerous; case 'ERROR': return Icons.error; case 'WARNING': return Icons.warning; case 'INFO': return Icons.info; default: return Icons.notifications; } } String _formatTimestamp(DateTime timestamp) { final now = DateTime.now(); final difference = now.difference(timestamp); if (difference.inMinutes < 60) { return '${difference.inMinutes}min'; } else if (difference.inHours < 24) { return '${difference.inHours}h'; } else { return '${difference.inDays}j'; } } void _acknowledgeAlert(String alertId) { setState(() { final alert = _activeAlerts.firstWhere((a) => a['id'] == alertId); alert['acknowledged'] = true; }); _showSuccessSnackBar('Alerte acquittée'); } void _clearFilters() { setState(() { _selectedLevel = 'Tous'; _selectedTimeRange = 'Dernières 24h'; _selectedSource = 'Tous'; _searchQuery = ''; _searchController.clear(); }); _showSuccessSnackBar('Filtres réinitialisés'); } void _updateSystemMetricsFromState(dynamic metrics) { if (metrics == null) return; setState(() { _systemMetrics['cpu'] = metrics.cpuUsagePercent ?? 23.5; _systemMetrics['memory'] = metrics.memoryUsagePercent ?? 67.2; _systemMetrics['disk'] = metrics.diskUsagePercent ?? 45.8; _systemMetrics['network'] = metrics.networkUsageMbps ?? 12.3; _systemMetrics['activeConnections'] = metrics.activeConnections ?? 1247; _systemMetrics['errorRate'] = metrics.errorRate ?? 0.02; _systemMetrics['responseTime'] = metrics.averageResponseTimeMs ?? 127; _systemMetrics['uptime'] = metrics.uptimeFormatted ?? '15j 7h 23m'; }); } void _showSuccessSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: ColorTokens.success, behavior: SnackBarBehavior.floating, ), ); } }