import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../controller/server/server_monitor_controller.dart'; class ServerMonitorPage extends StatelessWidget { const ServerMonitorPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final controller = Get.put(ServerMonitorController()); final themeColor = const Color(0xFF6366F1); return Scaffold( backgroundColor: const Color(0xFF0A0E27), body: RefreshIndicator( onRefresh: controller.fetchServerData, color: themeColor, backgroundColor: const Color(0xFF1A1F3A), child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ // === 1. App Bar المتجاوب === SliverAppBar( expandedHeight: 100, floating: true, pinned: true, backgroundColor: const Color(0xFF0A0E27), elevation: 0, flexibleSpace: FlexibleSpaceBar( titlePadding: const EdgeInsets.only(bottom: 16), title: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.dns_rounded, color: Colors.white, size: 20), const SizedBox(width: 8), const Text( 'Server Monitor', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white, fontFamily: 'Segoe UI', // أو أي خط تفضله ), ), ], ), centerTitle: true, background: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ themeColor.withOpacity(0.3), const Color(0xFF0A0E27), ], ), ), ), ), actions: [ _buildRefreshButton(controller), ], ), // === 2. المحتوى الرئيسي === SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), sliver: Obx(() { if (controller.isLoading.value && controller.serverData.value == null) { return const SliverFillRemaining(child: _LoadingState()); } if (controller.errorMessage.isNotEmpty) { return SliverFillRemaining( child: _ErrorState(controller: controller)); } final data = controller.serverData.value!; return SliverToBoxAdapter( child: Center( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 1000), // لمنع التمدد الزائد في الشاشات الكبيرة child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // معلومات الوقت والتشغيل _HeaderInfo(data: data), const SizedBox(height: 20), // بطاقات الأداء (CPU & RAM) LayoutBuilder(builder: (context, constraints) { return _buildCpuMemSection( data, constraints.maxWidth > 600); }), const SizedBox(height: 20), // القسم المتغير (خدمات + عمليات + تخزين) LayoutBuilder(builder: (context, constraints) { // إذا كانت الشاشة كبيرة (تابلت/ديسكتوب) if (constraints.maxWidth > 800) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // العمود الأول: الخدمات والشبكة Expanded( flex: 4, child: Column( children: [ _ServicesCard(data: data), const SizedBox(height: 20), _StorageNetworkCard(data: data), ], ), ), const SizedBox(width: 20), // العمود الثاني: العمليات Expanded( flex: 6, child: _TopProcessesCard( data: data, height: 600), // ارتفاع ثابت في وضع الكمبيوتر ), ], ); } // إذا كانت الشاشة موبايل else { return Column( children: [ _ServicesCard(data: data), const SizedBox(height: 16), _StorageNetworkCard(data: data), const SizedBox(height: 16), _TopProcessesCard( data: data), // ارتفاع ديناميكي ], ); } }), const SizedBox(height: 40), ], ), ), ), ); }), ), ], ), ), ); } Widget _buildRefreshButton(ServerMonitorController controller) { return Obx(() => Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: IconButton( icon: controller.isLoading.value ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.refresh_rounded, color: Colors.white), onPressed: controller.fetchServerData, ), )); } // دمج بطاقات المعالج والذاكرة Widget _buildCpuMemSection(dynamic data, bool isWide) { List cards = [ _MetricCard( title: "المعالج (CPU)", value: "${data.cpu.percent}%", subtitle: "${data.cpu.cores} Cores", icon: Icons.memory, percent: data.cpu.percent.toDouble(), color: const Color(0xFFFF6B6B), ), SizedBox(width: isWide ? 20 : 0, height: isWide ? 0 : 16), _MetricCard( title: "الذاكرة (RAM)", value: "${data.memory.percent}%", subtitle: "${data.memory.usedGb}/${data.memory.totalGb} GB", icon: Icons.sd_storage_rounded, percent: data.memory.percent.toDouble(), color: const Color(0xFF4E54C8), ), ]; return isWide ? Row( children: cards .map((e) => e is SizedBox ? e : Expanded(child: e)) .toList()) : Column(children: cards); } } // === مكونات فرعية معاد استخدامها (Widgets) === class _HeaderInfo extends StatelessWidget { final dynamic data; const _HeaderInfo({required this.data}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.05), borderRadius: BorderRadius.circular(50), border: Border.all(color: Colors.white.withOpacity(0.1)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.timer_outlined, size: 16, color: Colors.greenAccent.withOpacity(0.8)), const SizedBox(width: 8), Text( "Uptime: ${data.uptime.formatted}", style: const TextStyle( color: Colors.white70, fontSize: 12, fontWeight: FontWeight.w500), ), Container( width: 1, height: 12, color: Colors.white24, margin: const EdgeInsets.symmetric(horizontal: 12)), Icon(Icons.update, size: 16, color: Colors.blueAccent.withOpacity(0.8)), const SizedBox(width: 8), Text( "Last Update: ${data.timestamp.split(' ')[1]}", style: const TextStyle(color: Colors.white70, fontSize: 12), ), ], ), ); } } class _MetricCard extends StatelessWidget { final String title; final String value; final String subtitle; final IconData icon; final double percent; final Color color; const _MetricCard({ required this.title, required this.value, required this.subtitle, required this.icon, required this.percent, required this.color, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)], ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: color.withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 8), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: Colors.white, size: 24), ), Text( value, style: const TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 16), Text(title, style: const TextStyle(color: Colors.white70, fontSize: 14)), const SizedBox(height: 4), Text(subtitle, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 12), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: percent / 100, minHeight: 6, backgroundColor: Colors.black12, valueColor: const AlwaysStoppedAnimation(Colors.white), ), ), ], ), ); } } class _ServicesCard extends StatelessWidget { final dynamic data; const _ServicesCard({required this.data}); @override Widget build(BuildContext context) { return _BaseCard( title: "حالة الخدمات", icon: Icons.security, iconColor: Colors.tealAccent, child: Column( children: data.services.entries.map((e) { final isActive = e.value == 'active'; return Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF0F1629), borderRadius: BorderRadius.circular(12), border: Border.all( color: isActive ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3), ), ), child: Row( children: [ CircleAvatar( radius: 4, backgroundColor: isActive ? Colors.greenAccent : Colors.redAccent, ), const SizedBox(width: 12), Expanded( child: Text( e.key.toUpperCase(), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isActive ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Text( isActive ? "Running" : "Stopped", style: TextStyle( color: isActive ? Colors.greenAccent : Colors.redAccent, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ], ), ); }).toList(), ), ); } } class _StorageNetworkCard extends StatelessWidget { final dynamic data; const _StorageNetworkCard({required this.data}); @override Widget build(BuildContext context) { return _BaseCard( title: "التخزين والشبكة", icon: Icons.cloud_queue_rounded, iconColor: Colors.purpleAccent, child: Column( children: [ _buildRowItem(Icons.pie_chart_outline, "Storage", "${data.disk.percent}%", "${data.disk.usedGb} GB Used"), const Divider(color: Colors.white10, height: 24), _buildRowItem(Icons.download_rounded, "Download", "${data.network.receivedMb} MB", "In"), const SizedBox(height: 16), _buildRowItem(Icons.upload_rounded, "Upload", "${data.network.sentMb} MB", "Out"), ], ), ); } Widget _buildRowItem(IconData icon, String label, String value, String sub) { return Row( children: [ Icon(icon, color: Colors.white54, size: 20), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(color: Colors.white60, fontSize: 12)), Text(sub, style: TextStyle( color: Colors.white.withOpacity(0.4), fontSize: 10)), ], ), const Spacer(), Text(value, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)), ], ); } } class _TopProcessesCard extends StatelessWidget { final dynamic data; final double? height; const _TopProcessesCard({required this.data, this.height}); @override Widget build(BuildContext context) { return Container( height: height, // إذا كان null سيأخذ الارتفاع بناءً على المحتوى decoration: BoxDecoration( color: const Color(0xFF1A1F3A), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.white.withOpacity(0.05)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(20), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.orange.withOpacity(0.1), borderRadius: BorderRadius.circular(8)), child: const Icon(Icons.analytics_rounded, color: Colors.orange, size: 18), ), const SizedBox(width: 12), const Text("Top Processes", style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), ], ), ), const Divider(height: 1, color: Colors.white10), // نستخدم ListView.builder داخل Expanded إذا كان هناك ارتفاع محدد، وإلا Column للموبايل height != null ? Expanded(child: _buildList()) : _buildList(shrinkWrap: true), ], ), ); } Widget _buildList({bool shrinkWrap = false}) { return ListView.separated( padding: const EdgeInsets.all(16), physics: shrinkWrap ? const NeverScrollableScrollPhysics() : const BouncingScrollPhysics(), shrinkWrap: shrinkWrap, itemCount: data.topProcesses.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { final process = data.topProcesses[index]; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.03), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Text("#${index + 1}", style: const TextStyle( color: Colors.white38, fontWeight: FontWeight.bold)), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(process.name, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: Colors.orange.withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Text( process.usage, style: const TextStyle( color: Colors.orangeAccent, fontSize: 12, fontWeight: FontWeight.bold), ), ), ], ), ); }, ); } } class _BaseCard extends StatelessWidget { final String title; final IconData icon; final Color iconColor; final Widget child; const _BaseCard({ required this.title, required this.icon, required this.iconColor, required this.child, }); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: const Color(0xFF1A1F3A), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.white.withOpacity(0.05)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: iconColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8)), child: Icon(icon, color: iconColor, size: 18), ), const SizedBox(width: 12), Text(title, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), ], ), const SizedBox(height: 20), child, ], ), ); } } class _LoadingState extends StatelessWidget { const _LoadingState(); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(color: Color(0xFF6366F1)), const SizedBox(height: 16), Text("Connecting to server...", style: TextStyle(color: Colors.white.withOpacity(0.5))), ], ), ); } } class _ErrorState extends StatelessWidget { final ServerMonitorController controller; const _ErrorState({required this.controller}); @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.cloud_off_rounded, size: 60, color: Colors.redAccent), const SizedBox(height: 16), Text(controller.errorMessage.value, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white)), const SizedBox(height: 24), ElevatedButton( onPressed: controller.fetchServerData, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6366F1), shape: const StadiumBorder(), ), child: const Text("Try Again"), ) ], ), ), ); } }