diff --git a/lib/core/utils.dart b/lib/core/utils.dart index 38c93107..246e9dde 100644 --- a/lib/core/utils.dart +++ b/lib/core/utils.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:toolbox/core/persistant_store.dart'; import 'package:toolbox/view/widget/card_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -67,3 +69,16 @@ Widget buildSwitch(BuildContext context, StoreProperty prop, }, ); } + +void setTransparentNavigationBar(BuildContext context) { + if (Platform.isAndroid) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarContrastEnforced: true, + systemNavigationBarIconBrightness: + isDarkMode(context) ? Brightness.light : Brightness.dark, + )); + } +} diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index fbb7f8a9..ce817a89 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -1,20 +1,67 @@ +import 'dart:async'; + +import 'package:ssh2/ssh2.dart'; +import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/provider_base.dart'; +import 'package:toolbox/data/model/disk_info.dart'; import 'package:toolbox/data/model/server_private_info.dart'; +import 'package:toolbox/data/model/server_status.dart'; +import 'package:toolbox/data/model/tcp_status.dart'; import 'package:toolbox/data/store/server.dart'; import 'package:toolbox/locator.dart'; class ServerProvider extends BusyProvider { late List _servers; + late List _serversStatus; + late List _clients; List get servers => _servers; + List get serversStatus => _serversStatus; - Future loadData() async { + ServerStatus get emptyStatus => ServerStatus( + cpuPercent: 0, + memList: [100, 0], + disk: [ + DiskInfo( + mountLocation: '', + mountPath: '', + used: '', + size: '', + avail: '', + usedPercent: 0) + ], + sysVer: '', + uptime: '', + tcp: TcpStatus(maxConn: 0, active: 0, passive: 0, fail: 0)); + + Future loadLocalData() async { setBusyState(true); _servers = locator().fetch(); + _serversStatus = List.generate(_servers.length, (_) => emptyStatus); + _clients = List.generate( + _servers.length, + (idx) => SSHClient( + host: _servers[idx].ip!, + port: _servers[idx].port!, + username: _servers[idx].user!, + passwordOrKey: _servers[idx].authorization, + )); setBusyState(false); notifyListeners(); } + Future refreshData() async { + _serversStatus = await Future.wait( + _servers.map((s) => _getData(s, _servers.indexOf(s)))); + notifyListeners(); + } + + Future startAutoRefresh() async { + Timer.periodic(const Duration(seconds: 3), (_) async { + await refreshData(); + }); + } + void addServer(ServerPrivateInfo info) { _servers.add(info); locator().put(info); @@ -26,4 +73,81 @@ class ServerProvider extends BusyProvider { locator().delete(info); notifyListeners(); } + + Future _getData(ServerPrivateInfo info, int idx) async { + final client = _clients[idx]; + if (!(await client.isConnected())) { + await client.connect(); + } + final cpu = await client.execute( + "top -bn1 | grep load | awk '{printf \"%.2f\", \$(NF-2)}'") ?? + '0'; + final mem = await client.execute('free -m') ?? ''; + final sysVer = await client.execute('cat /etc/issue.net') ?? 'Unkown'; + final upTime = await client.execute('uptime') ?? 'Failed'; + final disk = await client.execute('df -h') ?? 'Failed'; + final tcp = await client.execute('cat /proc/net/snmp') ?? 'Failed'; + + return ServerStatus( + cpuPercent: double.parse(cpu.trim()), + memList: _getMem(mem), + sysVer: sysVer.trim(), + disk: _getDisk(disk), + uptime: _getUpTime(upTime), + tcp: _getTcp(tcp)); + } + + String _getUpTime(String raw) { + return raw.split('up ')[1].split(', ')[0]; + } + + TcpStatus _getTcp(String raw) { + final lines = raw.split('\n'); + int idx = 0; + for (var item in lines) { + if (item.contains('Tcp:')) { + idx++; + } + 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); + } + } + return TcpStatus(maxConn: 0, active: 0, passive: 0, fail: 0); + } + + List _getDisk(String disk) { + final list = []; + final items = disk.split('\n'); + for (var item in items) { + if (items.indexOf(item) == 0 || item.isEmpty) { + 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])); + } + return list; + } + + List _getMem(String mem) { + for (var item in mem.split('\n')) { + if (item.contains('Mem:')) { + return RegExp(r'[1-9][0-9]*') + .allMatches(item) + .map((e) => int.parse(item.substring(e.start, e.end))) + .toList(); + } + } + return []; + } } diff --git a/lib/main.dart b/lib/main.dart index 54c7c9e9..a0f3259f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,7 @@ import 'package:toolbox/locator.dart'; Future initApp() async { await Hive.initFlutter(); await setupLocator(); - locator().loadData(); + locator().loadLocalData(); } void runInZone(dynamic Function() body) { diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index 4b7a1718..5d830935 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -2,6 +2,7 @@ import 'package:after_layout/after_layout.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:toolbox/core/route.dart'; +import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/build_data.dart'; import 'package:toolbox/locator.dart'; @@ -33,6 +34,7 @@ class _MyHomePageState extends State @override Widget build(BuildContext context) { + setTransparentNavigationBar(context); super.build(context); return Scaffold( appBar: AppBar( @@ -103,6 +105,6 @@ class _MyHomePageState extends State @override Future afterFirstLayout(BuildContext context) async { await GetIt.I.allReady(); - await locator().loadData(); + await locator().loadLocalData(); } } diff --git a/lib/view/page/server.dart b/lib/view/page/server.dart index aeb6b2a8..0571c280 100644 --- a/lib/view/page/server.dart +++ b/lib/view/page/server.dart @@ -5,13 +5,9 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; -import 'package:ssh2/ssh2.dart'; -import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/utils.dart'; -import 'package:toolbox/data/model/disk_info.dart'; import 'package:toolbox/data/model/server_private_info.dart'; import 'package:toolbox/data/model/server_status.dart'; -import 'package:toolbox/data/model/tcp_status.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/widget/circle_pie.dart'; @@ -76,8 +72,8 @@ class _ServerPageState extends State ), children: [ const SizedBox(height: 13), - ...pro.servers - .map((e) => _buildEachServerCard(e, pro.servers.indexOf(e))) + ...pro.servers.map((e) => _buildEachServerCard( + pro.serversStatus[pro.servers.indexOf(e)], e)) ], )); })), @@ -182,159 +178,29 @@ class _ServerPageState extends State ); } - Future? _getData(ServerPrivateInfo info) async { - final client = SSHClient( - host: info.ip!, - port: info.port!, - username: info.user!, - passwordOrKey: info.authorization, - ); - - await client.connect(); - final cpu = await client.execute( - "top -bn1 | grep load | awk '{printf \"%.2f\", \$(NF-2)}'") ?? - '0'; - final mem = await client.execute('free -m') ?? ''; - final sysVer = await client.execute('cat /etc/issue.net') ?? 'Unkown'; - final upTime = await client.execute('uptime') ?? 'Failed'; - final disk = await client.execute('df -h') ?? 'Failed'; - final tcp = await client.execute('cat /proc/net/snmp') ?? 'Failed'; - - return ServerStatus( - cpuPercent: double.parse(cpu.trim()), - memList: _getMem(mem), - sysVer: sysVer.trim(), - disk: _getDisk(disk), - uptime: _getUpTime(upTime), - tcp: _getTcp(tcp)); - } - - String _getUpTime(String raw) { - return raw.split('up ')[1].split(', ')[0]; - } - - TcpStatus _getTcp(String raw) { - final lines = raw.split('\n'); - int idx = 0; - for (var item in lines) { - if (item.contains('Tcp:')) { - idx++; - } - 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); - } - } - return TcpStatus(maxConn: 0, active: 0, passive: 0, fail: 0); - } - - List _getDisk(String disk) { - final list = []; - final items = disk.split('\n'); - for (var item in items) { - if (items.indexOf(item) == 0 || item.isEmpty) { - 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])); - } - return list; - } - - List _getMem(String mem) { - for (var item in mem.split('\n')) { - if (item.contains('Mem:')) { - return RegExp(r'[1-9][0-9]*') - .allMatches(item) - .map((e) => int.parse(item.substring(e.start, e.end))) - .toList(); - } - } - return []; - } - - Widget _buildEachServerCard(ServerPrivateInfo e, int index) { - return FutureBuilder( - future: _getData(e), - builder: (BuildContext context, AsyncSnapshot snapshot) { - return GestureDetector( - child: _buildEachCardContent(snapshot, e.name ?? '', index), - onLongPress: () => - showRoundDialog(context, '是否删除', const Text('删除后无法恢复'), [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('否')), - TextButton( - onPressed: () { - serverProvider.delServer(e); - Navigator.of(context).pop(); - }, - child: const Text('是')) - ]), - ); - }, + Widget _buildEachServerCard(ServerStatus ss, ServerPrivateInfo spi) { + return GestureDetector( + child: _buildEachCardContent(ss, spi), + onLongPress: () => + showRoundDialog(context, '是否删除', const Text('删除后无法恢复'), [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('否')), + TextButton( + onPressed: () { + serverProvider.delServer(spi); + Navigator.of(context).pop(); + }, + child: const Text('是')) + ]), ); } - Widget _buildEachCardContent( - AsyncSnapshot snapshot, String serverName, int index) { - Widget child; - if (snapshot.connectionState != ConnectionState.done) { - if (cachedServerStatus.length > index && cachedServerStatus.elementAt(index) != null) { - child = _buildRealServerCard(cachedServerStatus.elementAt(index)!, serverName); - } else { - child = _buildRealServerCard( - ServerStatus( - cpuPercent: 0, - memList: [100, 0], - disk: [ - DiskInfo( - mountLocation: '', - mountPath: '', - used: '', - size: '', - avail: '', - usedPercent: 0) - ], - sysVer: '', - uptime: '', - tcp: TcpStatus(maxConn: 0, active: 0, passive: 0, fail: 0)), - serverName); - } - } else if (snapshot.hasError) { - child = Column( - children: [ - Text( - serverName, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - Center( - child: Text("Error: ${snapshot.error}"), - ) - ], - ); - } else { - if (cachedServerStatus.length <= index) { - cachedServerStatus.add(snapshot.data!); - } else { - cachedServerStatus[index] = snapshot.data!; - } - child = _buildRealServerCard(snapshot.data!, serverName); - } + Widget _buildEachCardContent(ServerStatus ss, ServerPrivateInfo spi) { return Card( child: Padding( padding: const EdgeInsets.all(13), - child: child, + child: _buildRealServerCard(ss, spi.name ?? ''), ), ); } @@ -461,6 +327,8 @@ class _ServerPageState extends State @override Future afterFirstLayout(BuildContext context) async { await GetIt.I.allReady(); - await locator().loadData(); + await serverProvider.loadLocalData(); + await serverProvider.refreshData(); + await serverProvider.startAutoRefresh(); } }