import 'package:flutter/material.dart'; import '../control_plane_status_client.dart'; class NodesPanel extends StatelessWidget { final List edges; final String? selectedEdgeId; final EdgeStatusResponseView? selectedEdgeStatus; final bool isLoading; final String? error; final ValueChanged onSelectEdge; final VoidCallback onRefresh; const NodesPanel({ super.key, required this.edges, required this.selectedEdgeId, required this.selectedEdgeStatus, 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( 'Nodes View', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 16), if (edges.isNotEmpty && dropdownValue != null) ...[ const Text('Active 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: _buildNodeContent()), ], ), ); } Widget _buildNodeContent() { 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 Nodes Status', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), const SizedBox(height: 8), Text(error!, style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 13)), ], ), ); } final nodes = selectedEdgeStatus?.nodes ?? []; if (nodes.isEmpty) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.developer_board_off_outlined, color: Color(0xFF64748B), size: 56), SizedBox(height: 16), Text( 'No Nodes Registered', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), ), SizedBox(height: 4), Text( 'No active nodes have registered on this edge.', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 13), ), ], ), ); } return ListView.builder( itemCount: nodes.length, itemBuilder: (context, index) { final node = nodes[index]; return Card( color: const Color(0xFF1E293B), margin: const EdgeInsets.only(bottom: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: node.connected ? const Color(0xFF10B981).withValues(alpha: 0.3) : const Color(0xFF334155), ), ), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( node.alias.isNotEmpty ? node.alias : node.nodeId, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( 'ID: ${node.nodeId}', style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12), overflow: TextOverflow.ellipsis, ), ], ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: node.connected ? const Color(0xFF10B981).withValues(alpha: 0.1) : const Color(0xFFEF4444).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Text( node.connected ? 'CONNECTED' : 'OFFLINE', style: TextStyle( color: node.connected ? const Color(0xFF10B981) : const Color(0xFFEF4444), fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ], ), if (node.label.isNotEmpty) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF334155), borderRadius: BorderRadius.circular(4), ), child: Text( 'Label: ${node.label}', style: const TextStyle(color: Colors.white, fontSize: 12), ), ), ], if (node.config != null) ...[ const SizedBox(height: 16), const Divider(color: Color(0xFF334155)), const SizedBox(height: 12), Row( children: [ const Icon(Icons.bolt, size: 16, color: Color(0xFF6366F1)), const SizedBox(width: 6), Text( 'Concurrency Limit: ${node.config!.concurrency}', style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 13), ), ], ), const SizedBox(height: 12), const Text( 'Adapters', style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), if (node.config!.adapters.isEmpty) const Text('No active adapters configured', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 12)) else Wrap( spacing: 8, runSpacing: 8, children: node.config!.adapters.map((adapter) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: adapter.enabled ? const Color(0xFF10B981).withValues(alpha: 0.1) : const Color(0xFFEF4444).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: adapter.enabled ? const Color(0xFF10B981) : const Color(0xFFEF4444), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( adapter.type, style: TextStyle( color: adapter.enabled ? const Color(0xFF10B981) : const Color(0xFFEF4444), fontSize: 12, fontWeight: FontWeight.w500, ), ), const SizedBox(width: 4), Icon( adapter.enabled ? Icons.check : Icons.close, size: 12, color: adapter.enabled ? const Color(0xFF10B981) : const Color(0xFFEF4444), ), ], ), ); }).toList(), ), ], ], ), ), ); }, ); } }