feat: WebSocket temps réel + Finance Workflow + corrections
- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics) * Backend: KafkaEventProducer, KafkaEventConsumer * Mobile: WebSocketService (reconnection, heartbeat, typed events) * DashboardBloc: Auto-refresh depuis WebSocket events - Finance Workflow: approbations + budgets (backend + mobile) * Backend: entities, services, resources, migrations Flyway V6 * Mobile: features finance_workflow complète avec BLoC - Corrections DI: interfaces IRepository partout * IProfileRepository, IOrganizationRepository, IMembreRepository * GetIt configuré avec @injectable - Spec-Kit: constitution + templates mis à jour * .specify/memory/constitution.md enrichie * Templates agent, plan, spec, tasks, checklist - Nettoyage: fichiers temporaires supprimés Signed-off-by: lions dev Team
This commit is contained in:
@@ -5,6 +5,7 @@ library feedback_page;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
|
||||
class FeedbackPage extends StatefulWidget {
|
||||
@@ -54,7 +55,8 @@ class _FeedbackPageState extends State<FeedbackPage> {
|
||||
_messageController.clear();
|
||||
_showSnackBar('Merci pour votre retour !');
|
||||
}
|
||||
} catch (_) {
|
||||
} catch (e, st) {
|
||||
AppLogger.error('FeedbackPage: envoi feedback échoué', error: e, stackTrace: st);
|
||||
if (mounted) {
|
||||
_showSnackBar('Envoi échoué. Réessayez plus tard.', isError: true);
|
||||
}
|
||||
|
||||
@@ -23,14 +23,16 @@ class _LanguageSettingsPageState extends State<LanguageSettingsPage> {
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_syncFromProvider();
|
||||
}
|
||||
|
||||
void _syncFromProvider() {
|
||||
final lp = context.read<LocaleProvider>();
|
||||
setState(() => _selectedLanguage = lp.currentLanguageName);
|
||||
if (lp.currentLanguageName != _selectedLanguage) {
|
||||
setState(() => _selectedLanguage = lp.currentLanguageName);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _changeLanguage(String languageName, String code) async {
|
||||
|
||||
@@ -4,6 +4,7 @@ library privacy_settings_page;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
|
||||
class PrivacySettingsPage extends StatefulWidget {
|
||||
@@ -278,7 +279,13 @@ class _PrivacySettingsPageState extends State<PrivacySettingsPage> {
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final uri = Uri.parse('mailto:support@unionflow.com?subject=Demande de suppression de compte');
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||
child: const Text('Contacter l\'administrateur', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
@@ -377,7 +384,9 @@ class _PrivacySettingsPageState extends State<PrivacySettingsPage> {
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: ColorTokens.primary,
|
||||
activeTrackColor: ColorTokens.primary,
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) =>
|
||||
states.contains(WidgetState.selected) ? Colors.white : null),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
|
||||
import '../../../../features/authentication/data/models/user_role.dart';
|
||||
import '../../data/models/system_metrics_model.dart';
|
||||
import '../bloc/system_settings_bloc.dart';
|
||||
import '../bloc/system_settings_event.dart';
|
||||
import '../bloc/system_settings_state.dart';
|
||||
|
||||
/// Page Paramètres Système - UnionFlow Mobile
|
||||
///
|
||||
@@ -15,7 +23,10 @@ class SystemSettingsPage extends StatefulWidget {
|
||||
class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
with TickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
|
||||
// Métriques système en temps réel
|
||||
SystemMetricsModel? _metrics;
|
||||
|
||||
// États des paramètres système
|
||||
bool _maintenanceMode = false;
|
||||
bool _debugMode = false;
|
||||
@@ -25,11 +36,11 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
bool _sslEnforced = true;
|
||||
bool _apiLoggingEnabled = false;
|
||||
bool _performanceMonitoring = true;
|
||||
|
||||
|
||||
String _selectedLogLevel = 'INFO';
|
||||
String _selectedBackupFrequency = 'Quotidien';
|
||||
String _selectedCacheStrategy = 'Intelligent';
|
||||
|
||||
|
||||
final List<String> _logLevels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
|
||||
final List<String> _backupFrequencies = ['Temps réel', 'Horaire', 'Quotidien', 'Hebdomadaire'];
|
||||
final List<String> _cacheStrategies = ['Agressif', 'Intelligent', 'Conservateur', 'Désactivé'];
|
||||
@@ -49,31 +60,112 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FA),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header harmonisé
|
||||
_buildHeader(),
|
||||
|
||||
// Onglets
|
||||
_buildTabBar(),
|
||||
|
||||
// Contenu des onglets
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
return BlocBuilder<AuthBloc, AuthState>(
|
||||
builder: (context, authState) {
|
||||
// Accès réservé aux super administrateurs (configuration système globale)
|
||||
if (authState is! AuthAuthenticated || authState.effectiveRole != UserRole.superAdmin) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FA),
|
||||
appBar: AppBar(
|
||||
title: const Text('Paramètres Système'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
tooltip: 'Retour',
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.lock_outline, size: 64, color: ColorTokens.onSurfaceVariant.withOpacity(0.5)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Accès réservé',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorTokens.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Les paramètres système sont réservés aux administrateurs plateforme.',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => sl<SystemSettingsBloc>()
|
||||
..add(LoadSystemConfig())
|
||||
..add(LoadSystemMetrics()),
|
||||
child: BlocConsumer<SystemSettingsBloc, SystemSettingsState>(
|
||||
listener: (context, state) {
|
||||
if (state is SystemMetricsLoaded) {
|
||||
setState(() {
|
||||
_metrics = state.metrics;
|
||||
});
|
||||
} else if (state is SystemSettingsSuccess) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: ColorTokens.success,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
} else if (state is SystemSettingsError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.error),
|
||||
backgroundColor: ColorTokens.error,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FA),
|
||||
body: Column(
|
||||
children: [
|
||||
_buildGeneralTab(),
|
||||
_buildSecurityTab(),
|
||||
_buildPerformanceTab(),
|
||||
_buildMaintenanceTab(),
|
||||
_buildMonitoringTab(),
|
||||
// Header harmonisé
|
||||
_buildHeader(),
|
||||
|
||||
// Onglets
|
||||
_buildTabBar(),
|
||||
|
||||
// Contenu des onglets
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildGeneralTab(),
|
||||
_buildSecurityTab(),
|
||||
_buildPerformanceTab(),
|
||||
_buildMaintenanceTab(),
|
||||
_buildMonitoringTab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,7 +271,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
Expanded(
|
||||
child: _buildSystemIndicator(
|
||||
'Statut',
|
||||
_maintenanceMode ? 'Maintenance' : 'Opérationnel',
|
||||
_metrics?.systemStatus ?? (_maintenanceMode ? 'MAINTENANCE' : 'OPERATIONAL'),
|
||||
_maintenanceMode ? Icons.build : Icons.check_circle,
|
||||
_maintenanceMode ? Colors.orange : Colors.green,
|
||||
),
|
||||
@@ -188,7 +280,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
Expanded(
|
||||
child: _buildSystemIndicator(
|
||||
'Charge CPU',
|
||||
'23%',
|
||||
_metrics != null ? '${_metrics!.cpuUsagePercent?.toStringAsFixed(0) ?? '0'}%' : '-',
|
||||
Icons.memory,
|
||||
Colors.blue,
|
||||
),
|
||||
@@ -197,7 +289,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
Expanded(
|
||||
child: _buildSystemIndicator(
|
||||
'Utilisateurs',
|
||||
'1,247',
|
||||
_metrics?.activeUsersCount?.toString() ?? '-',
|
||||
Icons.people,
|
||||
Colors.purple,
|
||||
),
|
||||
@@ -361,7 +453,9 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
),
|
||||
_buildActionSetting(
|
||||
'Vider le cache système',
|
||||
'Supprimer tous les fichiers temporaires (2.3 GB)',
|
||||
_metrics != null
|
||||
? 'Supprimer tous les fichiers temporaires (${_metrics!.totalCacheSizeFormatted ?? "0 B"})'
|
||||
: 'Supprimer tous les fichiers temporaires',
|
||||
Icons.delete_sweep,
|
||||
const Color(0xFFE17055),
|
||||
() => _clearSystemCache(),
|
||||
@@ -384,9 +478,9 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'Paramètres de connectivité',
|
||||
Icons.network_check,
|
||||
[
|
||||
_buildInfoSetting('Serveur API', 'https://api.unionflow.com'),
|
||||
_buildInfoSetting('Serveur d\'authentification', 'https://auth.unionflow.com'),
|
||||
_buildInfoSetting('CDN Assets', 'https://cdn.unionflow.com'),
|
||||
_buildInfoSetting('Serveur API', _metrics?.apiBaseUrl ?? 'Non configuré'),
|
||||
_buildInfoSetting('Serveur d\'authentification', _metrics?.authServerUrl ?? 'Non configuré'),
|
||||
_buildInfoSetting('CDN Assets', _metrics?.cdnUrl ?? 'Non configuré'),
|
||||
_buildActionSetting(
|
||||
'Tester la connectivité',
|
||||
'Vérifier la connexion aux services',
|
||||
@@ -453,8 +547,14 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'Gestion des accès utilisateurs',
|
||||
Icons.login,
|
||||
[
|
||||
_buildInfoSetting('Sessions actives', '1,247 utilisateurs connectés'),
|
||||
_buildInfoSetting('Tentatives échouées', '23 dans les dernières 24h'),
|
||||
_buildInfoSetting(
|
||||
'Sessions actives',
|
||||
_metrics != null ? '${_metrics!.activeSessionsCount ?? 0} sessions actives' : 'Chargement...',
|
||||
),
|
||||
_buildInfoSetting(
|
||||
'Tentatives échouées',
|
||||
_metrics != null ? '${_metrics!.failedLoginAttempts24h ?? 0} dans les dernières 24h' : 'Chargement...',
|
||||
),
|
||||
_buildActionSetting(
|
||||
'Forcer la déconnexion globale',
|
||||
'Déconnecter tous les utilisateurs',
|
||||
@@ -562,10 +662,30 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'État actuel du système',
|
||||
Icons.speed,
|
||||
[
|
||||
_buildMetricItem('CPU', '23%', Icons.memory, Colors.blue),
|
||||
_buildMetricItem('RAM', '67%', Icons.storage, Colors.green),
|
||||
_buildMetricItem('Disque', '45%', Icons.storage, Colors.orange),
|
||||
_buildMetricItem('Réseau', '12 MB/s', Icons.network_check, Colors.purple),
|
||||
_buildMetricItem(
|
||||
'CPU',
|
||||
_metrics != null ? '${_metrics!.cpuUsagePercent?.toStringAsFixed(1) ?? '0'}%' : '-',
|
||||
Icons.memory,
|
||||
Colors.blue,
|
||||
),
|
||||
_buildMetricItem(
|
||||
'RAM',
|
||||
_metrics != null ? '${_metrics!.memoryUsagePercent?.toStringAsFixed(1) ?? '0'}%' : '-',
|
||||
Icons.storage,
|
||||
Colors.green,
|
||||
),
|
||||
_buildMetricItem(
|
||||
'Disque',
|
||||
_metrics != null ? '${_metrics!.diskUsagePercent?.toStringAsFixed(1) ?? '0'}%' : '-',
|
||||
Icons.storage,
|
||||
Colors.orange,
|
||||
),
|
||||
_buildMetricItem(
|
||||
'Réseau',
|
||||
_metrics?.networkInFormatted ?? '0 B/s',
|
||||
Icons.network_check,
|
||||
Colors.purple,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -662,8 +782,14 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'Opérations de maintenance',
|
||||
Icons.build,
|
||||
[
|
||||
_buildInfoSetting('Dernière maintenance', '15/12/2024 à 02:30'),
|
||||
_buildInfoSetting('Prochaine maintenance', '22/12/2024 à 02:00'),
|
||||
_buildInfoSetting(
|
||||
'Dernière maintenance',
|
||||
_metrics?.lastMaintenance ?? 'Aucune maintenance récente',
|
||||
),
|
||||
_buildInfoSetting(
|
||||
'Prochaine maintenance',
|
||||
_metrics?.nextScheduledMaintenance ?? 'Non planifiée',
|
||||
),
|
||||
_buildActionSetting(
|
||||
'Planifier une maintenance',
|
||||
'Programmer une fenêtre de maintenance',
|
||||
@@ -689,8 +815,14 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'Gestion des versions',
|
||||
Icons.system_update,
|
||||
[
|
||||
_buildInfoSetting('Version actuelle', 'UnionFlow Server 2.1.0'),
|
||||
_buildInfoSetting('Dernière vérification', 'Il y a 2 heures'),
|
||||
_buildInfoSetting(
|
||||
'Version actuelle',
|
||||
_metrics != null ? 'UnionFlow Server ${_metrics!.applicationVersion ?? "N/A"}' : 'Chargement...',
|
||||
),
|
||||
_buildInfoSetting(
|
||||
'Uptime',
|
||||
_metrics?.uptimeFormatted ?? 'Chargement...',
|
||||
),
|
||||
_buildActionSetting(
|
||||
'Vérifier les mises à jour',
|
||||
'Rechercher les nouvelles versions',
|
||||
@@ -763,10 +895,26 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'Journaux d\'activité',
|
||||
Icons.article,
|
||||
[
|
||||
_buildLogItem('Erreurs critiques', '3', Colors.red),
|
||||
_buildLogItem('Avertissements', '27', Colors.orange),
|
||||
_buildLogItem('Informations', '1,247', Colors.blue),
|
||||
_buildLogItem('Debug', '5,892', Colors.grey),
|
||||
_buildLogItem(
|
||||
'Erreurs critiques',
|
||||
_metrics?.criticalErrorsCount?.toString() ?? '0',
|
||||
Colors.red,
|
||||
),
|
||||
_buildLogItem(
|
||||
'Avertissements',
|
||||
_metrics?.warningsCount?.toString() ?? '0',
|
||||
Colors.orange,
|
||||
),
|
||||
_buildLogItem(
|
||||
'Informations',
|
||||
_metrics?.infoLogsCount?.toString() ?? '0',
|
||||
Colors.blue,
|
||||
),
|
||||
_buildLogItem(
|
||||
'Debug',
|
||||
_metrics?.debugLogsCount?.toString() ?? '0',
|
||||
Colors.grey,
|
||||
),
|
||||
_buildActionSetting(
|
||||
'Voir tous les logs',
|
||||
'Ouvrir la console de logs complète',
|
||||
@@ -792,10 +940,22 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
'Métriques d\'activité',
|
||||
Icons.bar_chart,
|
||||
[
|
||||
_buildStatItem('Utilisateurs actifs (24h)', '1,247'),
|
||||
_buildStatItem('Requêtes API (1h)', '45,892'),
|
||||
_buildStatItem('Données transférées', '2.3 GB'),
|
||||
_buildStatItem('Temps de réponse moyen', '127ms'),
|
||||
_buildStatItem(
|
||||
'Utilisateurs actifs (24h)',
|
||||
_metrics?.activeUsersCount?.toString() ?? '0',
|
||||
),
|
||||
_buildStatItem(
|
||||
'Requêtes API (1h)',
|
||||
_metrics?.apiRequestsLastHour?.toString() ?? '0',
|
||||
),
|
||||
_buildStatItem(
|
||||
'Mémoire utilisée',
|
||||
_metrics?.usedMemoryFormatted ?? '0 B',
|
||||
),
|
||||
_buildStatItem(
|
||||
'Temps de réponse moyen',
|
||||
_metrics != null ? '${_metrics!.averageResponseTimeMs?.toStringAsFixed(0) ?? "0"}ms' : '0ms',
|
||||
),
|
||||
_buildActionSetting(
|
||||
'Rapport détaillé',
|
||||
'Générer un rapport complet d\'utilisation',
|
||||
@@ -1396,9 +1556,15 @@ class _SystemSettingsPageState extends State<SystemSettingsPage>
|
||||
}
|
||||
|
||||
// Actions générales
|
||||
void _clearSystemCache() => _showSuccessSnackBar('Cache système vidé (2.3 GB libérés)');
|
||||
void _clearSystemCache() {
|
||||
context.read<SystemSettingsBloc>().add(ClearCache());
|
||||
}
|
||||
|
||||
void _optimizeDatabase() => _showSuccessSnackBar('Base de données optimisée');
|
||||
void _testConnectivity() => _showSuccessSnackBar('Connectivité OK - Tous les services répondent');
|
||||
|
||||
void _testConnectivity() {
|
||||
context.read<SystemSettingsBloc>().add(TestDatabaseConnection());
|
||||
}
|
||||
|
||||
// Actions de sécurité
|
||||
void _regenerateApiKeys() => _showWarningDialog('Régénérer les clés API', 'Cette action invalidera toutes les clés existantes.');
|
||||
|
||||
Reference in New Issue
Block a user