import 'package:flutter/material.dart'; import '../control_plane_status_client.dart'; class ExecutionLogsPanel extends StatelessWidget { final List edges; final String? selectedEdgeId; final List events; final bool isLoading; final String? error; final ValueChanged onSelectEdge; final VoidCallback onRefresh; const ExecutionLogsPanel({ super.key, required this.edges, required this.selectedEdgeId, required this.events, required this.isLoading, required this.error, required this.onSelectEdge, required this.onRefresh, }); @override Widget build(BuildContext context) { if (edges.isEmpty) { return const Center( child: Text( 'Please connect and register at least one Edge first.', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 14), ), ); } final currentEdgeId = selectedEdgeId ?? edges.first.edgeId; final hasCurrentEdge = edges.any((e) => e.edgeId == currentEdgeId); final dropdownValue = hasCurrentEdge ? currentEdgeId : (edges.isNotEmpty ? edges.first.edgeId : null); return Container( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Expanded( child: Text( 'Lifecycle Events & Logs', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 16), if (edges.isNotEmpty && dropdownValue != null) ...[ const Text('Filter Edge: ', style: TextStyle(color: Color(0xFF94A3B8))), const SizedBox(width: 8), DropdownButton( value: dropdownValue, dropdownColor: const Color(0xFF1E293B), style: const TextStyle(color: Colors.white), underline: const SizedBox(), items: edges.map((e) { return DropdownMenuItem( value: e.edgeId, child: Text(e.edgeName.isNotEmpty ? e.edgeName : e.edgeId), ); }).toList(), onChanged: (val) { if (val != null) onSelectEdge(val); }, ), ], const SizedBox(width: 8), IconButton( icon: const Icon(Icons.refresh, color: Color(0xFF6366F1)), onPressed: onRefresh, ), ], ), const SizedBox(height: 20), Expanded(child: _buildLogsContent()), ], ), ); } Widget _buildLogsContent() { if (isLoading) { return const Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)), ), ); } if (error != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, color: Color(0xFFEF4444), size: 48), SizedBox(height: 16), const Text( 'Failed to Load Events', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), const SizedBox(height: 8), Text(error!, style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 13)), ], ), ); } if (events.isEmpty) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.history_toggle_off, color: Color(0xFF64748B), size: 56), SizedBox(height: 16), Text( 'No Events Captured', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), SizedBox(height: 4), Text( 'No lifecycle events have been recorded for this edge yet.', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 13), ), ], ), ); } return ListView.builder( itemCount: events.length, itemBuilder: (context, index) { final event = events[index]; final eventColor = _getEventColor(event.type); final eventIcon = _getEventIcon(event.type); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF1E293B), borderRadius: BorderRadius.circular(8), border: Border( left: BorderSide(color: eventColor, width: 4), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(eventIcon, size: 16, color: eventColor), const SizedBox(width: 8), Text( event.type.toUpperCase(), style: TextStyle(color: eventColor, fontSize: 12, fontWeight: FontWeight.bold), ), const Spacer(), Text( event.timestamp.toLocal().toString().substring(11, 19), style: const TextStyle(color: Color(0xFF64748B), fontSize: 12), ), ], ), const SizedBox(height: 8), Text( event.reason, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), ), const SizedBox(height: 8), Row( children: [ const Text('Node: ', style: TextStyle(color: Color(0xFF64748B), fontSize: 12)), Text( event.alias.isNotEmpty ? event.alias : (event.nodeId.isNotEmpty ? event.nodeId : 'Edge System'), style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12), ), if (event.source.isNotEmpty) ...[ const SizedBox(width: 12), const Text('Source: ', style: TextStyle(color: Color(0xFF64748B), fontSize: 12)), Text( event.source, style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12), ), ], ], ), ], ), ); }, ); } Color _getEventColor(String type) { switch (type.toLowerCase()) { case 'error': case 'failed': return const Color(0xFFEF4444); case 'warning': case 'warn': return const Color(0xFFF59E0B); case 'success': case 'connected': case 'online': return const Color(0xFF10B981); case 'info': default: return const Color(0xFF6366F1); } } IconData _getEventIcon(String type) { switch (type.toLowerCase()) { case 'error': case 'failed': return Icons.error_outline; case 'warning': case 'warn': return Icons.warning_amber_outlined; case 'success': case 'connected': case 'online': return Icons.check_circle_outline; case 'info': default: return Icons.info_outline; } } }