服务器状态页预览部分完成

This commit is contained in:
LollipopKit
2021-09-18 19:03:06 +08:00
parent 2c9b08264f
commit 962ef4ca48
12 changed files with 223 additions and 139 deletions

View File

@@ -0,0 +1,11 @@
import 'package:ssh2/ssh2.dart';
import 'package:toolbox/data/model/server_private_info.dart';
import 'package:toolbox/data/model/server_status.dart';
class ServerInfo {
ServerPrivateInfo info;
ServerStatus status;
SSHClient client;
ServerInfo(this.info, this.status, this.client);
}

View File

@@ -11,9 +11,9 @@ import 'package:toolbox/data/store/server.dart';
import 'package:toolbox/locator.dart';
class ServerProvider extends BusyProvider {
late List<ServerPrivateInfo> _servers;
late List<ServerStatus> _serversStatus;
late List<SSHClient> _clients;
List<ServerPrivateInfo> _servers = [];
List<ServerStatus> _serversStatus = [];
List<SSHClient> _clients = [];
List<ServerPrivateInfo> get servers => _servers;
List<ServerStatus> get serversStatus => _serversStatus;
@@ -23,11 +23,11 @@ class ServerProvider extends BusyProvider {
memList: [100, 0],
disk: [
DiskInfo(
mountLocation: '',
mountPath: '',
used: '',
size: '',
avail: '',
mountLocation: '/',
mountPath: '/',
used: '0',
size: '0',
avail: '0',
usedPercent: 0)
],
sysVer: '',
@@ -37,17 +37,35 @@ class ServerProvider extends BusyProvider {
Future<void> loadLocalData() async {
setBusyState(true);
_servers = locator<ServerStore>().fetch();
_serversStatus = List.generate(_servers.length, (_) => emptyStatus);
initStatusList();
setBusyState(false);
notifyListeners();
}
void initStatusList() {
_clients = List.generate(
_servers.length,
(idx) => SSHClient(
(idx) => _fillClient(idx));
_serversStatus = List.generate(_servers.length, (idx) => _fillStatus(idx));
}
SSHClient _fillClient(int idx) {
if (idx < _clients.length) {
return _clients[idx];
}
return SSHClient(
host: _servers[idx].ip!,
port: _servers[idx].port!,
username: _servers[idx].user!,
passwordOrKey: _servers[idx].authorization,
));
setBusyState(false);
notifyListeners();
);
}
ServerStatus _fillStatus(int idx) {
if (idx < _serversStatus.length) {
return _serversStatus[idx];
}
return emptyStatus;
}
Future<void> refreshData() async {
@@ -57,7 +75,7 @@ class ServerProvider extends BusyProvider {
}
Future<void> startAutoRefresh() async {
Timer.periodic(const Duration(seconds: 3), (_) async {
Timer.periodic(const Duration(seconds: 7), (_) async {
await refreshData();
});
}
@@ -65,12 +83,21 @@ class ServerProvider extends BusyProvider {
void addServer(ServerPrivateInfo info) {
_servers.add(info);
locator<ServerStore>().put(info);
initStatusList();
notifyListeners();
}
void delServer(ServerPrivateInfo info) {
_servers.remove(info);
locator<ServerStore>().delete(info);
initStatusList();
notifyListeners();
}
void updateServer(ServerPrivateInfo old, ServerPrivateInfo newInfo) {
_servers[_servers.indexOf(old)] = newInfo;
locator<ServerStore>().update(old, newInfo);
initStatusList();
notifyListeners();
}

View File

@@ -2,9 +2,8 @@
class BuildData {
static const String name = "ToolBox";
static const int build = 40;
static const String engine =
"Flutter 2.2.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision f4abaa0735 (9 weeks ago) • 2021-07-01 12:46:11 -0700\nEngine • revision 241c87ad80\nTools • Dart 2.13.4\n";
static const String buildAt = "2021-09-04 09:34:17.507782";
static const int modifications = 0;
static const int build = 10;
static const String engine = "Flutter 2.5.0 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 4cc385b4b8 (10 days ago) • 2021-09-07 23:01:49 -0700\nEngine • revision f0826da7ef\nTools • Dart 2.14.0\n";
static const String buildAt = "2021-09-18 17:25:30.528659";
static const int modifications = 9;
}

View File

@@ -17,11 +17,18 @@ class ServerStore extends PersistentStore {
void delete(ServerPrivateInfo s) {
final ss = fetch();
ss.removeWhere((e) => e.ip == s.ip && e.port == s.port && e.user == e.user);
ss.removeAt(index(s));
box.put('servers', json.encode(ss));
}
bool have(ServerPrivateInfo s) => fetch()
.where((e) => e.ip == s.ip && e.port == s.port && e.user == e.user)
.isNotEmpty;
void update(ServerPrivateInfo old, ServerPrivateInfo newInfo) {
final ss = fetch();
ss[index(old)] = newInfo;
box.put('servers', json.encode(ss));
}
int index(ServerPrivateInfo s) => fetch()
.indexWhere((e) => e.ip == s.ip && e.port == s.port && e.user == e.user);
bool have(ServerPrivateInfo s) => index(s) != -1;
}

View File

@@ -81,36 +81,7 @@ class _ServerPageState extends State<ServerPage>
onTap: () => FocusScope.of(context).requestFocus(FocusNode()),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showRoundDialog(context, '新建服务器连接', _buildTextInputField(context), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('关闭')),
TextButton(
onPressed: () {
final authorization = keyController.text.isEmpty
? passwordController.text
: {
"privateKey": keyController.text,
"passphrase": passwordController.text
};
serverProvider.addServer(ServerPrivateInfo(
name: nameController.text,
ip: ipController.text,
port: int.parse(portController.text),
user: usernameController.text,
authorization: authorization));
nameController.clear();
ipController.clear();
portController.clear();
usernameController.clear();
passwordController.clear();
keyController.clear();
Navigator.of(context).pop();
},
child: const Text('连接'))
]);
},
onPressed: () => showAddServerDialog(),
tooltip: 'add a server',
heroTag: 'server page fab',
child: const Icon(Icons.add),
@@ -118,6 +89,37 @@ class _ServerPageState extends State<ServerPage>
);
}
void showAddServerDialog() {
showRoundDialog(context, '新建服务器连接', _buildTextInputField(context), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('关闭')),
TextButton(
onPressed: () {
final authorization = keyController.text.isEmpty
? passwordController.text
: {
"privateKey": keyController.text,
"passphrase": passwordController.text
};
serverProvider.addServer(ServerPrivateInfo(
name: nameController.text,
ip: ipController.text,
port: int.parse(portController.text),
user: usernameController.text,
authorization: authorization));
nameController.clear();
ipController.clear();
portController.clear();
usernameController.clear();
passwordController.clear();
keyController.clear();
Navigator.of(context).pop();
},
child: const Text('连接'))
]);
}
InputDecoration _buildDecoration(String label, {TextStyle? textStyle}) {
return InputDecoration(labelText: label, labelStyle: textStyle);
}
@@ -214,6 +216,13 @@ class _ServerPageState extends State<ServerPage>
for (var e in ss.memList!) {
memData.add(IndexPercent(ss.memList!.indexOf(e), e!.toInt()));
}
final mem1 = memData[1];
memData[1] = memData.last;
memData.last = mem1;
final rootDisk = ss.disk!.firstWhere((element) => element!.mountLocation == '/');
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -247,42 +256,45 @@ class _ServerPageState extends State<ServerPage>
ss.memList![1]! / ss.memList![0]! * 100, 'Mem', [
chart.Series<IndexPercent, int>(
id: 'Mem',
domainFn: (IndexPercent sales, _) => sales.id,
measureFn: (IndexPercent sales, _) => sales.percent,
domainFn: (IndexPercent mem, _) => mem.id,
measureFn: (IndexPercent mem, _) => mem.percent,
data: memData,
)
]),
_buildIOData('Net', ss.tcp!.maxConn!.toString(), '0kb/s'),
_buildIOData('Disk', '0kb/s', '0kb/s')
_buildIOData('Net', 'Conn:\n' + ss.tcp!.maxConn!.toString(), 'Fail:\n' + ss.tcp!.fail.toString()),
_buildIOData('Disk', 'Total:\n' + rootDisk!.size!, 'Used:\n' + rootDisk.usedPercent.toString() + '%')
],
)
),
],
);
}
Widget _buildIOData(String title, String up, String down) {
final statusTextStyle = TextStyle(fontSize: 11, color: _theme.textTheme.bodyText1!.color!.withAlpha(177));
return SizedBox(
width: _media.size.width * 0.2,
height: _media.size.height * 0.1,
child: Stack(
children: [
Positioned(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
Positioned.fill(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$up',
textAlign: TextAlign.start,
up,
style: statusTextStyle,
textAlign: TextAlign.center,
),
const SizedBox(height: 3),
Text(
'$down',
down + '\n',
style: statusTextStyle,
textAlign: TextAlign.center,
)
],
),
top: _media.size.height * 0.012,
left: 0,
right: 0,
),
),
Positioned(
child: Text(title, textAlign: TextAlign.center),
@@ -302,14 +314,11 @@ class _ServerPageState extends State<ServerPage>
child: Stack(
children: [
DonutPieChart(series),
Positioned(
child: Text(
'${percent.toStringAsFixed(1)}%',
Positioned.fill(
child: Center(child: Text(
'${percent.toStringAsFixed(1)}%\n',
textAlign: TextAlign.center,
),
left: 0,
right: 0,
top: _media.size.height * 0.03,
),),
),
Positioned(
child: Text(title, textAlign: TextAlign.center),