mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
使用provider获取数据,Android底部导航栏透明
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:toolbox/core/persistant_store.dart';
|
import 'package:toolbox/core/persistant_store.dart';
|
||||||
import 'package:toolbox/view/widget/card_dialog.dart';
|
import 'package:toolbox/view/widget/card_dialog.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -67,3 +69,16 @@ Widget buildSwitch(BuildContext context, StoreProperty<bool> 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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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/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_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/data/store/server.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
class ServerProvider extends BusyProvider {
|
class ServerProvider extends BusyProvider {
|
||||||
late List<ServerPrivateInfo> _servers;
|
late List<ServerPrivateInfo> _servers;
|
||||||
|
late List<ServerStatus> _serversStatus;
|
||||||
|
late List<SSHClient> _clients;
|
||||||
|
|
||||||
List<ServerPrivateInfo> get servers => _servers;
|
List<ServerPrivateInfo> get servers => _servers;
|
||||||
|
List<ServerStatus> get serversStatus => _serversStatus;
|
||||||
|
|
||||||
Future<void> 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<void> loadLocalData() async {
|
||||||
setBusyState(true);
|
setBusyState(true);
|
||||||
_servers = locator<ServerStore>().fetch();
|
_servers = locator<ServerStore>().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);
|
setBusyState(false);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
_serversStatus = await Future.wait(
|
||||||
|
_servers.map((s) => _getData(s, _servers.indexOf(s))));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> startAutoRefresh() async {
|
||||||
|
Timer.periodic(const Duration(seconds: 3), (_) async {
|
||||||
|
await refreshData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void addServer(ServerPrivateInfo info) {
|
void addServer(ServerPrivateInfo info) {
|
||||||
_servers.add(info);
|
_servers.add(info);
|
||||||
locator<ServerStore>().put(info);
|
locator<ServerStore>().put(info);
|
||||||
@@ -26,4 +73,81 @@ class ServerProvider extends BusyProvider {
|
|||||||
locator<ServerStore>().delete(info);
|
locator<ServerStore>().delete(info);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ServerStatus> _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<DiskInfo> _getDisk(String disk) {
|
||||||
|
final list = <DiskInfo>[];
|
||||||
|
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<int> _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 [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'package:toolbox/locator.dart';
|
|||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
await setupLocator();
|
await setupLocator();
|
||||||
locator<ServerProvider>().loadData();
|
locator<ServerProvider>().loadLocalData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void runInZone(dynamic Function() body) {
|
void runInZone(dynamic Function() body) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:after_layout/after_layout.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
|
import 'package:toolbox/core/utils.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
import 'package:toolbox/data/res/build_data.dart';
|
import 'package:toolbox/data/res/build_data.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
@@ -33,6 +34,7 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
setTransparentNavigationBar(context);
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -103,6 +105,6 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
@override
|
@override
|
||||||
Future<void> afterFirstLayout(BuildContext context) async {
|
Future<void> afterFirstLayout(BuildContext context) async {
|
||||||
await GetIt.I.allReady();
|
await GetIt.I.allReady();
|
||||||
await locator<ServerProvider>().loadData();
|
await locator<ServerProvider>().loadLocalData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,9 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:provider/provider.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/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_private_info.dart';
|
||||||
import 'package:toolbox/data/model/server_status.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/data/provider/server.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
import 'package:toolbox/view/widget/circle_pie.dart';
|
import 'package:toolbox/view/widget/circle_pie.dart';
|
||||||
@@ -76,8 +72,8 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 13),
|
const SizedBox(height: 13),
|
||||||
...pro.servers
|
...pro.servers.map((e) => _buildEachServerCard(
|
||||||
.map((e) => _buildEachServerCard(e, pro.servers.indexOf(e)))
|
pro.serversStatus[pro.servers.indexOf(e)], e))
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
})),
|
})),
|
||||||
@@ -182,159 +178,29 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ServerStatus>? _getData(ServerPrivateInfo info) async {
|
Widget _buildEachServerCard(ServerStatus ss, ServerPrivateInfo spi) {
|
||||||
final client = SSHClient(
|
return GestureDetector(
|
||||||
host: info.ip!,
|
child: _buildEachCardContent(ss, spi),
|
||||||
port: info.port!,
|
onLongPress: () =>
|
||||||
username: info.user!,
|
showRoundDialog(context, '是否删除', const Text('删除后无法恢复'), [
|
||||||
passwordOrKey: info.authorization,
|
TextButton(
|
||||||
);
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('否')),
|
||||||
await client.connect();
|
TextButton(
|
||||||
final cpu = await client.execute(
|
onPressed: () {
|
||||||
"top -bn1 | grep load | awk '{printf \"%.2f\", \$(NF-2)}'") ??
|
serverProvider.delServer(spi);
|
||||||
'0';
|
Navigator.of(context).pop();
|
||||||
final mem = await client.execute('free -m') ?? '';
|
},
|
||||||
final sysVer = await client.execute('cat /etc/issue.net') ?? 'Unkown';
|
child: const Text('是'))
|
||||||
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<DiskInfo> _getDisk(String disk) {
|
|
||||||
final list = <DiskInfo>[];
|
|
||||||
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<int> _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<ServerStatus>(
|
|
||||||
future: _getData(e),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<ServerStatus> 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 _buildEachCardContent(
|
Widget _buildEachCardContent(ServerStatus ss, ServerPrivateInfo spi) {
|
||||||
AsyncSnapshot<ServerStatus> 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);
|
|
||||||
}
|
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(13),
|
padding: const EdgeInsets.all(13),
|
||||||
child: child,
|
child: _buildRealServerCard(ss, spi.name ?? ''),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -461,6 +327,8 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
@override
|
@override
|
||||||
Future<void> afterFirstLayout(BuildContext context) async {
|
Future<void> afterFirstLayout(BuildContext context) async {
|
||||||
await GetIt.I.allReady();
|
await GetIt.I.allReady();
|
||||||
await locator<ServerProvider>().loadData();
|
await serverProvider.loadLocalData();
|
||||||
|
await serverProvider.refreshData();
|
||||||
|
await serverProvider.startAutoRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user