2026-03-10-1

This commit is contained in:
Hamza-Ayed
2026-03-10 00:02:17 +03:00
parent c000d22ca3
commit cdda136006
28 changed files with 9912 additions and 3592 deletions

View File

@@ -0,0 +1,669 @@
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<Widget> 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<Widget>((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"),
)
],
),
),
);
}
}