mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-02-16 21:24:56 +01:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fb8c88ab8 | ||
|
|
f480c49f1f | ||
|
|
f7558d6beb | ||
|
|
de1e970108 | ||
|
|
9ef59f4c12 | ||
|
|
89ef2cb95c | ||
|
|
e0fb591dea | ||
|
|
7c34530821 | ||
|
|
72c1901989 | ||
|
|
ff76c6c539 | ||
|
|
d38bad7802 | ||
|
|
9e73dd07ca | ||
|
|
3105552eae | ||
|
|
ad05b296f6 | ||
|
|
f0a8941b59 | ||
|
|
fbc8f9598d | ||
|
|
e7d87b40b8 | ||
|
|
1cd69c8f44 | ||
|
|
6e3fca32db | ||
|
|
1943fde6eb | ||
|
|
2eb6e19a86 | ||
|
|
9f3f07388e | ||
|
|
702dd86a84 | ||
|
|
434ef77c03 |
32
README.md
32
README.md
@@ -12,7 +12,7 @@ A new Flutter project which provide a chart view to display server status data.
|
|||||||
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3347.PNG">
|
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3347.PNG">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3385.PNG">
|
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/detail.jpg">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -31,14 +31,28 @@ A new Flutter project which provide a chart view to display server status data.
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Milestone
|
## Milestone
|
||||||
- [x] SSH Connect
|
- [x] SSH connect
|
||||||
- [x] Server Info Store
|
- [x] Server info store
|
||||||
- [x] Status Chart View
|
- [x] Status chart view
|
||||||
- [x] Base64/Url En/Decode
|
- [x] Base64/Url En/Decode
|
||||||
- [x] Private Key Store
|
- [x] Private key store
|
||||||
- [x] Server Status Detail Page
|
- [x] Server status detail page
|
||||||
- [x] Theme Switch
|
- [x] Theme switch
|
||||||
- [ ] Execute Snippet
|
- [x] Execute snippet
|
||||||
|
- [ ] Migrate from `ssh2` to `dartssh2`
|
||||||
|
- [ ] Desktop support
|
||||||
|
|
||||||
|
## Build
|
||||||
|
Please use `make.dart` to build.
|
||||||
|
```shell
|
||||||
|
# build android apk
|
||||||
|
./make.dart build android
|
||||||
|
# due to pub package 'ssh2' incompatibility
|
||||||
|
# can't build ios ipa through './make.dart build ios'
|
||||||
|
# more info: [https://github.com/jda258/flutter_ssh2/issues/8]
|
||||||
|
# please run below cmd to run on ios device
|
||||||
|
./make.dart run release
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
`Apache License. LollipopKit 2021`
|
`LGPL License. LollipopKit 2021`
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="tech.lolli.toolbox">
|
package="tech.lolli.toolbox">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="ServerBox"
|
android:label="ServerBox"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:hasFragileUserData="true"
|
||||||
|
android:restoreAnyVersion="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ PODS:
|
|||||||
- GZ-NMSSH (4.1.5)
|
- GZ-NMSSH (4.1.5)
|
||||||
- path_provider (0.0.1):
|
- path_provider (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- r_upgrade (0.0.1):
|
||||||
|
- Flutter
|
||||||
- ssh2 (2.2.3):
|
- ssh2 (2.2.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- GZ-NMSSH (~> 4.1.5)
|
- GZ-NMSSH (~> 4.1.5)
|
||||||
@@ -15,6 +17,7 @@ DEPENDENCIES:
|
|||||||
- countly_flutter (from `.symlinks/plugins/countly_flutter/ios`)
|
- countly_flutter (from `.symlinks/plugins/countly_flutter/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||||
|
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
|
||||||
- ssh2 (from `.symlinks/plugins/ssh2/ios`)
|
- ssh2 (from `.symlinks/plugins/ssh2/ios`)
|
||||||
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||||
|
|
||||||
@@ -29,6 +32,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
path_provider:
|
path_provider:
|
||||||
:path: ".symlinks/plugins/path_provider/ios"
|
:path: ".symlinks/plugins/path_provider/ios"
|
||||||
|
r_upgrade:
|
||||||
|
:path: ".symlinks/plugins/r_upgrade/ios"
|
||||||
ssh2:
|
ssh2:
|
||||||
:path: ".symlinks/plugins/ssh2/ios"
|
:path: ".symlinks/plugins/ssh2/ios"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
@@ -39,6 +44,7 @@ SPEC CHECKSUMS:
|
|||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
GZ-NMSSH: d749f8ae2fd0094b953cd1d5abd8e0cab3c93f8d
|
GZ-NMSSH: d749f8ae2fd0094b953cd1d5abd8e0cab3c93f8d
|
||||||
path_provider: d1e9807085df1f9cc9318206cd649dc0b76be3de
|
path_provider: d1e9807085df1f9cc9318206cd649dc0b76be3de
|
||||||
|
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
|
||||||
ssh2: 74165efc99417a075ecafd52caf93edadfb5eb60
|
ssh2: 74165efc99417a075ecafd52caf93edadfb5eb60
|
||||||
url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649
|
url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ class Analysis {
|
|||||||
static bool _enabled = false;
|
static bool _enabled = false;
|
||||||
|
|
||||||
static Future<void> init(bool debug) async {
|
static Future<void> init(bool debug) async {
|
||||||
if (_url.isEmpty || _key.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_enabled = true;
|
_enabled = true;
|
||||||
await Countly.setLoggingEnabled(debug);
|
await Countly.setLoggingEnabled(debug);
|
||||||
await Countly.init(_url, _key);
|
await Countly.init(_url, _key);
|
||||||
@@ -22,12 +18,14 @@ class Analysis {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void recordView(String view) {
|
static void recordView(String view) {
|
||||||
if (!_enabled) return;
|
if (_enabled) {
|
||||||
Countly.recordView(view);
|
Countly.recordView(view);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void recordException(Object exception, [bool fatal = false]) {
|
static void recordException(Object exception, [bool fatal = false]) {
|
||||||
if (!_enabled) return;
|
if (_enabled) {
|
||||||
Countly.logException(exception.toString(), !fatal, null);
|
Countly.logException(exception.toString(), !fatal, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:r_upgrade/r_upgrade.dart';
|
||||||
import 'package:toolbox/core/utils.dart';
|
import 'package:toolbox/core/utils.dart';
|
||||||
import 'package:toolbox/data/provider/app.dart';
|
import 'package:toolbox/data/provider/app.dart';
|
||||||
import 'package:toolbox/data/res/build_data.dart';
|
import 'package:toolbox/data/res/build_data.dart';
|
||||||
@@ -39,8 +40,14 @@ Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
|||||||
showSnackBarWithAction(
|
showSnackBarWithAction(
|
||||||
context,
|
context,
|
||||||
update.min > BuildData.build
|
update.min > BuildData.build
|
||||||
? '您的版本过旧,请及时更新'
|
? 'Your version is too old. \nPlease update to v1.0.${update.newest}.'
|
||||||
: '${BuildData.name}有更新啦,Ver:${update.newest}\n${update.changelog}',
|
: 'Update: v1.0.${update.newest} available. \n${update.changelog}',
|
||||||
'更新',
|
'Update', () async {
|
||||||
() => openUrl(Platform.isAndroid ? update.android : update.ios));
|
if (Platform.isAndroid) {
|
||||||
|
await RUpgrade.upgrade(update.android,
|
||||||
|
fileName: update.android.split('/').last, isAutoRequestInstall: true);
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
showSnackBar(context, const Text('Not support iOS now.'));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ bool isDarkMode(BuildContext context) =>
|
|||||||
void showSnackBar(BuildContext context, Widget child) =>
|
void showSnackBar(BuildContext context, Widget child) =>
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: child));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: child));
|
||||||
|
|
||||||
void showSnackBarWithAction(
|
void showSnackBarWithAction(BuildContext context, String content, String action,
|
||||||
BuildContext context, String content, String action, Function onTap) {
|
GestureTapCallback onTap) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: Text(content),
|
content: Text(content),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: action,
|
label: action,
|
||||||
onPressed: () => onTap,
|
onPressed: onTap,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,28 +33,6 @@ class CpuStatus {
|
|||||||
this.irq,
|
this.irq,
|
||||||
this.softirq,
|
this.softirq,
|
||||||
);
|
);
|
||||||
CpuStatus.fromJson(Map<String, dynamic> json) {
|
|
||||||
id = json["id"];
|
|
||||||
user = json["user"]?.toInt();
|
|
||||||
sys = json["sys"]?.toInt();
|
|
||||||
nice = json["nice"]?.toInt();
|
|
||||||
idle = json["idle"]?.toInt();
|
|
||||||
iowait = json["iowait"]?.toInt();
|
|
||||||
irq = json["irq"]?.toInt();
|
|
||||||
softirq = json["softirq"]?.toInt();
|
|
||||||
}
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
|
||||||
data["id"] = id;
|
|
||||||
data["user"] = user;
|
|
||||||
data["sys"] = sys;
|
|
||||||
data["nice"] = nice;
|
|
||||||
data["idle"] = idle;
|
|
||||||
data["iowait"] = iowait;
|
|
||||||
data["irq"] = irq;
|
|
||||||
data["softirq"] = softirq;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
int get total => user + sys + nice + idle + iowait + irq + softirq;
|
int get total => user + sys + nice + idle + iowait + irq + softirq;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,23 +25,4 @@ class DiskInfo {
|
|||||||
this.size,
|
this.size,
|
||||||
this.avail,
|
this.avail,
|
||||||
);
|
);
|
||||||
|
|
||||||
DiskInfo.fromJson(Map<String, dynamic> json) {
|
|
||||||
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<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
|
||||||
data["mountPath"] = mountPath;
|
|
||||||
data["mountLocation"] = mountLocation;
|
|
||||||
data["usedPercent"] = usedPercent;
|
|
||||||
data["used"] = used;
|
|
||||||
data["size"] = size;
|
|
||||||
data["avail"] = avail;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
lib/data/model/server/memory.dart
Normal file
15
lib/data/model/server/memory.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
class Memory {
|
||||||
|
int total;
|
||||||
|
int used;
|
||||||
|
int free;
|
||||||
|
int shared;
|
||||||
|
int cache;
|
||||||
|
int avail;
|
||||||
|
Memory(
|
||||||
|
{required this.total,
|
||||||
|
required this.used,
|
||||||
|
required this.free,
|
||||||
|
required this.shared,
|
||||||
|
required this.cache,
|
||||||
|
required this.avail});
|
||||||
|
}
|
||||||
71
lib/data/model/server/net_speed.dart
Normal file
71
lib/data/model/server/net_speed.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
class NetSpeedPart {
|
||||||
|
String device;
|
||||||
|
int bytesIn;
|
||||||
|
int bytesOut;
|
||||||
|
int time;
|
||||||
|
NetSpeedPart(this.device, this.bytesIn, this.bytesOut, this.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetSpeed {
|
||||||
|
List<NetSpeedPart> old;
|
||||||
|
List<NetSpeedPart> now;
|
||||||
|
NetSpeed(this.old, this.now);
|
||||||
|
|
||||||
|
List<String> get devices {
|
||||||
|
final devices = <String>[];
|
||||||
|
for (var item in now) {
|
||||||
|
devices.add(item.device);
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetSpeed update(List<NetSpeedPart> newOne) => NetSpeed(now, newOne);
|
||||||
|
|
||||||
|
int get timeDiff => now[0].time - old[0].time;
|
||||||
|
|
||||||
|
String speedIn({String? device}) {
|
||||||
|
if (old[0].device == '' || now[0].device == '') return '0kb/s';
|
||||||
|
int idx = 0;
|
||||||
|
if (device != null) {
|
||||||
|
for (var item in now) {
|
||||||
|
if (item.device == device) {
|
||||||
|
idx = now.indexOf(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final speedInBytesPerSecond =
|
||||||
|
(now[idx].bytesIn - old[idx].bytesIn) / timeDiff;
|
||||||
|
int squareTimes = 0;
|
||||||
|
for (; speedInBytesPerSecond / pow(1024, squareTimes) > 1024;) {
|
||||||
|
if (squareTimes >= suffixs.length - 1) break;
|
||||||
|
squareTimes++;
|
||||||
|
}
|
||||||
|
return '${(speedInBytesPerSecond / pow(1024, squareTimes)).toStringAsFixed(1)} ${suffixs[squareTimes]}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String speedOut({String? device}) {
|
||||||
|
if (old[0].device == '' || now[0].device == '') return '0kb/s';
|
||||||
|
int idx = 0;
|
||||||
|
if (device != null) {
|
||||||
|
for (var item in now) {
|
||||||
|
if (item.device == device) {
|
||||||
|
idx = now.indexOf(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final speedInBytesPerSecond =
|
||||||
|
(now[idx].bytesOut - old[idx].bytesOut) / timeDiff;
|
||||||
|
int squareTimes = 0;
|
||||||
|
for (; speedInBytesPerSecond / pow(1024, squareTimes) > 1024;) {
|
||||||
|
if (squareTimes >= suffixs.length - 1) break;
|
||||||
|
squareTimes++;
|
||||||
|
}
|
||||||
|
return '${(speedInBytesPerSecond / pow(1024, squareTimes)).toStringAsFixed(1)} ${suffixs[squareTimes]}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffixs = ['b/s', 'kb/s', 'mb/s', 'gb/s'];
|
||||||
@@ -35,7 +35,7 @@ class PrivateKeyInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PrivateKeyInfo>? getPrivateKeyInfoList(dynamic data) {
|
List<PrivateKeyInfo> getPrivateKeyInfoList(dynamic data) {
|
||||||
List<PrivateKeyInfo> ss = [];
|
List<PrivateKeyInfo> ss = [];
|
||||||
if (data is String) {
|
if (data is String) {
|
||||||
data = json.decode(data);
|
data = json.decode(data);
|
||||||
|
|||||||
@@ -13,25 +13,27 @@ class ServerPrivateInfo {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String? name;
|
late String name;
|
||||||
String? ip;
|
late String ip;
|
||||||
int? port;
|
late int port;
|
||||||
String? user;
|
late String user;
|
||||||
Object? authorization;
|
late Object authorization;
|
||||||
|
String? pubKeyId;
|
||||||
|
|
||||||
ServerPrivateInfo({
|
ServerPrivateInfo(
|
||||||
this.name,
|
{required this.name,
|
||||||
this.ip,
|
required this.ip,
|
||||||
this.port,
|
required this.port,
|
||||||
this.user,
|
required this.user,
|
||||||
this.authorization,
|
required this.authorization,
|
||||||
});
|
this.pubKeyId});
|
||||||
ServerPrivateInfo.fromJson(Map<String, dynamic> json) {
|
ServerPrivateInfo.fromJson(Map<String, dynamic> json) {
|
||||||
name = json["name"]?.toString();
|
name = json["name"].toString();
|
||||||
ip = json["ip"]?.toString();
|
ip = json["ip"].toString();
|
||||||
port = json["port"]?.toInt();
|
port = json["port"].toInt();
|
||||||
user = json["user"]?.toString();
|
user = json["user"].toString();
|
||||||
authorization = json["authorization"];
|
authorization = json["authorization"];
|
||||||
|
pubKeyId = json["pubKeyId"]?.toString();
|
||||||
}
|
}
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
@@ -40,11 +42,12 @@ class ServerPrivateInfo {
|
|||||||
data["port"] = port;
|
data["port"] = port;
|
||||||
data["user"] = user;
|
data["user"] = user;
|
||||||
data["authorization"] = authorization;
|
data["authorization"] = authorization;
|
||||||
|
data["pubKeyId"] = pubKeyId;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ServerPrivateInfo>? getServerInfoList(dynamic data) {
|
List<ServerPrivateInfo> getServerInfoList(dynamic data) {
|
||||||
List<ServerPrivateInfo> ss = [];
|
List<ServerPrivateInfo> ss = [];
|
||||||
if (data is String) {
|
if (data is String) {
|
||||||
data = json.decode(data);
|
data = json.decode(data);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:toolbox/data/model/server/cpu_2_status.dart';
|
import 'package:toolbox/data/model/server/cpu_2_status.dart';
|
||||||
import 'package:toolbox/data/model/server/disk_info.dart';
|
import 'package:toolbox/data/model/server/disk_info.dart';
|
||||||
|
import 'package:toolbox/data/model/server/memory.dart';
|
||||||
|
import 'package:toolbox/data/model/server/net_speed.dart';
|
||||||
import 'package:toolbox/data/model/server/tcp_status.dart';
|
import 'package:toolbox/data/model/server/tcp_status.dart';
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -28,13 +30,16 @@ class ServerStatus {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
late Cpu2Status cpu2Status;
|
Cpu2Status cpu2Status;
|
||||||
late List<int> memList;
|
Memory memory;
|
||||||
late String sysVer;
|
String sysVer;
|
||||||
late String uptime;
|
String uptime;
|
||||||
late List<DiskInfo> disk;
|
List<DiskInfo> disk;
|
||||||
late TcpStatus tcp;
|
TcpStatus tcp;
|
||||||
|
NetSpeed netSpeed;
|
||||||
|
String? failedInfo;
|
||||||
|
|
||||||
ServerStatus(this.cpu2Status, this.memList, this.sysVer, this.uptime,
|
ServerStatus(this.cpu2Status, this.memory, this.sysVer, this.uptime,
|
||||||
this.disk, this.tcp);
|
this.disk, this.tcp, this.netSpeed,
|
||||||
|
{this.failedInfo});
|
||||||
}
|
}
|
||||||
|
|||||||
30
lib/data/model/server/snippet.dart
Normal file
30
lib/data/model/server/snippet.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class Snippet {
|
||||||
|
late String name;
|
||||||
|
late String script;
|
||||||
|
Snippet(this.name, this.script);
|
||||||
|
|
||||||
|
Snippet.fromJson(Map<String, dynamic> json) {
|
||||||
|
name = json['name'].toString();
|
||||||
|
script = json['script'].toString();
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final data = <String, dynamic>{};
|
||||||
|
data['name'] = name;
|
||||||
|
data['script'] = script;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Snippet> getSnippetList(dynamic data) {
|
||||||
|
List<Snippet> ss = [];
|
||||||
|
if (data is String) {
|
||||||
|
data = json.decode(data);
|
||||||
|
}
|
||||||
|
for (var t in data) {
|
||||||
|
ss.add(Snippet.fromJson(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
@@ -6,11 +6,14 @@ 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/server/cpu_2_status.dart';
|
import 'package:toolbox/data/model/server/cpu_2_status.dart';
|
||||||
import 'package:toolbox/data/model/server/cpu_status.dart';
|
import 'package:toolbox/data/model/server/cpu_status.dart';
|
||||||
|
import 'package:toolbox/data/model/server/memory.dart';
|
||||||
|
import 'package:toolbox/data/model/server/net_speed.dart';
|
||||||
import 'package:toolbox/data/model/server/server_connection_state.dart';
|
import 'package:toolbox/data/model/server/server_connection_state.dart';
|
||||||
import 'package:toolbox/data/model/server/disk_info.dart';
|
import 'package:toolbox/data/model/server/disk_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server.dart';
|
import 'package:toolbox/data/model/server/server.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server_status.dart';
|
import 'package:toolbox/data/model/server/server_status.dart';
|
||||||
|
import 'package:toolbox/data/model/server/snippet.dart';
|
||||||
import 'package:toolbox/data/model/server/tcp_status.dart';
|
import 'package:toolbox/data/model/server/tcp_status.dart';
|
||||||
import 'package:toolbox/data/store/server.dart';
|
import 'package:toolbox/data/store/server.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
@@ -22,6 +25,14 @@ class ServerProvider extends BusyProvider {
|
|||||||
|
|
||||||
final logger = Logger('ServerProvider');
|
final logger = Logger('ServerProvider');
|
||||||
|
|
||||||
|
Memory get emptyMemory =>
|
||||||
|
Memory(total: 1, used: 0, free: 1, shared: 0, cache: 0, avail: 1);
|
||||||
|
|
||||||
|
NetSpeedPart get emptyNetSpeedPart => NetSpeedPart('', 0, 0, 0);
|
||||||
|
|
||||||
|
NetSpeed get emptyNetSpeed =>
|
||||||
|
NetSpeed([emptyNetSpeedPart], [emptyNetSpeedPart]);
|
||||||
|
|
||||||
CpuStatus get emptyCpuStatus => CpuStatus('cpu', 0, 0, 0, 0, 0, 0, 0);
|
CpuStatus get emptyCpuStatus => CpuStatus('cpu', 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
Cpu2Status get emptyCpu2Status =>
|
Cpu2Status get emptyCpu2Status =>
|
||||||
@@ -29,11 +40,12 @@ class ServerProvider extends BusyProvider {
|
|||||||
|
|
||||||
ServerStatus get emptyStatus => ServerStatus(
|
ServerStatus get emptyStatus => ServerStatus(
|
||||||
emptyCpu2Status,
|
emptyCpu2Status,
|
||||||
[100, 0],
|
emptyMemory,
|
||||||
'',
|
'Loading...',
|
||||||
'',
|
'',
|
||||||
[DiskInfo('/', '/', 0, '0', '0', '0')],
|
[DiskInfo('/', '/', 0, '0', '0', '0')],
|
||||||
TcpStatus(0, 0, 0, 0));
|
TcpStatus(0, 0, 0, 0),
|
||||||
|
emptyNetSpeed);
|
||||||
|
|
||||||
Future<void> loadLocalData() async {
|
Future<void> loadLocalData() async {
|
||||||
setBusyState(true);
|
setBusyState(true);
|
||||||
@@ -50,9 +62,9 @@ class ServerProvider extends BusyProvider {
|
|||||||
|
|
||||||
SSHClient genClient(ServerPrivateInfo spi) {
|
SSHClient genClient(ServerPrivateInfo spi) {
|
||||||
return SSHClient(
|
return SSHClient(
|
||||||
host: spi.ip!,
|
host: spi.ip,
|
||||||
port: spi.port!,
|
port: spi.port,
|
||||||
username: spi.user!,
|
username: spi.user,
|
||||||
passwordOrKey: spi.authorization);
|
passwordOrKey: spi.authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +126,9 @@ class ServerProvider extends BusyProvider {
|
|||||||
final client = _servers[idx].client;
|
final client = _servers[idx].client;
|
||||||
final connected = await client.isConnected();
|
final connected = await client.isConnected();
|
||||||
final state = _servers[idx].connectionState;
|
final state = _servers[idx].connectionState;
|
||||||
if (!connected || state != ServerConnectionState.connected) {
|
if (!connected ||
|
||||||
|
state == ServerConnectionState.failed ||
|
||||||
|
state == ServerConnectionState.disconnected) {
|
||||||
_servers[idx].connectionState = ServerConnectionState.connecting;
|
_servers[idx].connectionState = ServerConnectionState.connecting;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
final time1 = DateTime.now();
|
final time1 = DateTime.now();
|
||||||
@@ -127,6 +141,7 @@ class ServerProvider extends BusyProvider {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_servers[idx].connectionState = ServerConnectionState.failed;
|
_servers[idx].connectionState = ServerConnectionState.failed;
|
||||||
|
_servers[idx].status.failedInfo = e.toString().split(', ')[1];
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
logger.warning(e);
|
logger.warning(e);
|
||||||
}
|
}
|
||||||
@@ -142,6 +157,8 @@ class ServerProvider extends BusyProvider {
|
|||||||
final upTime = await client.execute('uptime') ?? '';
|
final upTime = await client.execute('uptime') ?? '';
|
||||||
final disk = await client.execute('df -h') ?? '';
|
final disk = await client.execute('df -h') ?? '';
|
||||||
final tcp = await client.execute('cat /proc/net/snmp') ?? '';
|
final tcp = await client.execute('cat /proc/net/snmp') ?? '';
|
||||||
|
final netSpeed =
|
||||||
|
await client.execute('cat /proc/net/dev && date +%s') ?? '';
|
||||||
|
|
||||||
return ServerStatus(
|
return ServerStatus(
|
||||||
_getCPU(cpu, _servers[idx].status.cpu2Status, cpuTemp),
|
_getCPU(cpu, _servers[idx].status.cpu2Status, cpuTemp),
|
||||||
@@ -149,7 +166,8 @@ class ServerProvider extends BusyProvider {
|
|||||||
_getSysVer(sysVer),
|
_getSysVer(sysVer),
|
||||||
_getUpTime(upTime),
|
_getUpTime(upTime),
|
||||||
_getDisk(disk),
|
_getDisk(disk),
|
||||||
_getTcp(tcp));
|
_getTcp(tcp),
|
||||||
|
_getNetSpeed(netSpeed, _servers[idx].status.netSpeed));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_servers[idx].connectionState = ServerConnectionState.failed;
|
_servers[idx].connectionState = ServerConnectionState.failed;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -158,6 +176,30 @@ class ServerProvider extends BusyProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [raw] example:
|
||||||
|
/// Inter-| Receive | Transmit
|
||||||
|
/// face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
|
||||||
|
/// lo: 45929941 269112 0 0 0 0 0 0 45929941 269112 0 0 0 0 0 0
|
||||||
|
/// eth0: 48481023 505772 0 0 0 0 0 0 36002262 202307 0 0 0 0 0 0
|
||||||
|
/// 1635752901
|
||||||
|
NetSpeed _getNetSpeed(String raw, NetSpeed old) {
|
||||||
|
final split = raw.split('\n');
|
||||||
|
final deviceCount = split.length - 3;
|
||||||
|
if (deviceCount < 1) return emptyNetSpeed;
|
||||||
|
final time = int.parse(split[split.length - 2]);
|
||||||
|
final results = <NetSpeedPart>[];
|
||||||
|
for (int idx = 2; idx < deviceCount; idx++) {
|
||||||
|
final data = split[idx].trim().split(':');
|
||||||
|
final device = data.first;
|
||||||
|
final bytes = data.last.trim().split(' ');
|
||||||
|
bytes.removeWhere((element) => element == '');
|
||||||
|
final bytesIn = int.parse(bytes.first);
|
||||||
|
final bytesOut = int.parse(bytes[8]);
|
||||||
|
results.add(NetSpeedPart(device, bytesIn, bytesOut, time));
|
||||||
|
}
|
||||||
|
return old.update(results);
|
||||||
|
}
|
||||||
|
|
||||||
String _getSysVer(String raw) {
|
String _getSysVer(String raw) {
|
||||||
final s = raw.split('=');
|
final s = raw.split('=');
|
||||||
if (s.length == 2) {
|
if (s.length == 2) {
|
||||||
@@ -233,15 +275,25 @@ class ServerProvider extends BusyProvider {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> _getMem(String mem) {
|
Memory _getMem(String mem) {
|
||||||
for (var item in mem.split('\n')) {
|
for (var item in mem.split('\n')) {
|
||||||
if (item.contains('Mem:')) {
|
if (item.contains('Mem:')) {
|
||||||
return RegExp(r'[1-9][0-9]*')
|
final split = item.replaceFirst('Mem:', '').split(' ');
|
||||||
.allMatches(item)
|
split.removeWhere((e) => e == '');
|
||||||
.map((e) => int.parse(item.substring(e.start, e.end)))
|
final memList = split.map((e) => int.parse(e)).toList();
|
||||||
.toList();
|
return Memory(
|
||||||
|
total: memList[0],
|
||||||
|
used: memList[1],
|
||||||
|
free: memList[2],
|
||||||
|
shared: memList[3],
|
||||||
|
cache: memList[4],
|
||||||
|
avail: memList[5]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return emptyMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> runSnippet(int idx, Snippet snippet) {
|
||||||
|
return _servers[idx].client.execute(snippet.script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
lib/data/provider/snippet.dart
Normal file
32
lib/data/provider/snippet.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:toolbox/core/provider_base.dart';
|
||||||
|
import 'package:toolbox/data/model/server/snippet.dart';
|
||||||
|
import 'package:toolbox/data/store/snippet.dart';
|
||||||
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
|
class SnippetProvider extends BusyProvider {
|
||||||
|
List<Snippet> get snippets => _snippets;
|
||||||
|
late List<Snippet> _snippets;
|
||||||
|
|
||||||
|
void loadData() {
|
||||||
|
_snippets = locator<SnippetStore>().fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addInfo(Snippet snippet) {
|
||||||
|
_snippets.add(snippet);
|
||||||
|
locator<SnippetStore>().put(snippet);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void delInfo(Snippet snippet) {
|
||||||
|
_snippets.removeWhere((e) => e.name == snippet.name);
|
||||||
|
locator<SnippetStore>().delete(snippet);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateInfo(Snippet old, Snippet newOne) {
|
||||||
|
final idx = _snippets.indexWhere((e) => e.name == old.name);
|
||||||
|
_snippets[idx] = newOne;
|
||||||
|
locator<SnippetStore>().update(old, newOne);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
class BuildData {
|
class BuildData {
|
||||||
static const String name = "ToolBox";
|
static const String name = "ToolBox";
|
||||||
static const int build = 43;
|
static const int build = 65;
|
||||||
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 engine =
|
||||||
static const String buildAt = "2021-10-28 21:14:38.326376";
|
"Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 77d935af4d (2 weeks ago) • 2021-12-16 08:37:33 -0800\nEngine • revision 890a5fca2e\nTools • Dart 2.15.1\n";
|
||||||
static const int modifications = 2;
|
static const String buildAt = "2021-12-31 15:55:47.350456";
|
||||||
|
static const int modifications = 7;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ class DynamicColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final mainColor = DynamicColor(Colors.black87, Colors.white70);
|
final mainColor = DynamicColor(Colors.black87, Colors.white70);
|
||||||
|
final progressColor = DynamicColor(Colors.grey.shade100, Colors.grey);
|
||||||
|
|||||||
3
lib/data/res/icon/common.dart
Normal file
3
lib/data/res/icon/common.dart
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
final appIcon = Image.asset('assets/app_icon.png');
|
||||||
4
lib/data/res/icon/linux_icons.dart
Normal file
4
lib/data/res/icon/linux_icons.dart
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import 'package:toolbox/data/model/server/linux_icon.dart';
|
||||||
|
|
||||||
|
final linuxIcons = LinuxIcons(
|
||||||
|
['ubuntu', 'arch', 'centos', 'debian', 'fedora', 'opensuse', 'kali']);
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import 'package:toolbox/data/model/server/linux_icon.dart';
|
|
||||||
|
|
||||||
final linuxIcons = LinuxIcons(['ubuntu', 'arch', 'centos', 'debian', 'fedora',
|
|
||||||
'opensuse', 'kali']);
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
const backendUrl = 'https://v2.custed.lolli.tech';
|
const backendUrl = 'https://v2.custed.lolli.tech';
|
||||||
const baseUrl = backendUrl + '/res/toolbox';
|
const baseUrl = backendUrl + '/res/toolbox';
|
||||||
const joinQQGroupUrl = 'https://jq.qq.com/?_wv=1027&k=G0hUmPAq';
|
const joinQQGroupUrl = 'https://jq.qq.com/?_wv=1027&k=G0hUmPAq';
|
||||||
|
const myGithub = 'https://github.com/LollipopKit';
|
||||||
|
const rainSunMeGithub = 'https://github.com/RainSunMe';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class PrivateKeyStore extends PersistentStore {
|
|||||||
|
|
||||||
List<PrivateKeyInfo> fetch() {
|
List<PrivateKeyInfo> fetch() {
|
||||||
return getPrivateKeyInfoList(
|
return getPrivateKeyInfoList(
|
||||||
json.decode(box.get('key', defaultValue: '[]')!))!;
|
json.decode(box.get('key', defaultValue: '[]')!));
|
||||||
}
|
}
|
||||||
|
|
||||||
void delete(PrivateKeyInfo s) {
|
void delete(PrivateKeyInfo s) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class ServerStore extends PersistentStore {
|
|||||||
|
|
||||||
List<ServerPrivateInfo> fetch() {
|
List<ServerPrivateInfo> fetch() {
|
||||||
return getServerInfoList(
|
return getServerInfoList(
|
||||||
json.decode(box.get('servers', defaultValue: '[]')!))!;
|
json.decode(box.get('servers', defaultValue: '[]')!));
|
||||||
}
|
}
|
||||||
|
|
||||||
void delete(ServerPrivateInfo s) {
|
void delete(ServerPrivateInfo s) {
|
||||||
|
|||||||
32
lib/data/store/snippet.dart
Normal file
32
lib/data/store/snippet.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:toolbox/core/persistant_store.dart';
|
||||||
|
import 'package:toolbox/data/model/server/snippet.dart';
|
||||||
|
|
||||||
|
class SnippetStore extends PersistentStore {
|
||||||
|
void put(Snippet snippet) {
|
||||||
|
final ss = fetch();
|
||||||
|
if (!have(snippet)) ss.add(snippet);
|
||||||
|
box.put('snippet', json.encode(ss));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Snippet> fetch() {
|
||||||
|
return getSnippetList(json.decode(box.get('snippet', defaultValue: '[]')!));
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete(Snippet s) {
|
||||||
|
final ss = fetch();
|
||||||
|
ss.removeAt(index(s));
|
||||||
|
box.put('snippet', json.encode(ss));
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Snippet old, Snippet newInfo) {
|
||||||
|
final ss = fetch();
|
||||||
|
ss[index(old)] = newInfo;
|
||||||
|
box.put('snippet', json.encode(ss));
|
||||||
|
}
|
||||||
|
|
||||||
|
int index(Snippet s) => fetch().indexWhere((e) => e.name == s.name);
|
||||||
|
|
||||||
|
bool have(Snippet s) => index(s) != -1;
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ import 'package:toolbox/data/provider/app.dart';
|
|||||||
import 'package:toolbox/data/provider/debug.dart';
|
import 'package:toolbox/data/provider/debug.dart';
|
||||||
import 'package:toolbox/data/provider/private_key.dart';
|
import 'package:toolbox/data/provider/private_key.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
|
import 'package:toolbox/data/provider/snippet.dart';
|
||||||
import 'package:toolbox/data/service/app.dart';
|
import 'package:toolbox/data/service/app.dart';
|
||||||
import 'package:toolbox/data/store/private_key.dart';
|
import 'package:toolbox/data/store/private_key.dart';
|
||||||
import 'package:toolbox/data/store/server.dart';
|
import 'package:toolbox/data/store/server.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
|
import 'package:toolbox/data/store/snippet.dart';
|
||||||
|
|
||||||
GetIt locator = GetIt.instance;
|
GetIt locator = GetIt.instance;
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ void setupLocatorForProviders() {
|
|||||||
locator.registerSingleton(AppProvider());
|
locator.registerSingleton(AppProvider());
|
||||||
locator.registerSingleton(DebugProvider());
|
locator.registerSingleton(DebugProvider());
|
||||||
locator.registerSingleton(ServerProvider());
|
locator.registerSingleton(ServerProvider());
|
||||||
|
locator.registerSingleton(SnippetProvider());
|
||||||
locator.registerSingleton(PrivateKeyProvider());
|
locator.registerSingleton(PrivateKeyProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +36,10 @@ Future<void> setupLocatorForStores() async {
|
|||||||
final key = PrivateKeyStore();
|
final key = PrivateKeyStore();
|
||||||
await key.init(boxName: 'key');
|
await key.init(boxName: 'key');
|
||||||
locator.registerSingleton(key);
|
locator.registerSingleton(key);
|
||||||
|
|
||||||
|
final snippet = SnippetStore();
|
||||||
|
await snippet.init(boxName: 'snippet');
|
||||||
|
locator.registerSingleton(snippet);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setupLocator() async {
|
Future<void> setupLocator() async {
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import 'package:toolbox/data/provider/app.dart';
|
|||||||
import 'package:toolbox/data/provider/debug.dart';
|
import 'package:toolbox/data/provider/debug.dart';
|
||||||
import 'package:toolbox/data/provider/private_key.dart';
|
import 'package:toolbox/data/provider/private_key.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
|
import 'package:toolbox/data/provider/snippet.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
await setupLocator();
|
await setupLocator();
|
||||||
|
locator<SnippetProvider>().loadData();
|
||||||
locator<PrivateKeyProvider>().loadData();
|
locator<PrivateKeyProvider>().loadData();
|
||||||
|
|
||||||
///设置Logger
|
///设置Logger
|
||||||
@@ -62,6 +64,7 @@ Future<void> main() async {
|
|||||||
ChangeNotifierProvider(create: (_) => locator<AppProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<AppProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<DebugProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<DebugProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<ServerProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<ServerProvider>()),
|
||||||
|
ChangeNotifierProvider(create: (_) => locator<SnippetProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
||||||
],
|
],
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import 'package:after_layout/after_layout.dart';
|
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/analysis.dart';
|
||||||
|
import 'package:toolbox/core/build_mode.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
import 'package:toolbox/core/update.dart';
|
import 'package:toolbox/core/update.dart';
|
||||||
import 'package:toolbox/core/utils.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/data/res/icon/common.dart';
|
||||||
|
import 'package:toolbox/data/res/url.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
import 'package:toolbox/view/page/convert.dart';
|
import 'package:toolbox/view/page/convert.dart';
|
||||||
import 'package:toolbox/view/page/debug.dart';
|
import 'package:toolbox/view/page/debug.dart';
|
||||||
import 'package:toolbox/view/page/private_key/stored.dart';
|
import 'package:toolbox/view/page/private_key/list.dart';
|
||||||
import 'package:toolbox/view/page/server/tab.dart';
|
import 'package:toolbox/view/page/server/tab.dart';
|
||||||
import 'package:toolbox/view/page/setting.dart';
|
import 'package:toolbox/view/page/setting.dart';
|
||||||
|
import 'package:toolbox/view/page/snippet/list.dart';
|
||||||
import 'package:toolbox/view/widget/url_text.dart';
|
import 'package:toolbox/view/widget/url_text.dart';
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
@@ -67,7 +72,7 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
children: [
|
||||||
UserAccountsDrawerHeader(
|
UserAccountsDrawerHeader(
|
||||||
accountName: const Text('ToolBox'),
|
accountName: const Text(BuildData.name),
|
||||||
accountEmail: Text(_buildVersionStr()),
|
accountEmail: Text(_buildVersionStr()),
|
||||||
currentAccountPicture: _buildIcon(),
|
currentAccountPicture: _buildIcon(),
|
||||||
),
|
),
|
||||||
@@ -80,7 +85,14 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
leading: const Icon(Icons.vpn_key),
|
leading: const Icon(Icons.vpn_key),
|
||||||
title: const Text('Private Key'),
|
title: const Text('Private Key'),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
AppRoute(const StoredPrivateKeysPage(), 'Setting').go(context),
|
AppRoute(const StoredPrivateKeysPage(), 'private key list')
|
||||||
|
.go(context),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.snippet_folder),
|
||||||
|
title: const Text('Snippet'),
|
||||||
|
onTap: () =>
|
||||||
|
AppRoute(const SnippetListPage(), 'snippet list').go(context),
|
||||||
),
|
),
|
||||||
AboutListTile(
|
AboutListTile(
|
||||||
icon: const Icon(Icons.text_snippet),
|
icon: const Icon(Icons.text_snippet),
|
||||||
@@ -90,8 +102,12 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
applicationIcon: _buildIcon(),
|
applicationIcon: _buildIcon(),
|
||||||
aboutBoxChildren: const [
|
aboutBoxChildren: const [
|
||||||
UrlText(
|
UrlText(
|
||||||
text: '''\nMade with ❤️ by https://github.com/LollipopKit .
|
text: '\nMade with ❤️ by $myGithub', replace: 'LollipopKit'),
|
||||||
\nAll rights reserved.''', replace: 'LollipopKit'),
|
UrlText(
|
||||||
|
text:
|
||||||
|
'\nThanks $rainSunMeGithub for participating in the test.\n\nAll rights reserved.',
|
||||||
|
replace: 'RainSunMe',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -102,7 +118,7 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
Widget _buildIcon() {
|
Widget _buildIcon() {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxHeight: 60, maxWidth: 60),
|
constraints: const BoxConstraints(maxHeight: 60, maxWidth: 60),
|
||||||
child: Image.asset('assets/app_icon.png'),
|
child: appIcon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,5 +134,8 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
await GetIt.I.allReady();
|
await GetIt.I.allReady();
|
||||||
await locator<ServerProvider>().loadLocalData();
|
await locator<ServerProvider>().loadLocalData();
|
||||||
await doUpdate(context);
|
await doUpdate(context);
|
||||||
|
if (BuildMode.isRelease) {
|
||||||
|
await Analysis.init(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:after_layout/after_layout.dart';
|
import 'package:after_layout/after_layout.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/core/utils.dart';
|
||||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||||
import 'package:toolbox/data/provider/private_key.dart';
|
import 'package:toolbox/data/provider/private_key.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
@@ -70,8 +71,15 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
|
|||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Icons.send),
|
child: const Icon(Icons.send),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final info = PrivateKeyInfo(
|
final name = nameController.text;
|
||||||
nameController.text, keyController.text, pwdController.text);
|
final key = keyController.text;
|
||||||
|
final pwd = pwdController.text;
|
||||||
|
if (name.isEmpty || key.isEmpty || pwd.isEmpty) {
|
||||||
|
showSnackBar(
|
||||||
|
context, const Text('Three fields must not be empty.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final info = PrivateKeyInfo(name, key, pwd);
|
||||||
if (widget.info != null) {
|
if (widget.info != null) {
|
||||||
_provider.updateInfo(widget.info!, info);
|
_provider.updateInfo(widget.info!, info);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:toolbox/core/route.dart';
|
||||||
|
import 'package:toolbox/data/model/server/net_speed.dart';
|
||||||
import 'package:toolbox/data/model/server/server.dart';
|
import 'package:toolbox/data/model/server/server.dart';
|
||||||
import 'package:toolbox/data/model/server/server_status.dart';
|
import 'package:toolbox/data/model/server/server_status.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
import 'package:toolbox/data/res/color.dart';
|
import 'package:toolbox/data/res/color.dart';
|
||||||
import 'package:toolbox/data/res/linux_icons.dart';
|
import 'package:toolbox/data/res/icon/linux_icons.dart';
|
||||||
|
import 'package:toolbox/view/page/server/edit.dart';
|
||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
|
const style11 = TextStyle(fontSize: 11);
|
||||||
|
const style13 = TextStyle(fontSize: 13);
|
||||||
|
|
||||||
class ServerDetailPage extends StatefulWidget {
|
class ServerDetailPage extends StatefulWidget {
|
||||||
const ServerDetailPage(this.id, {Key? key}) : super(key: key);
|
const ServerDetailPage(this.id, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -37,7 +43,17 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
Widget _buildMainPage(ServerInfo si) {
|
Widget _buildMainPage(ServerInfo si) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(si.info.name ?? 'Server Detail'),
|
title: Text(si.info.name),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => AppRoute(
|
||||||
|
ServerEditPage(
|
||||||
|
spi: si.info,
|
||||||
|
),
|
||||||
|
'Edit server info page')
|
||||||
|
.go(context),
|
||||||
|
icon: const Icon(Icons.edit))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.all(17),
|
padding: const EdgeInsets.all(17),
|
||||||
@@ -47,7 +63,8 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
_buildUpTimeAndSys(si.status),
|
_buildUpTimeAndSys(si.status),
|
||||||
_buildCPUView(si.status),
|
_buildCPUView(si.status),
|
||||||
_buildDiskView(si.status),
|
_buildDiskView(si.status),
|
||||||
_buildMemView(si.status)
|
_buildMemView(si.status),
|
||||||
|
_buildNetView(si.status.netSpeed)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -56,7 +73,8 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
Widget _buildLinuxIcon(String sysVer) {
|
Widget _buildLinuxIcon(String sysVer) {
|
||||||
final iconPath = linuxIcons.search(sysVer);
|
final iconPath = linuxIcons.search(sysVer);
|
||||||
if (iconPath == null) return const SizedBox();
|
if (iconPath == null) return const SizedBox();
|
||||||
return SizedBox(height: _media.size.height * 0.15, child: Image.asset(iconPath));
|
return SizedBox(
|
||||||
|
height: _media.size.height * 0.15, child: Image.asset(iconPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCPUView(ServerStatus ss) {
|
Widget _buildCPUView(ServerStatus ss) {
|
||||||
@@ -144,7 +162,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
return LinearProgressIndicator(
|
return LinearProgressIndicator(
|
||||||
value: percentWithinOne,
|
value: percentWithinOne,
|
||||||
minHeight: 7,
|
minHeight: 7,
|
||||||
backgroundColor: Colors.grey[100],
|
backgroundColor: progressColor.resolve(context),
|
||||||
color: pColor.withOpacity(0.5 + percentWithinOne / 2),
|
color: pColor.withOpacity(0.5 + percentWithinOne / 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -155,16 +173,30 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(ss.sysVer),
|
Text(ss.sysVer, style: style11, textScaleFactor: 1.0),
|
||||||
Text(ss.uptime),
|
Text(
|
||||||
|
ss.uptime,
|
||||||
|
style: style11,
|
||||||
|
textScaleFactor: 1.0,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String convertMB(int mb) {
|
||||||
|
const suffix = ['MB', 'GB', 'TB'];
|
||||||
|
double value = mb.toDouble();
|
||||||
|
int squareTimes = 0;
|
||||||
|
for (; value / 1024 > 1 && squareTimes < 3; squareTimes++) {
|
||||||
|
value /= 1024;
|
||||||
|
}
|
||||||
|
return '${value.toStringAsFixed(1)} ${suffix[squareTimes]}';
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildMemView(ServerStatus ss) {
|
Widget _buildMemView(ServerStatus ss) {
|
||||||
final pColor = primaryColor;
|
final pColor = primaryColor;
|
||||||
final used = ss.memList[1] / ss.memList[0];
|
final used = ss.memory.used / ss.memory.total;
|
||||||
final width = _media.size.width - 17 * 2 - 17 * 2;
|
final width = _media.size.width - 17 * 2 - 17 * 2;
|
||||||
return RoundRectCard(SizedBox(
|
return RoundRectCard(SizedBox(
|
||||||
height: 47,
|
height: 47,
|
||||||
@@ -175,9 +207,11 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildMemExplain('Used', pColor),
|
_buildMemExplain(convertMB(ss.memory.used), pColor),
|
||||||
_buildMemExplain('Cache', pColor.withAlpha(77)),
|
_buildMemExplain(
|
||||||
_buildMemExplain('Avail', Colors.grey.shade100)
|
convertMB(ss.memory.cache), pColor.withAlpha(77)),
|
||||||
|
_buildMemExplain(
|
||||||
|
convertMB(ss.memory.total - ss.memory.avail), progressColor.resolve(context))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -194,8 +228,11 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: width * (1 - used),
|
width: width * (1 - used),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: ss.memList[4] / ss.memList[0],
|
// memory.total == 1: failed to get mem, now mem = [emptyMemory] which is initial value.
|
||||||
backgroundColor: Colors.grey[100],
|
value: ss.memory.total == 1
|
||||||
|
? 0
|
||||||
|
: ss.memory.cache / ss.memory.total,
|
||||||
|
backgroundColor: progressColor.resolve(context),
|
||||||
color: pColor.withAlpha(77),
|
color: pColor.withAlpha(77),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -215,7 +252,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
width: 11,
|
width: 11,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(type, style: const TextStyle(fontSize: 10), textScaleFactor: 1.0)
|
Text(type, style: style11, textScaleFactor: 1.0)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -244,12 +281,10 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${disk.usedPercent}% of ${disk.size}',
|
'${disk.usedPercent}% of ${disk.size}',
|
||||||
style: const TextStyle(fontSize: 11),
|
style: style11,
|
||||||
textScaleFactor: 1.0,
|
textScaleFactor: 1.0,
|
||||||
),
|
),
|
||||||
Text(disk.mountPath,
|
Text(disk.mountPath, style: style11, textScaleFactor: 1.0)
|
||||||
style: const TextStyle(fontSize: 11),
|
|
||||||
textScaleFactor: 1.0)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_buildProgress(disk.usedPercent.toDouble())
|
_buildProgress(disk.usedPercent.toDouble())
|
||||||
@@ -260,5 +295,72 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const ignorePath = ['/run', '/sys', '/dev/shm', '/snap', '/var/lib/docker'];
|
Widget _buildNetView(NetSpeed ns) {
|
||||||
|
final children = <Widget>[
|
||||||
|
_buildNetSpeedTop(),
|
||||||
|
const Divider(
|
||||||
|
height: 7,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
children.addAll(ns.devices.map((e) => _buildNetSpeedItem(ns, e)));
|
||||||
|
return RoundRectCard(Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 7),
|
||||||
|
child: Column(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNetSpeedTop() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: const [
|
||||||
|
Icon(
|
||||||
|
Icons.device_hub,
|
||||||
|
size: 17,
|
||||||
|
),
|
||||||
|
Icon(Icons.arrow_upward, size: 17),
|
||||||
|
Icon(Icons.arrow_downward, size: 17)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNetSpeedItem(NetSpeed ns, String device) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: _media.size.width / 4,
|
||||||
|
child: Text(device, style: style11, textScaleFactor: 1.0)),
|
||||||
|
SizedBox(
|
||||||
|
width: _media.size.width / 4,
|
||||||
|
child: Text(ns.speedIn(device: device),
|
||||||
|
style: style11,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textScaleFactor: 1.0),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: _media.size.width / 4,
|
||||||
|
child: Text(ns.speedOut(device: device),
|
||||||
|
style: style11,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
textScaleFactor: 1.0))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ignorePath = [
|
||||||
|
'/run',
|
||||||
|
'/sys',
|
||||||
|
'/dev/shm',
|
||||||
|
'/snap',
|
||||||
|
'/var/lib/docker',
|
||||||
|
'/dev/tty'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
import 'package:toolbox/core/utils.dart';
|
import 'package:toolbox/core/utils.dart';
|
||||||
|
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/data/provider/private_key.dart';
|
import 'package:toolbox/data/provider/private_key.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
@@ -32,8 +33,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
|
|
||||||
bool usePublicKey = false;
|
bool usePublicKey = false;
|
||||||
|
|
||||||
int _typeOptionIndex = -1;
|
int _pubKeyIndex = -1;
|
||||||
final List<String> _keyInfo = ['', ''];
|
late PrivateKeyInfo _keyInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -47,9 +48,27 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
appBar: AppBar(title: const Text('Edit'), actions: [
|
appBar: AppBar(title: const Text('Edit'), actions: [
|
||||||
widget.spi != null
|
widget.spi != null
|
||||||
? IconButton(
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showRoundDialog(
|
||||||
|
context,
|
||||||
|
'Attention',
|
||||||
|
Text(
|
||||||
|
'Are you sure to delete server [${widget.spi!.name}]'),
|
||||||
|
[
|
||||||
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_serverProvider.delServer(widget.spi!);
|
_serverProvider.delServer(widget.spi!);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'Yes',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('No'))
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.delete))
|
icon: const Icon(Icons.delete))
|
||||||
: const SizedBox()
|
: const SizedBox()
|
||||||
@@ -109,13 +128,17 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
usePublicKey
|
usePublicKey
|
||||||
? Consumer<PrivateKeyProvider>(builder: (_, key, __) {
|
? Consumer<PrivateKeyProvider>(builder: (_, key, __) {
|
||||||
|
for (var item in key.infos) {
|
||||||
|
if (item.id == widget.spi?.pubKeyId) {
|
||||||
|
_pubKeyIndex = key.infos.indexOf(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
final tiles = key.infos
|
final tiles = key.infos
|
||||||
.map(
|
.map(
|
||||||
(e) => ListTile(
|
(e) => ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: Text(e.id, textAlign: TextAlign.start),
|
title: Text(e.id, textAlign: TextAlign.start),
|
||||||
trailing: _buildRadio(key.infos.indexOf(e),
|
trailing: _buildRadio(key.infos.indexOf(e), e)),
|
||||||
e.privateKey, e.password)),
|
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
tiles.add(ListTile(
|
tiles.add(ListTile(
|
||||||
@@ -155,7 +178,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
showSnackBar(context, const Text('Please enter password.'));
|
showSnackBar(context, const Text('Please enter password.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (usePublicKey && _typeOptionIndex == -1) {
|
if (usePublicKey && _pubKeyIndex == -1) {
|
||||||
showSnackBar(context, const Text('Please select a private key.'));
|
showSnackBar(context, const Text('Please select a private key.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -166,14 +189,18 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
portController.text = '22';
|
portController.text = '22';
|
||||||
}
|
}
|
||||||
final authorization = usePublicKey
|
final authorization = usePublicKey
|
||||||
? {"privateKey": _keyInfo[0], "passphrase": _keyInfo[1]}
|
? {
|
||||||
|
"privateKey": _keyInfo.privateKey,
|
||||||
|
"passphrase": _keyInfo.password
|
||||||
|
}
|
||||||
: passwordController.text;
|
: passwordController.text;
|
||||||
final spi = ServerPrivateInfo(
|
final spi = ServerPrivateInfo(
|
||||||
name: nameController.text,
|
name: nameController.text,
|
||||||
ip: ipController.text,
|
ip: ipController.text,
|
||||||
port: int.parse(portController.text),
|
port: int.parse(portController.text),
|
||||||
user: usernameController.text,
|
user: usernameController.text,
|
||||||
authorization: authorization);
|
authorization: authorization,
|
||||||
|
pubKeyId: usePublicKey ? _keyInfo.id : null);
|
||||||
|
|
||||||
if (widget.spi == null) {
|
if (widget.spi == null) {
|
||||||
_serverProvider.addServer(spi);
|
_serverProvider.addServer(spi);
|
||||||
@@ -187,15 +214,14 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Radio _buildRadio(int index, String key, String pwd) {
|
Radio _buildRadio(int index, PrivateKeyInfo pki) {
|
||||||
return Radio<int>(
|
return Radio<int>(
|
||||||
value: index,
|
value: index,
|
||||||
groupValue: _typeOptionIndex,
|
groupValue: _pubKeyIndex,
|
||||||
onChanged: (int? value) {
|
onChanged: (int? value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_typeOptionIndex = value!;
|
_pubKeyIndex = value!;
|
||||||
_keyInfo[0] = key;
|
_keyInfo = pki;
|
||||||
_keyInfo[1] = pwd;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -214,10 +240,9 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
final auth = widget.spi?.authorization as Map;
|
final auth = widget.spi?.authorization as Map;
|
||||||
passwordController.text = auth['passphrase'];
|
passwordController.text = auth['passphrase'];
|
||||||
keyController.text = auth['privateKey'];
|
keyController.text = auth['privateKey'];
|
||||||
setState(() {
|
|
||||||
usePublicKey = true;
|
usePublicKey = true;
|
||||||
});
|
}
|
||||||
}
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import 'package:after_layout/after_layout.dart';
|
import 'package:after_layout/after_layout.dart';
|
||||||
import 'package:circle_chart/circle_chart.dart';
|
import 'package:circle_chart/circle_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
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:marquee/marquee.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
import 'package:toolbox/data/model/server/server.dart';
|
import 'package:toolbox/data/model/server/server.dart';
|
||||||
import 'package:toolbox/data/model/server/server_connection_state.dart';
|
import 'package:toolbox/data/model/server/server_connection_state.dart';
|
||||||
import 'package:toolbox/data/model/server/server_status.dart';
|
import 'package:toolbox/data/model/server/server_status.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
|
import 'package:toolbox/data/res/color.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
import 'package:toolbox/view/page/server/detail.dart';
|
import 'package:toolbox/view/page/server/detail.dart';
|
||||||
@@ -27,6 +29,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
late MediaQueryData _media;
|
late MediaQueryData _media;
|
||||||
late ThemeData _theme;
|
late ThemeData _theme;
|
||||||
late Color _primaryColor;
|
late Color _primaryColor;
|
||||||
|
late RefreshController _refreshController;
|
||||||
|
|
||||||
late ServerProvider _serverProvider;
|
late ServerProvider _serverProvider;
|
||||||
|
|
||||||
@@ -34,6 +37,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_serverProvider = locator<ServerProvider>();
|
_serverProvider = locator<ServerProvider>();
|
||||||
|
_refreshController = RefreshController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,22 +45,31 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
_media = MediaQuery.of(context);
|
_media = MediaQuery.of(context);
|
||||||
_theme = Theme.of(context);
|
_theme = Theme.of(context);
|
||||||
_primaryColor = Color(locator<SettingStore>().primaryColor.fetch()!);
|
_primaryColor = primaryColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return Scaffold(
|
final autoUpdate =
|
||||||
body: SingleChildScrollView(
|
locator<SettingStore>().serverStatusUpdateInterval.fetch() != 0;
|
||||||
|
final child = Consumer<ServerProvider>(builder: (_, pro, __) {
|
||||||
|
if (pro.servers.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text(
|
||||||
|
'There is no server.\nClick the fab to add one.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||||
child: AnimationLimiter(
|
child: AnimationLimiter(
|
||||||
child: Consumer<ServerProvider>(builder: (_, pro, __) {
|
child: Column(
|
||||||
return Column(
|
|
||||||
children: AnimationConfiguration.toStaggeredList(
|
children: AnimationConfiguration.toStaggeredList(
|
||||||
duration: const Duration(milliseconds: 377),
|
duration: const Duration(milliseconds: 777),
|
||||||
childAnimationBuilder: (widget) => SlideAnimation(
|
childAnimationBuilder: (widget) => SlideAnimation(
|
||||||
verticalOffset: 50.0,
|
verticalOffset: 77.0,
|
||||||
child: FadeInAnimation(
|
child: FadeInAnimation(
|
||||||
child: widget,
|
child: widget,
|
||||||
),
|
),
|
||||||
@@ -65,8 +78,19 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
const SizedBox(height: 13),
|
const SizedBox(height: 13),
|
||||||
...pro.servers.map((e) => _buildEachServerCard(e))
|
...pro.servers.map((e) => _buildEachServerCard(e))
|
||||||
],
|
],
|
||||||
));
|
))),
|
||||||
})),
|
);
|
||||||
|
});
|
||||||
|
return Scaffold(
|
||||||
|
body: autoUpdate
|
||||||
|
? child
|
||||||
|
: SmartRefresher(
|
||||||
|
controller: _refreshController,
|
||||||
|
child: child,
|
||||||
|
onRefresh: () async {
|
||||||
|
await _serverProvider.refreshData();
|
||||||
|
_refreshController.refreshCompleted();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
@@ -80,25 +104,18 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEachServerCard(ServerInfo si) {
|
Widget _buildEachServerCard(ServerInfo si) {
|
||||||
return GestureDetector(
|
return Card(
|
||||||
child: _buildEachCardContent(si),
|
child: InkWell(
|
||||||
onLongPress: () {
|
onLongPress: () => AppRoute(
|
||||||
AppRoute(
|
|
||||||
ServerEditPage(
|
ServerEditPage(
|
||||||
spi: si.info,
|
spi: si.info,
|
||||||
),
|
),
|
||||||
'Edit server info page')
|
'Edit server info page')
|
||||||
.go(context);
|
.go(context),
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEachCardContent(ServerInfo si) {
|
|
||||||
return Card(
|
|
||||||
child: InkWell(
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(13),
|
padding: const EdgeInsets.all(13),
|
||||||
child: _buildRealServerCard(
|
child:
|
||||||
si.status, si.info.name ?? '', si.connectionState),
|
_buildRealServerCard(si.status, si.info.name, si.connectionState),
|
||||||
),
|
),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
AppRoute(ServerDetailPage(si.client.id!), 'server detail page')
|
AppRoute(ServerDetailPage(si.client.id!), 'server detail page')
|
||||||
@@ -112,6 +129,12 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
final rootDisk =
|
final rootDisk =
|
||||||
ss.disk.firstWhere((element) => element.mountLocation == '/');
|
ss.disk.firstWhere((element) => element.mountLocation == '/');
|
||||||
|
|
||||||
|
final topRightStr =
|
||||||
|
getTopRightStr(cs, ss.cpu2Status.temp, ss.uptime, ss.failedInfo);
|
||||||
|
final hasError = cs == ServerConnectionState.failed;
|
||||||
|
final style = TextStyle(
|
||||||
|
color: _theme.textTheme.bodyText1!.color!.withAlpha(100), fontSize: 11);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -123,11 +146,18 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
||||||
textScaleFactor: 1.0,
|
textScaleFactor: 1.0,
|
||||||
),
|
),
|
||||||
Text(getTopRightStr(cs, ss.cpu2Status.temp, ss.uptime),
|
hasError
|
||||||
textScaleFactor: 1.0,
|
? ConstrainedBox(
|
||||||
style: TextStyle(
|
constraints: BoxConstraints(
|
||||||
color: _theme.textTheme.bodyText1!.color!.withAlpha(100),
|
maxWidth: _media.size.width * 0.57, maxHeight: 17),
|
||||||
fontSize: 11))
|
child: Marquee(
|
||||||
|
accelerationDuration: const Duration(seconds: 3),
|
||||||
|
accelerationCurve: Curves.linear,
|
||||||
|
decelerationDuration: const Duration(seconds: 3),
|
||||||
|
decelerationCurve: Curves.linear,
|
||||||
|
text: topRightStr, textScaleFactor: 1.0, style: style),
|
||||||
|
)
|
||||||
|
: Text(topRightStr, style: style, textScaleFactor: 1.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -137,7 +167,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_buildPercentCircle(ss.cpu2Status.usedPercent(), 'CPU'),
|
_buildPercentCircle(ss.cpu2Status.usedPercent(), 'CPU'),
|
||||||
_buildPercentCircle(ss.memList[1] / ss.memList[0] * 100, 'Mem'),
|
_buildPercentCircle(ss.memory.used / ss.memory.total * 100, 'Mem'),
|
||||||
_buildIOData('Net', 'Conn:\n' + ss.tcp.maxConn.toString(),
|
_buildIOData('Net', 'Conn:\n' + ss.tcp.maxConn.toString(),
|
||||||
'Fail:\n' + ss.tcp.fail.toString()),
|
'Fail:\n' + ss.tcp.fail.toString()),
|
||||||
_buildIOData('Disk', 'Total:\n' + rootDisk.size,
|
_buildIOData('Disk', 'Total:\n' + rootDisk.size,
|
||||||
@@ -148,7 +178,8 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTopRightStr(ServerConnectionState cs, String temp, String upTime) {
|
String getTopRightStr(ServerConnectionState cs, String temp, String upTime,
|
||||||
|
String? failedInfo) {
|
||||||
switch (cs) {
|
switch (cs) {
|
||||||
case ServerConnectionState.disconnected:
|
case ServerConnectionState.disconnected:
|
||||||
return 'Disconnected';
|
return 'Disconnected';
|
||||||
@@ -157,7 +188,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
case ServerConnectionState.connecting:
|
case ServerConnectionState.connecting:
|
||||||
return 'Connecting...';
|
return 'Connecting...';
|
||||||
case ServerConnectionState.failed:
|
case ServerConnectionState.failed:
|
||||||
return 'Failed';
|
return failedInfo ?? 'Failed';
|
||||||
default:
|
default:
|
||||||
return 'Unknown State';
|
return 'Unknown State';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:toolbox/core/update.dart';
|
||||||
|
import 'package:toolbox/data/provider/app.dart';
|
||||||
|
import 'package:toolbox/data/res/build_data.dart';
|
||||||
import 'package:toolbox/data/res/color.dart';
|
import 'package:toolbox/data/res/color.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
@@ -17,6 +21,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
late int _selectedColorValue;
|
late int _selectedColorValue;
|
||||||
double _intervalValue = 0;
|
double _intervalValue = 0;
|
||||||
late Color priColor;
|
late Color priColor;
|
||||||
|
static const textStyle = TextStyle(fontSize: 14);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
@@ -40,15 +45,46 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.all(17),
|
padding: const EdgeInsets.all(17),
|
||||||
children: [
|
children: [
|
||||||
RoundRectCard(_buildAppColorPreview()),
|
_buildAppColorPreview(),
|
||||||
RoundRectCard(
|
_buildUpdateInterval(),
|
||||||
ExpansionTile(
|
_buildCheckUpdate()
|
||||||
|
].map((e) => RoundRectCard(e)).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCheckUpdate() {
|
||||||
|
return Consumer<AppProvider>(builder: (_, app, __) {
|
||||||
|
String display;
|
||||||
|
if (app.newestBuild != null) {
|
||||||
|
if (app.newestBuild! > BuildData.build) {
|
||||||
|
display = 'Found: v1.0.${app.newestBuild}, click to update';
|
||||||
|
} else {
|
||||||
|
display = 'Current: v1.0.${BuildData.build},is up to date';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
display = 'Current: v1.0.${BuildData.build}';
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
|
title: Text(
|
||||||
|
display,
|
||||||
|
style: textStyle,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
onTap: () => doUpdate(context, force: true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUpdateInterval() {
|
||||||
|
return ExpansionTile(
|
||||||
tilePadding: EdgeInsets.zero,
|
tilePadding: EdgeInsets.zero,
|
||||||
childrenPadding: EdgeInsets.zero,
|
childrenPadding: EdgeInsets.zero,
|
||||||
textColor: priColor,
|
textColor: priColor,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Server status update interval',
|
'Server status update interval',
|
||||||
style: TextStyle(fontSize: 14),
|
style: textStyle,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
),
|
),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
@@ -77,16 +113,16 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
height: 3,
|
height: 3,
|
||||||
),
|
),
|
||||||
_intervalValue == 0.0
|
_intervalValue == 0.0
|
||||||
? const Text('You set to 0, will not update automatically.')
|
? const Text(
|
||||||
|
'You set to 0, will not update automatically.\nYou can pull to refresh manually.',
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 13,
|
height: 13,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +144,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
),
|
),
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'App primary color',
|
'App primary color',
|
||||||
style: TextStyle(fontSize: 14),
|
style: textStyle,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
91
lib/view/page/snippet/edit.dart
Normal file
91
lib/view/page/snippet/edit.dart
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import 'package:after_layout/after_layout.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/core/utils.dart';
|
||||||
|
import 'package:toolbox/data/model/server/snippet.dart';
|
||||||
|
import 'package:toolbox/data/provider/snippet.dart';
|
||||||
|
import 'package:toolbox/locator.dart';
|
||||||
|
import 'package:toolbox/view/widget/input_decoration.dart';
|
||||||
|
|
||||||
|
class SnippetEditPage extends StatefulWidget {
|
||||||
|
const SnippetEditPage({Key? key, this.snippet}) : super(key: key);
|
||||||
|
|
||||||
|
final Snippet? snippet;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SnippetEditPageState createState() => _SnippetEditPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SnippetEditPageState extends State<SnippetEditPage>
|
||||||
|
with AfterLayoutMixin {
|
||||||
|
final nameController = TextEditingController();
|
||||||
|
final scriptController = TextEditingController();
|
||||||
|
|
||||||
|
late SnippetProvider _provider;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_provider = locator<SnippetProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Edit'), actions: [
|
||||||
|
widget.snippet != null
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_provider.delInfo(widget.snippet!);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete))
|
||||||
|
: const SizedBox()
|
||||||
|
]),
|
||||||
|
body: ListView(
|
||||||
|
padding: const EdgeInsets.all(13),
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: nameController,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
decoration: buildDecoration('Name', icon: Icons.info),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: scriptController,
|
||||||
|
autocorrect: false,
|
||||||
|
minLines: 3,
|
||||||
|
maxLines: 10,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
enableSuggestions: false,
|
||||||
|
decoration: buildDecoration('Snippet', icon: Icons.code),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(Icons.send),
|
||||||
|
onPressed: () {
|
||||||
|
final name = nameController.text;
|
||||||
|
final script = scriptController.text;
|
||||||
|
if (name.isEmpty || script.isEmpty) {
|
||||||
|
showSnackBar(context, const Text('Two fields must not be empty.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final snippet = Snippet(name, script);
|
||||||
|
if (widget.snippet != null) {
|
||||||
|
_provider.updateInfo(widget.snippet!, snippet);
|
||||||
|
} else {
|
||||||
|
_provider.addInfo(snippet);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void afterFirstLayout(BuildContext context) {
|
||||||
|
if (widget.snippet != null) {
|
||||||
|
nameController.text = widget.snippet!.name;
|
||||||
|
scriptController.text = widget.snippet!.script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
139
lib/view/page/snippet/list.dart
Normal file
139
lib/view/page/snippet/list.dart
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:toolbox/core/route.dart';
|
||||||
|
import 'package:toolbox/core/utils.dart';
|
||||||
|
import 'package:toolbox/data/model/server/snippet.dart';
|
||||||
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
|
import 'package:toolbox/data/provider/snippet.dart';
|
||||||
|
import 'package:toolbox/data/res/color.dart';
|
||||||
|
import 'package:toolbox/locator.dart';
|
||||||
|
import 'package:toolbox/view/page/snippet/edit.dart';
|
||||||
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
|
class SnippetListPage extends StatefulWidget {
|
||||||
|
const SnippetListPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SnippetListPageState createState() => _SnippetListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SnippetListPageState extends State<SnippetListPage> {
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
|
final _textStyle = TextStyle(color: primaryColor);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Snippet List'),
|
||||||
|
),
|
||||||
|
body: _buildBody(),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
onPressed: () =>
|
||||||
|
AppRoute(const SnippetEditPage(), 'snippet edit page').go(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody() {
|
||||||
|
return Consumer<SnippetProvider>(
|
||||||
|
builder: (_, key, __) {
|
||||||
|
return key.snippets.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(13),
|
||||||
|
itemCount: key.snippets.length,
|
||||||
|
itemExtent: 57,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
return RoundRectCard(Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
key.snippets[idx].name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Row(children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => AppRoute(
|
||||||
|
SnippetEditPage(snippet: key.snippets[idx]),
|
||||||
|
'snippet edit page')
|
||||||
|
.go(context),
|
||||||
|
child: Text(
|
||||||
|
'Edit',
|
||||||
|
style: _textStyle,
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _showRunDialog(key.snippets[idx]),
|
||||||
|
child: Text(
|
||||||
|
'Run',
|
||||||
|
style: _textStyle,
|
||||||
|
))
|
||||||
|
])
|
||||||
|
],
|
||||||
|
));
|
||||||
|
})
|
||||||
|
: const Center(child: Text('No saved snippets.'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showRunDialog(Snippet snippet) {
|
||||||
|
showRoundDialog(context, 'Choose destination',
|
||||||
|
Consumer<ServerProvider>(builder: (_, provider, __) {
|
||||||
|
if (provider.servers.isEmpty) {
|
||||||
|
return const Text('No server available');
|
||||||
|
}
|
||||||
|
return SizedBox(
|
||||||
|
height: 111,
|
||||||
|
child: Stack(children: [
|
||||||
|
Positioned(
|
||||||
|
child: Container(
|
||||||
|
height: 37,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(7)),
|
||||||
|
color: Colors.black12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
top: 36,
|
||||||
|
bottom: 36,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
),
|
||||||
|
ListWheelScrollView.useDelegate(
|
||||||
|
itemExtent: 37,
|
||||||
|
diameterRatio: 1.2,
|
||||||
|
controller: FixedExtentScrollController(initialItem: 0),
|
||||||
|
onSelectedItemChanged: (idx) => _selectedIndex = idx,
|
||||||
|
physics: const FixedExtentScrollPhysics(),
|
||||||
|
childDelegate: ListWheelChildBuilderDelegate(
|
||||||
|
builder: (context, index) => Center(
|
||||||
|
child: Text(
|
||||||
|
provider.servers[index].info.name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
childCount: provider.servers.length),
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
}), [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await locator<ServerProvider>()
|
||||||
|
.runSnippet(_selectedIndex, snippet);
|
||||||
|
if (result != null) {
|
||||||
|
showRoundDialog(context, 'Result',
|
||||||
|
Text(result, style: const TextStyle(fontSize: 13)), [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Close'))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Run')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,6 +96,7 @@ Future<void> flutterBuild(String source, String target, bool isAndroid) async {
|
|||||||
} else {
|
} else {
|
||||||
print(buildResult.stderr.toString());
|
print(buildResult.stderr.toString());
|
||||||
print('\nBuild failed with exit code $exitCode');
|
print('\nBuild failed with exit code $exitCode');
|
||||||
|
exit(exitCode);
|
||||||
}
|
}
|
||||||
final endTime = DateTime.now();
|
final endTime = DateTime.now();
|
||||||
print('Spent time: ${endTime.difference(startTime).toString()}');
|
print('Spent time: ${endTime.difference(startTime).toString()}');
|
||||||
|
|||||||
46
pubspec.lock
46
pubspec.lock
@@ -28,7 +28,7 @@ packages:
|
|||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.1"
|
version: "2.8.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -42,7 +42,7 @@ packages:
|
|||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -55,7 +55,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: f3e0088bb08c5d05473b1d568943f4f8732dd984
|
resolved-ref: "36a46aaa41690aac96fa808a6e75841464007a3b"
|
||||||
url: "https://github.com/LollipopKit/circle_chart"
|
url: "https://github.com/LollipopKit/circle_chart"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.3"
|
version: "0.0.3"
|
||||||
@@ -109,7 +109,7 @@ packages:
|
|||||||
name: extended_image
|
name: extended_image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "5.1.3"
|
||||||
extended_image_library:
|
extended_image_library:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -117,6 +117,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
|
fading_edge_scrollview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fading_edge_scrollview
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -163,7 +170,7 @@ packages:
|
|||||||
name: flutter_native_splash
|
name: flutter_native_splash
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.4"
|
version: "1.3.1"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -258,13 +265,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
marquee:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: marquee
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.10"
|
version: "0.12.11"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -292,7 +306,7 @@ packages:
|
|||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.6"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -363,6 +377,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.1"
|
version: "6.0.1"
|
||||||
|
pull_to_refresh:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pull_to_refresh
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
r_upgrade:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: r_upgrade
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.6"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -416,7 +444,7 @@ packages:
|
|||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.2"
|
version: "0.4.3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -486,7 +514,7 @@ packages:
|
|||||||
name: vector_math
|
name: vector_math
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ dependencies:
|
|||||||
dio: ^4.0.0
|
dio: ^4.0.0
|
||||||
after_layout: ^1.1.0
|
after_layout: ^1.1.0
|
||||||
flutter_staggered_animations: ^1.0.0
|
flutter_staggered_animations: ^1.0.0
|
||||||
extended_image: ^4.0.0
|
extended_image: ^5.1.3
|
||||||
url_launcher: ^6.0.9
|
url_launcher: ^6.0.9
|
||||||
countly_flutter:
|
countly_flutter:
|
||||||
git:
|
git:
|
||||||
@@ -51,6 +51,9 @@ dependencies:
|
|||||||
url: https://github.com/LollipopKit/circle_chart
|
url: https://github.com/LollipopKit/circle_chart
|
||||||
ref: main
|
ref: main
|
||||||
clipboard: ^0.1.3
|
clipboard: ^0.1.3
|
||||||
|
r_upgrade: ^0.3.6
|
||||||
|
pull_to_refresh: ^2.0.0
|
||||||
|
marquee: ^2.2.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 258 KiB |
BIN
screenshots/detail.jpg
Normal file
BIN
screenshots/detail.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
Reference in New Issue
Block a user