mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-02-10 02:05:03 +01:00
服务器状态页预览部分完成
This commit is contained in:
11
lib/data/model/server.dart
Normal file
11
lib/data/model/server.dart
Normal 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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user