From 53d8268220c41276c35236ae92f679942989ca8b Mon Sep 17 00:00:00 2001 From: LollipopKit <2036293523@qq.com> Date: Thu, 28 Oct 2021 19:04:52 +0800 Subject: [PATCH] Finish server detail page --- README.md | 2 +- lib/data/model/cpu_2_status.dart | 28 ++++ lib/data/model/disk_info.dart | 31 ++-- lib/data/model/linux_icon.dart | 14 ++ lib/data/model/server_status.dart | 4 +- lib/data/model/tcp_status.dart | 22 +-- lib/data/provider/server.dart | 31 ++-- lib/data/res/build_data.dart | 9 +- lib/data/res/linux_icons.dart | 4 + lib/view/page/server/detail.dart | 227 ++++++++++++++++++++++++++++-- lib/view/page/server/tab.dart | 13 +- 11 files changed, 312 insertions(+), 73 deletions(-) create mode 100644 lib/data/model/linux_icon.dart create mode 100644 lib/data/res/linux_icons.dart diff --git a/README.md b/README.md index f1e90b62..6899397f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ A new Flutter project which provide a chart view to display server status data. - [x] Status Chart View - [x] Base64/Url En/Decode - [x] Private Key Store -- [ ] Server Status Detail Page +- [x] Server Status Detail Page - [x] Theme Switch - [ ] Custom Home Page diff --git a/lib/data/model/cpu_2_status.dart b/lib/data/model/cpu_2_status.dart index 66b3de37..c908a51d 100644 --- a/lib/data/model/cpu_2_status.dart +++ b/lib/data/model/cpu_2_status.dart @@ -6,6 +6,7 @@ class Cpu2Status { Cpu2Status(this.pre, this.now); double usedPercent({int coreIdx = 0}) { + if (now.length != pre.length) return 0; final idleDelta = now[coreIdx].idle - pre[coreIdx].idle; final totalDelta = now[coreIdx].total - pre[coreIdx].total; final used = idleDelta / totalDelta; @@ -15,4 +16,31 @@ class Cpu2Status { Cpu2Status update(List newStatus) { return Cpu2Status(now, newStatus); } + + int get coresCount => now.length; + + int get totalDelta => now[0].total - pre[0].total; + + double get user { + if (now.length != pre.length) return 0; + final delta = now[0].user - pre[0].user; + final used = delta / totalDelta; + return used.isNaN ? 0 : used * 100; + } + + double get sys { + if (now.length != pre.length) return 0; + final delta = now[0].sys - pre[0].sys; + final used = delta / totalDelta; + return used.isNaN ? 0 : used * 100; + } + + double get nice { + if (now.length != pre.length) return 0; + final delta = now[0].nice - pre[0].nice; + final used = delta / totalDelta; + return used.isNaN ? 0 : used * 100; + } + + double get idle => 100 - usedPercent(); } diff --git a/lib/data/model/disk_info.dart b/lib/data/model/disk_info.dart index b8cc2c63..762c470a 100644 --- a/lib/data/model/disk_info.dart +++ b/lib/data/model/disk_info.dart @@ -4,34 +4,35 @@ class DiskInfo { "mountPath": "", "mountLocation": "", "usedPercent": 0, - "used": "",= + "used": "", "size": "", "avail": "" } */ - String? mountPath; - String? mountLocation; - double? usedPercent; - String? used; - String? size; - String? avail; + late String mountPath; + late String mountLocation; + late int usedPercent; + late String used; + late String size; + late String avail; - DiskInfo({ + DiskInfo( this.mountPath, this.mountLocation, this.usedPercent, this.used, this.size, this.avail, - }); + ); + DiskInfo.fromJson(Map json) { - mountPath = json["mountPath"]?.toString(); - mountLocation = json["mountLocation"]?.toString(); - usedPercent = double.parse(json["usedPercent"]); - used = json["used"]?.toString(); - size = json["size"]?.toString(); - avail = json["avail"]?.toString(); + mountPath = json["mountPath"].toString(); + mountLocation = json["mountLocation"].toString(); + usedPercent = int.parse(json["usedPercent"]); + used = json["used"].toString(); + size = json["size"].toString(); + avail = json["avail"].toString(); } Map toJson() { final Map data = {}; diff --git a/lib/data/model/linux_icon.dart b/lib/data/model/linux_icon.dart new file mode 100644 index 00000000..675544c6 --- /dev/null +++ b/lib/data/model/linux_icon.dart @@ -0,0 +1,14 @@ +class LinuxIcons { + List db; + + LinuxIcons(this.db); + + String? search(String sysVer) { + for (var item in db) { + if (sysVer.contains(item)) { + return 'assets/linux/$item.png'; + } + } + return null; + } +} diff --git a/lib/data/model/server_status.dart b/lib/data/model/server_status.dart index c202e5e9..c9a4d106 100644 --- a/lib/data/model/server_status.dart +++ b/lib/data/model/server_status.dart @@ -29,10 +29,10 @@ class ServerStatus { */ late Cpu2Status cpu2Status; - late List memList; + late List memList; late String sysVer; late String uptime; - late List disk; + late List disk; late TcpStatus tcp; ServerStatus(this.cpu2Status, this.memList, this.sysVer, this.uptime, diff --git a/lib/data/model/tcp_status.dart b/lib/data/model/tcp_status.dart index 17a2321e..835aba8f 100644 --- a/lib/data/model/tcp_status.dart +++ b/lib/data/model/tcp_status.dart @@ -11,23 +11,25 @@ class TcpStatus { } */ - int? maxConn; - int? active; - int? passive; - int? fail; + late int maxConn; + late int active; + late int passive; + late int fail; - TcpStatus({ + TcpStatus( this.maxConn, this.active, this.passive, this.fail, - }); + ); + TcpStatus.fromJson(Map json) { - maxConn = json["maxConn"]?.toInt(); - active = json["active"]?.toInt(); - passive = json["passive"]?.toInt(); - fail = json["fail"]?.toInt(); + maxConn = json["maxConn"].toInt(); + active = json["active"].toInt(); + passive = json["passive"].toInt(); + fail = json["fail"].toInt(); } + Map toJson() { final Map data = {}; data["maxConn"] = maxConn; diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index dabd9fce..ea032c11 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -32,16 +32,8 @@ class ServerProvider extends BusyProvider { [100, 0], '', '', - [ - DiskInfo( - mountLocation: '/', - mountPath: '/', - used: '0', - size: '0', - avail: '0', - usedPercent: 0) - ], - TcpStatus(maxConn: 0, active: 0, passive: 0, fail: 0)); + [DiskInfo('/', '/', 0, '0', '0', '0')], + TcpStatus(0, 0, 0, 0)); Future loadLocalData() async { setBusyState(true); @@ -199,13 +191,13 @@ class ServerProvider extends BusyProvider { if (idx == 2) { final vals = item.split(RegExp(r'\s{1,}')); return TcpStatus( - maxConn: vals[5].i, - active: vals[6].i, - passive: vals[7].i, - fail: vals[8].i); + vals[5].i, + vals[6].i, + vals[7].i, + vals[8].i); } } - return TcpStatus(maxConn: 0, active: 0, passive: 0, fail: 0); + return TcpStatus(0, 0, 0, 0); } List _getDisk(String disk) { @@ -216,13 +208,8 @@ class ServerProvider extends BusyProvider { continue; } final vals = item.split(RegExp(r'\s{1,}')); - list.add(DiskInfo( - mountPath: vals[1], - mountLocation: vals[5], - usedPercent: double.parse(vals[4].replaceFirst('%', '')), - used: vals[2], - size: vals[1], - avail: vals[3])); + list.add(DiskInfo(vals[0], vals[5], + int.parse(vals[4].replaceFirst('%', '')), vals[2], vals[1], vals[3])); } return list; } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 4f24ad4e..fc514dd5 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,8 @@ class BuildData { static const String name = "ToolBox"; - static const int build = 30; - static const String engine = - "Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 18116933e7 (11 days ago) • 2021-10-15 10:46:35 -0700\nEngine • revision d3ea636dc5\nTools • Dart 2.14.4\n"; - static const String buildAt = "2021-10-26 17:55:37.268093"; - static const int modifications = 0; + static const int build = 40; + static const String engine = "Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 18116933e7 (13 days ago) • 2021-10-15 10:46:35 -0700\nEngine • revision d3ea636dc5\nTools • Dart 2.14.4\n"; + static const String buildAt = "2021-10-28 19:02:21.118303"; + static const int modifications = 17; } diff --git a/lib/data/res/linux_icons.dart b/lib/data/res/linux_icons.dart new file mode 100644 index 00000000..76b6b99d --- /dev/null +++ b/lib/data/res/linux_icons.dart @@ -0,0 +1,4 @@ +import 'package:toolbox/data/model/linux_icon.dart'; + +final linuxIcons = LinuxIcons(['ubuntu', 'arch', 'centos', 'debian', 'fedora', +'opensuse', 'kali']); \ No newline at end of file diff --git a/lib/view/page/server/detail.dart b/lib/view/page/server/detail.dart index 7ab92d6d..7259e202 100644 --- a/lib/view/page/server/detail.dart +++ b/lib/view/page/server/detail.dart @@ -3,6 +3,9 @@ import 'package:provider/provider.dart'; import 'package:toolbox/data/model/server.dart'; import 'package:toolbox/data/model/server_status.dart'; import 'package:toolbox/data/provider/server.dart'; +import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/linux_icons.dart'; +import 'package:toolbox/view/widget/round_rect_card.dart'; class ServerDetailPage extends StatefulWidget { const ServerDetailPage(this.id, {Key? key}) : super(key: key); @@ -13,7 +16,8 @@ class ServerDetailPage extends StatefulWidget { _ServerDetailPageState createState() => _ServerDetailPageState(); } -class _ServerDetailPageState extends State { +class _ServerDetailPageState extends State + with SingleTickerProviderStateMixin { late MediaQueryData _media; @override @@ -36,24 +40,225 @@ class _ServerDetailPageState extends State { title: Text(si.info.name ?? 'Server Detail'), ), body: ListView( - children: [_buildCPUView(si.status), _buildMemView(si.status)], + padding: const EdgeInsets.all(17), + children: [ + _buildLinuxIcon(si.status.sysVer), + SizedBox(height: _media.size.height * 0.03), + _buildUpTimeAndSys(si.status), + _buildCPUView(si.status), + _buildDiskView(si.status), + _buildMemView(si.status) + ], ), ); } + Widget _buildLinuxIcon(String sysVer) { + final iconPath = linuxIcons.search(sysVer.toLowerCase()); + if (iconPath == null) return const SizedBox(); + return SizedBox(height: _media.size.height * 0.15, child: Image.asset(iconPath)); + } + Widget _buildCPUView(ServerStatus ss) { - return ConstrainedBox( - constraints: BoxConstraints(maxHeight: _media.size.height * 0.3), - child: ListView.builder( - itemBuilder: (ctx, idx) { - return Text('$idx ${ss.cpu2Status.usedPercent(coreIdx: idx)}'); - }, - itemCount: ss.cpu2Status.now.length, + return RoundRectCard( + SizedBox( + height: 12 * ss.cpu2Status.coresCount + 67, + child: Column(children: [ + SizedBox( + height: _media.size.height * 0.02, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${ss.cpu2Status.usedPercent(coreIdx: 0).toInt()}%', + style: const TextStyle(fontSize: 27), + textScaleFactor: 1.0, + ), + Row( + children: [ + _buildCPUTimePercent(ss.cpu2Status.user, 'user'), + SizedBox( + width: _media.size.width * 0.03, + ), + _buildCPUTimePercent(ss.cpu2Status.sys, 'sys'), + SizedBox( + width: _media.size.width * 0.03, + ), + _buildCPUTimePercent(ss.cpu2Status.nice, 'nice'), + SizedBox( + width: _media.size.width * 0.03, + ), + _buildCPUTimePercent(ss.cpu2Status.idle, 'idle') + ], + ) + ], + ), + _buildCPUProgress(ss) + ]), ), ); } - Widget _buildMemView(ServerStatus ss) { - return Text(ss.memList.length.toString()); + Widget _buildCPUTimePercent(double percent, String timeType) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + percent.toStringAsFixed(1) + '%', + style: const TextStyle(fontSize: 13), + textScaleFactor: 1.0, + ), + Text( + timeType, + style: const TextStyle(fontSize: 10, color: Colors.grey), + textScaleFactor: 1.0, + ), + ], + ); } + + Widget _buildCPUProgress(ServerStatus ss) { + return SizedBox( + height: 12.0 * ss.cpu2Status.coresCount, + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(vertical: 17), + itemBuilder: (ctx, idx) { + if (idx == 0) return const SizedBox(); + return Padding( + padding: const EdgeInsets.all(2), + child: _buildProgress(ss.cpu2Status.usedPercent(coreIdx: idx)), + ); + }, + itemCount: ss.cpu2Status.coresCount, + ), + ); + } + + Widget _buildProgress(double percent) { + final pColor = primaryColor; + final percentWithinOne = percent / 100; + return LinearProgressIndicator( + value: percentWithinOne, + minHeight: 7, + backgroundColor: Colors.grey[100], + color: pColor.withOpacity(0.5 + percentWithinOne / 2), + ); + } + + Widget _buildUpTimeAndSys(ServerStatus ss) { + return RoundRectCard(Padding( + padding: const EdgeInsets.symmetric(vertical: 13), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(ss.sysVer), + Text(ss.uptime), + ], + ), + )); + } + + Widget _buildMemView(ServerStatus ss) { + final pColor = primaryColor; + final used = ss.memList[1] / ss.memList[0]; + final width = _media.size.width - 17 * 2 - 17 * 2; + return RoundRectCard(SizedBox( + height: 47, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildMemExplain('Used', pColor), + _buildMemExplain('Cache', pColor.withAlpha(77)), + _buildMemExplain('Avail', Colors.grey.shade100) + ], + ), + const SizedBox( + height: 7, + ), + Row( + children: [ + SizedBox( + width: width * used, + child: LinearProgressIndicator( + value: 1, + color: pColor, + )), + SizedBox( + width: width * (1 - used), + child: LinearProgressIndicator( + value: ss.memList[4] / ss.memList[0], + backgroundColor: Colors.grey[100], + color: pColor.withAlpha(77), + ), + ) + ], + ) + ], + ), + )); + } + + Widget _buildMemExplain(String type, Color color) { + return Row( + children: [ + Container( + color: color, + height: 11, + width: 11, + ), + const SizedBox(width: 4), + Text(type, style: const TextStyle(fontSize: 10), textScaleFactor: 1.0) + ], + ); + } + + Widget _buildDiskView(ServerStatus ss) { + final clone = ss.disk.toList(); + for (var item in ss.disk) { + if (ignorePath.any((ele) => item.mountLocation.contains(ele))) { + clone.remove(item); + } + } + return RoundRectCard(SizedBox( + height: 27 * clone.length + 25, + child: ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 13), + physics: const NeverScrollableScrollPhysics(), + itemCount: clone.length, + itemBuilder: (_, idx) { + final disk = clone[idx]; + return Padding( + padding: const EdgeInsets.all(3), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${disk.usedPercent}% of ${disk.size}', + style: const TextStyle(fontSize: 11), + textScaleFactor: 1.0, + ), + Text(disk.mountPath, + style: const TextStyle(fontSize: 11), + textScaleFactor: 1.0) + ], + ), + _buildProgress(disk.usedPercent.toDouble()) + ], + ), + ); + }), + )); + } + + static const ignorePath = ['/run', '/sys', '/dev/shm', '/snap']; } diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index b667c7b7..4ceba42c 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -110,7 +110,7 @@ class _ServerPageState extends State Widget _buildRealServerCard( ServerStatus ss, String serverName, ServerConnectionState cs) { final rootDisk = - ss.disk.firstWhere((element) => element!.mountLocation == '/'); + ss.disk.firstWhere((element) => element.mountLocation == '/'); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -137,11 +137,10 @@ class _ServerPageState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildPercentCircle(ss.cpu2Status.usedPercent(), 'CPU'), - _buildPercentCircle( - ss.memList[1]! / ss.memList[0]! * 100 + 0.01, 'Mem'), - _buildIOData('Net', 'Conn:\n' + ss.tcp.maxConn!.toString(), + _buildPercentCircle(ss.memList[1] / ss.memList[0] * 100, 'Mem'), + _buildIOData('Net', 'Conn:\n' + ss.tcp.maxConn.toString(), 'Fail:\n' + ss.tcp.fail.toString()), - _buildIOData('Disk', 'Total:\n' + rootDisk!.size!, + _buildIOData('Disk', 'Total:\n' + rootDisk.size, 'Used:\n' + rootDisk.usedPercent.toString() + '%') ], ), @@ -203,8 +202,8 @@ class _ServerPageState extends State } Widget _buildPercentCircle(double percent, String title) { - if (percent == 0.0) percent += 0.01; - if (percent == 100.0) percent -= 0.01; + if (percent <= 0) percent = 0.01; + if (percent >= 100) percent = 99.9; return SizedBox( width: _media.size.width * 0.2, height: _media.size.height * 0.1,