Compare commits

...

11 Commits

Author SHA1 Message Date
Junyuan Feng
1184841c24 Check drawer state when back. 2022-02-08 21:33:14 +08:00
Junyuan Feng
02a8a38a0a Update screenshots. 2022-02-08 21:21:35 +08:00
Junyuan Feng
d70cbb66d2 New UI. 2022-02-08 21:12:38 +08:00
Junyuan Feng
4636219b6a Update README 2022-02-08 19:18:21 +08:00
Junyuan Feng
b7b8d33bd9 Fix update server info failed. Display temp&uptime 2022-02-08 19:04:55 +08:00
Junyuan Feng
10c874e7d4 Migrate Flutter 2.10.0 2022-02-08 16:53:28 +08:00
Junyuan Feng
5bf5a4b67e Improve 2022-02-08 15:40:26 +08:00
Junyuan Feng
1bd43829bb Improve UI. Rounded Card. 2022-02-04 18:52:35 +08:00
Junyuan Feng
026d203608 Improve efficiency of data fetching 2022-02-02 13:23:54 +08:00
Junyuan Feng
5d9b19407f Auto unfocus to hide IME 2022-01-31 16:26:01 +08:00
Junyuan Feng
86be556a22 Improve 2022-01-19 16:54:08 +08:00
42 changed files with 769 additions and 434 deletions

View File

@@ -1,31 +1,38 @@
# Server Monitor & Toolbox # Server Monitor & Toolbox
A new Flutter project which provide a chart view to display server status data. A new Flutter project which provide a chart view to display server status data and a manager toolbox.
## ScreenShots ## ScreenShots
<table> <table>
<tr> <tr>
<td> <td>
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3327.PNG"> <img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/server.jpg">
</td> </td>
<td> <td>
<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/server_detail.jpg">
</td> </td>
<td> <td>
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/detail.jpg"> <img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/server_edit.jpg">
</td> </td>
</tr> </tr>
</table> </table>
<table> <table>
<tr> <tr>
<td> <td>
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3330.PNG"> <img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/convert.jpg">
</td> </td>
<td> <td>
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3331.PNG"> <img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/ping.jpg">
</td> </td>
<td> <td>
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/IMG_3346.PNG"> <img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/setting.jpg">
</td>
</tr>
</table>
<table>
<tr>
<td>
<img width="200px" src="https://raw.githubusercontent.com/LollipopKit/flutter_server_monitor_toolbox/main/screenshots/drawer.jpg">
</td> </td>
</tr> </tr>
</table> </table>
@@ -41,22 +48,22 @@ Support, but not tested|Windows/Linux
- [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] Server status detail page - [x] Server status detail page
- [x] Theme switch - [x] Theme switch
- [x] Execute snippet
- [x] Migrate from `ssh2` to `dartssh2` - [x] Migrate from `ssh2` to `dartssh2`
- [x] Desktop support - [x] Desktop support
- [ ] Apt manager
- [ ] SFTP
- [ ] Snippet market
- [ ] Docker manager
- [ ] SSH terminal
## Build ## Build
Please use `make.dart` to build. Please use `make.dart` to build.
```shell ```shell
# build android apk # build android apk
./make.dart build android ./make.dart build android
# due to pub package 'ssh2' incompatibility # Run in release mode
# 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 ./make.dart run release
``` ```

View File

@@ -32,7 +32,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 30 compileSdkVersion 31
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8

View File

@@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

View File

@@ -2,6 +2,8 @@ PODS:
- countly_flutter (20.11.4): - countly_flutter (20.11.4):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_icmp_ping (0.0.1):
- Flutter
- path_provider_ios (0.0.1): - path_provider_ios (0.0.1):
- Flutter - Flutter
- r_upgrade (0.0.1): - r_upgrade (0.0.1):
@@ -12,6 +14,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- countly_flutter (from `.symlinks/plugins/countly_flutter/ios`) - countly_flutter (from `.symlinks/plugins/countly_flutter/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_icmp_ping (from `.symlinks/plugins/flutter_icmp_ping/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`) - r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -21,6 +24,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/countly_flutter/ios" :path: ".symlinks/plugins/countly_flutter/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_icmp_ping:
:path: ".symlinks/plugins/flutter_icmp_ping/ios"
path_provider_ios: path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios" :path: ".symlinks/plugins/path_provider_ios/ios"
r_upgrade: r_upgrade:
@@ -31,6 +36,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
countly_flutter: 38419412e193a1faa5babeb5d28a63fda260687d countly_flutter: 38419412e193a1faa5babeb5d28a63fda260687d
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_icmp_ping: 07e508847df7fa9262d050bb0b203de074bbe517
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114 r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af

View File

@@ -354,7 +354,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 84; CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -362,7 +362,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.84; MARKETING_VERSION = 1.0.88;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -484,7 +484,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 84; CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -492,7 +492,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.84; MARKETING_VERSION = 1.0.88;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -508,7 +508,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 84; CURRENT_PROJECT_VERSION = 88;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -516,7 +516,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.84; MARKETING_VERSION = 1.0.88;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class MenuItem {
final String text;
final IconData icon;
const MenuItem({
required this.text,
required this.icon,
});
}
class MenuItems {
static const List<MenuItem> firstItems = [sftp, snippet, apt];
static const List<MenuItem> secondItems = [edit];
static const sftp = MenuItem(text: 'SFTP', icon: Icons.file_present);
static const snippet = MenuItem(text: 'Snippet', icon: Icons.label);
static const apt = MenuItem(text: 'Apt', icon: Icons.system_security_update);
static const edit = MenuItem(text: 'Edit', icon: Icons.settings);
static Widget buildItem(MenuItem item) {
return Row(
children: [
Icon(item.icon),
const SizedBox(
width: 10,
),
Text(
item.text,
),
],
);
}
}

View File

@@ -0,0 +1,8 @@
import 'package:flutter/material.dart';
class NavigationItem {
final IconData icon;
final String title;
NavigationItem(this.icon, this.title);
}

View File

@@ -85,16 +85,14 @@ class ServerProvider extends BusyProvider {
identities: await compute(loadIndentity, key.privateKey)); identities: await compute(loadIndentity, key.privateKey));
} }
Future<void> refreshData({int? idx}) async { Future<void> refreshData({ServerPrivateInfo? spi}) async {
if (idx != null) { if (spi != null) {
_getData(idx); _getData(spi);
return; return;
} }
try { try {
await Future.wait(_servers.map((s) async { await Future.wait(_servers.map((s) async {
final idx = _servers.indexOf(s); await _getData(s.info);
if (idx == -1) return;
await _getData(idx);
})); }));
} catch (e) { } catch (e) {
if (e is! RangeError) { if (e is! RangeError) {
@@ -120,11 +118,17 @@ class ServerProvider extends BusyProvider {
} }
} }
void addServer(ServerPrivateInfo info) { void setDisconnected() {
_servers.add(genInfo(info)); for (var i = 0; i < _servers.length; i++) {
locator<ServerStore>().put(info); _servers[i].connectionState = ServerConnectionState.disconnected;
}
}
void addServer(ServerPrivateInfo spi) {
_servers.add(genInfo(spi));
locator<ServerStore>().put(spi);
notifyListeners(); notifyListeners();
refreshData(idx: _servers.length - 1); refreshData(spi: spi);
} }
void delServer(ServerPrivateInfo info) { void delServer(ServerPrivateInfo info) {
@@ -136,17 +140,20 @@ class ServerProvider extends BusyProvider {
} }
Future<void> updateServer( Future<void> updateServer(
ServerPrivateInfo old, ServerPrivateInfo newInfo) async { ServerPrivateInfo old, ServerPrivateInfo newSpi) async {
final idx = _servers.indexWhere((e) => e.info == old); final idx = _servers.indexWhere((e) => e.info == old);
_servers[idx].info = newInfo; if (idx < 0) {
_servers[idx].client = await genClient(newInfo); throw RangeError.index(idx, _servers);
locator<ServerStore>().update(old, newInfo); }
_servers[idx].info = newSpi;
_servers[idx].client = await genClient(newSpi);
locator<ServerStore>().update(old, newSpi);
notifyListeners(); notifyListeners();
refreshData(idx: idx); refreshData(spi: newSpi);
} }
Future<void> _getData(int idx) async { Future<void> _getData(ServerPrivateInfo spi) async {
final info = _servers[idx].info; final idx = _servers.indexWhere((element) => element.info == spi);
final state = _servers[idx].connectionState; final state = _servers[idx].connectionState;
if (_servers[idx].client == null || if (_servers[idx].client == null ||
state == ServerConnectionState.failed || state == ServerConnectionState.failed ||
@@ -155,10 +162,10 @@ class ServerProvider extends BusyProvider {
notifyListeners(); notifyListeners();
final time1 = DateTime.now(); final time1 = DateTime.now();
try { try {
_servers[idx].client = await genClient(info); _servers[idx].client = await genClient(spi);
final time2 = DateTime.now(); final time2 = DateTime.now();
logger.info( logger.info(
'Connected to [${info.name}] in [${time2.difference(time1).toString()}].'); 'Connected to [${spi.name}] in [${time2.difference(time1).toString()}].');
_servers[idx].connectionState = ServerConnectionState.connected; _servers[idx].connectionState = ServerConnectionState.connected;
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {
@@ -168,15 +175,19 @@ class ServerProvider extends BusyProvider {
logger.warning(e); logger.warning(e);
} }
} }
final si = _servers[idx];
try { try {
if (_servers[idx].client == null) return; if (si.client == null) return;
_getCPU(_servers[idx]); final raw = utf8.decode(await si.client!.run(
_getMem(_servers[idx]); r"cat /proc/net/dev && date +%s && echo 'A====A' && cat /etc/os-release | grep PRETTY_NAME && echo 'A====A' && cat /proc/stat | grep cpu && echo 'A====A' && paste <(cat /sys/class/thermal/thermal_zone*/type) <(cat /sys/class/thermal/thermal_zone*/temp) | column -s $'\t' -t | sed 's/\(.\)..$/.\1°C/' && echo 'A====A' && uptime && echo 'A====A' && cat /proc/net/snmp && echo 'A====A' && df -h && echo 'A====A' && free -m"));
_getSysVer(_servers[idx]); final lines = raw.split('A====A').map((e) => e.trim()).toList();
_getUpTime(_servers[idx]); _getCPU(spi, lines[2], lines[3]);
_getDisk(_servers[idx]); _getMem(spi, lines[7]);
_getTcp(_servers[idx]); _getSysVer(spi, lines[1]);
_getNetSpeed(_servers[idx]); _getUpTime(spi, lines[4]);
_getDisk(spi, lines[6]);
_getTcp(spi, lines[5]);
_getNetSpeed(spi, lines[0]);
} catch (e) { } catch (e) {
_servers[idx].connectionState = ServerConnectionState.failed; _servers[idx].connectionState = ServerConnectionState.failed;
servers[idx].status.failedInfo = e.toString(); servers[idx].status.failedInfo = e.toString();
@@ -191,14 +202,12 @@ class ServerProvider extends BusyProvider {
/// lo: 45929941 269112 0 0 0 0 0 0 45929941 269112 0 0 0 0 0 0 /// 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 /// eth0: 48481023 505772 0 0 0 0 0 0 36002262 202307 0 0 0 0 0 0
/// 1635752901 /// 1635752901
Future<void> _getNetSpeed(ServerInfo info) async { void _getNetSpeed(ServerPrivateInfo spi, String raw) {
final idx = _servers.indexOf(info); final info = _servers.firstWhere((e) => e.info == spi);
final client = _servers[idx].client!;
final raw = utf8.decode(await client.run('cat /proc/net/dev && date +%s'));
final split = raw.split('\n'); final split = raw.split('\n');
final deviceCount = split.length - 3; final deviceCount = split.length - 3;
if (deviceCount < 1) return; if (deviceCount < 1) return;
final time = int.parse(split[split.length - 2]); final time = int.parse(split[split.length - 1]);
final results = <NetSpeedPart>[]; final results = <NetSpeedPart>[];
for (int idx = 2; idx < deviceCount; idx++) { for (int idx = 2; idx < deviceCount; idx++) {
final data = split[idx].trim().split(':'); final data = split[idx].trim().split(':');
@@ -213,16 +222,11 @@ class ServerProvider extends BusyProvider {
notifyListeners(); notifyListeners();
} }
Future<void> _getSysVer(ServerInfo info) async { void _getSysVer(ServerPrivateInfo spi, String raw) {
final idx = _servers.indexOf(info); final info = _servers.firstWhere((e) => e.info == spi);
final client = _servers[idx].client!;
final raw =
utf8.decode(await client.run('cat /etc/os-release | grep PRETTY_NAME'));
final s = raw.split('='); final s = raw.split('=');
if (s.length == 2) { if (s.length == 2) {
info.status.sysVer = s[1].replaceAll('"', '').replaceFirst('\n', ''); info.status.sysVer = s[1].replaceAll('"', '').replaceFirst('\n', '');
} else {
info.status.sysVer = '';
} }
notifyListeners(); notifyListeners();
@@ -238,12 +242,8 @@ class ServerProvider extends BusyProvider {
return ''; return '';
} }
Future<void> _getCPU(ServerInfo info) async { void _getCPU(ServerPrivateInfo spi, String raw, String temp) {
final idx = _servers.indexOf(info); final info = _servers.firstWhere((e) => e.info == spi);
final client = _servers[idx].client!;
final raw = utf8.decode(await client.run("cat /proc/stat | grep cpu"));
final temp = utf8.decode(await client.run(
r"paste <(cat /sys/class/thermal/thermal_zone*/type) <(cat /sys/class/thermal/thermal_zone*/temp) | column -s $'\t' -t | sed 's/\(.\)..$/.\1°C/'"));
final List<CpuStatus> cpus = []; final List<CpuStatus> cpus = [];
for (var item in raw.split('\n')) { for (var item in raw.split('\n')) {
@@ -260,9 +260,7 @@ class ServerProvider extends BusyProvider {
int.parse(matches[5]), int.parse(matches[5]),
int.parse(matches[6]))); int.parse(matches[6])));
} }
if (cpus.isEmpty) { if (cpus.isNotEmpty) {
info.status.cpu2Status = emptyCpu2Status;
} else {
info.status.cpu2Status = info.status.cpu2Status =
info.status.cpu2Status.update(cpus, _getCPUTemp(temp)); info.status.cpu2Status.update(cpus, _getCPUTemp(temp));
} }
@@ -270,36 +268,28 @@ class ServerProvider extends BusyProvider {
notifyListeners(); notifyListeners();
} }
Future<void> _getUpTime(ServerInfo info) async { void _getUpTime(ServerPrivateInfo spi, String raw) {
final idx = _servers.indexOf(info); _servers.firstWhere((e) => e.info == spi).status.uptime =
final client = _servers[idx].client!; raw.split('up ')[1].split(', ')[0];
final raw = utf8.decode(await client.run('uptime'));
info.status.uptime = raw.split('up ')[1].split(', ')[0];
notifyListeners(); notifyListeners();
} }
Future<void> _getTcp(ServerInfo info) async { void _getTcp(ServerPrivateInfo spi, String raw) {
final sidx = _servers.indexOf(info); final info = _servers.firstWhere((e) => e.info == spi);
final client = _servers[sidx].client!;
final raw = utf8.decode(await client.run('cat /proc/net/snmp'));
final lines = raw.split('\n'); final lines = raw.split('\n');
final idx = lines.lastWhere((element) => element.startsWith('Tcp:'), final idx = lines.lastWhere((element) => element.startsWith('Tcp:'),
orElse: () => ''); orElse: () => '');
if (idx != '') { if (idx != '') {
final vals = idx.split(RegExp(r'\s{1,}')); final vals = idx.split(RegExp(r'\s{1,}'));
info.status.tcp = TcpStatus(vals[5].i, vals[6].i, vals[7].i, vals[8].i); info.status.tcp = TcpStatus(vals[5].i, vals[6].i, vals[7].i, vals[8].i);
} else {
info.status.tcp = TcpStatus(0, 0, 0, 0);
} }
notifyListeners(); notifyListeners();
} }
Future<void> _getDisk(ServerInfo info) async { void _getDisk(ServerPrivateInfo spi, String raw) {
final idx = _servers.indexOf(info); final info = _servers.firstWhere((e) => e.info == spi);
final client = _servers[idx].client!;
final disk = utf8.decode(await client.run('df -h'));
final list = <DiskInfo>[]; final list = <DiskInfo>[];
final items = disk.split('\n'); final items = raw.split('\n');
for (var item in items) { for (var item in items) {
if (items.indexOf(item) == 0 || item.isEmpty) { if (items.indexOf(item) == 0 || item.isEmpty) {
continue; continue;
@@ -312,11 +302,9 @@ class ServerProvider extends BusyProvider {
notifyListeners(); notifyListeners();
} }
Future<void> _getMem(ServerInfo info) async { void _getMem(ServerPrivateInfo spi, String raw) {
final idx = _servers.indexOf(info); final info = _servers.firstWhere((e) => e.info == spi);
final client = _servers[idx].client!; for (var item in raw.split('\n')) {
final mem = utf8.decode(await client.run('free -m'));
for (var item in mem.split('\n')) {
if (item.contains('Mem:')) { if (item.contains('Mem:')) {
final split = item.replaceFirst('Mem:', '').split(' '); final split = item.replaceFirst('Mem:', '').split(' ');
split.removeWhere((e) => e == ''); split.removeWhere((e) => e == '');
@@ -333,8 +321,11 @@ class ServerProvider extends BusyProvider {
notifyListeners(); notifyListeners();
} }
Future<String?> runSnippet(int idx, Snippet snippet) async { Future<String?> runSnippet(ServerPrivateInfo spi, Snippet snippet) async {
final result = await _servers[idx].client!.run(snippet.script); final result = await _servers
.firstWhere((element) => element.info == spi)
.client!
.run(snippet.script);
return utf8.decode(result); return utf8.decode(result);
} }
} }

View File

@@ -2,9 +2,9 @@
class BuildData { class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 84; static const int build = 95;
static const String engine = static const String engine =
"Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 77d935af4d (4 weeks ago) • 2021-12-16 08:37:33 -0800\nEngine • revision 890a5fca2e\nTools • Dart 2.15.1\n"; "Flutter 2.10.0 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 5f105a6ca7 (7 days ago) • 2022-02-01 14:15:42 -0800\nEngine • revision 776efd2034\nTools • Dart 2.16.0 • DevTools 2.9.2\n";
static const String buildAt = "2022-01-16 15:21:07.640484"; static const String buildAt = "2022-02-08 21:30:51.218738";
static const int modifications = 3; static const int modifications = 3;
} }

View File

@@ -0,0 +1,3 @@
import 'package:flutter/material.dart';
const roundRectCardPadding = EdgeInsets.symmetric(horizontal: 17);

View File

@@ -1 +1,9 @@
final List<String> tabs = ['Servers', 'En/Decode', 'Ping']; import 'package:flutter/material.dart';
import 'package:toolbox/data/model/app/navigation_item.dart';
final List<String> tabs = ['Servers', 'En/Decode', 'Ping'];
final List<NavigationItem> tabItems = [
NavigationItem(Icons.computer, 'Server'),
NavigationItem(Icons.code, 'Convert'),
NavigationItem(Icons.network_check, 'Ping'),
];

View File

@@ -23,7 +23,11 @@ class ServerStore extends PersistentStore {
void update(ServerPrivateInfo old, ServerPrivateInfo newInfo) { void update(ServerPrivateInfo old, ServerPrivateInfo newInfo) {
final ss = fetch(); final ss = fetch();
ss[index(old)] = newInfo; final idx = index(old);
if (idx < 0) {
throw RangeError.index(idx, ss);
}
ss[idx] = newInfo;
box.put('servers', json.encode(ss)); box.put('servers', json.encode(ss));
} }

View File

@@ -5,7 +5,6 @@ class SettingStore extends PersistentStore {
StoreProperty<int> get primaryColor => StoreProperty<int> get primaryColor =>
property('primaryColor', defaultValue: Colors.deepPurpleAccent.value); property('primaryColor', defaultValue: Colors.deepPurpleAccent.value);
StoreProperty<int> get serverStatusUpdateInterval => StoreProperty<int> get serverStatusUpdateInterval =>
property('serverStatusUpdateInterval', defaultValue: 3); property('serverStatusUpdateInterval', defaultValue: 2);
StoreProperty<int> get launchPage => StoreProperty<int> get launchPage => property('launchPage', defaultValue: 0);
property('launchPage', defaultValue: 0);
} }

View File

@@ -4,6 +4,8 @@ import 'package:clipboard/clipboard.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/core/utils.dart'; import 'package:toolbox/core/utils.dart';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/view/widget/input_field.dart';
import 'package:toolbox/view/widget/round_rect_card.dart';
class ConvertPage extends StatefulWidget { class ConvertPage extends StatefulWidget {
const ConvertPage({Key? key}) : super(key: key); const ConvertPage({Key? key}) : super(key: key);
@@ -82,20 +84,20 @@ class _ConvertPageState extends State<ConvertPage>
case 3: case 3:
return Uri.decodeFull(text); return Uri.decodeFull(text);
default: default:
return 'Unknown'; return 'Unknown Convert Method';
} }
} }
Widget _buildInputTop() { Widget _buildInputTop() {
return SizedBox( return SizedBox(
height: _media.size.height * 0.33, height: _media.size.height * 0.33,
child: _buildInput(_textEditingController), child: buildInput(context, _textEditingController),
); );
} }
Widget _buildTypeOption() { Widget _buildTypeOption() {
return Card( return RoundRectCard(
child: ExpansionTile( ExpansionTile(
tilePadding: const EdgeInsets.only(left: 7, right: 27), tilePadding: const EdgeInsets.only(left: 7, right: 27),
childrenPadding: EdgeInsets.zero, childrenPadding: EdgeInsets.zero,
title: Row( title: Row(
@@ -161,20 +163,7 @@ class _ConvertPageState extends State<ConvertPage>
Widget _buildResult() { Widget _buildResult() {
return SizedBox( return SizedBox(
height: _media.size.height * 0.33, height: _media.size.height * 0.33,
child: _buildInput(_textEditingControllerResult), child: buildInput(context, _textEditingControllerResult),
);
}
Widget _buildInput(TextEditingController controller) {
return Card(
child: TextField(
maxLines: 20,
decoration: InputDecoration(
fillColor: Theme.of(context).cardColor,
filled: true,
border: InputBorder.none),
controller: controller,
),
); );
} }

View File

@@ -14,7 +14,7 @@ class _DebugPageState extends State<DebugPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: appBar:
AppBar(title: const Text('Terminal'), backgroundColor: Colors.black), AppBar(title: const Text('App log'), backgroundColor: Colors.black),
body: _buildTerminal(context), body: _buildTerminal(context),
backgroundColor: Colors.black, backgroundColor: Colors.black,
); );

View File

@@ -1,13 +1,16 @@
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:flutter_advanced_drawer/flutter_advanced_drawer.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/analysis.dart';
import 'package:toolbox/core/build_mode.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/model/app/navigation_item.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/color.dart';
import 'package:toolbox/data/res/icon/common.dart'; import 'package:toolbox/data/res/icon/common.dart';
import 'package:toolbox/data/res/tab.dart'; import 'package:toolbox/data/res/tab.dart';
import 'package:toolbox/data/res/url.dart'; import 'package:toolbox/data/res/url.dart';
@@ -36,15 +39,26 @@ class _MyHomePageState extends State<MyHomePage>
SingleTickerProviderStateMixin, SingleTickerProviderStateMixin,
AfterLayoutMixin, AfterLayoutMixin,
WidgetsBindingObserver { WidgetsBindingObserver {
late final TabController _tabController;
late final ServerProvider _serverProvider; late final ServerProvider _serverProvider;
late final PageController _pageController;
late final AdvancedDrawerController _advancedDrawerController;
late int _selectIndex;
late double _width;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_serverProvider = locator<ServerProvider>(); _serverProvider = locator<ServerProvider>();
WidgetsBinding.instance?.addObserver(this); WidgetsBinding.instance?.addObserver(this);
_tabController = TabController(initialIndex: locator<SettingStore>().launchPage.fetch()!, length: tabs.length, vsync: this); _selectIndex = locator<SettingStore>().launchPage.fetch()!;
_pageController = PageController(initialPage: _selectIndex);
_advancedDrawerController = AdvancedDrawerController();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_width = MediaQuery.of(context).size.width;
} }
@override @override
@@ -57,6 +71,7 @@ class _MyHomePageState extends State<MyHomePage>
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) { if (state == AppLifecycleState.paused) {
_serverProvider.setDisconnected();
_serverProvider.stopAutoRefresh(); _serverProvider.stopAutoRefresh();
} }
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
@@ -68,35 +83,135 @@ class _MyHomePageState extends State<MyHomePage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
setTransparentNavigationBar(context); setTransparentNavigationBar(context);
super.build(context); super.build(context);
return Scaffold( return WillPopScope(
appBar: AppBar( child: _buildMain(context),
title: GestureDetector( onWillPop: () {
onLongPress: () => if (_advancedDrawerController.value.visible) {
AppRoute(const DebugPage(), 'Debug Page').go(context), _advancedDrawerController.hideDrawer();
child: const Text(BuildData.name), return Future.value(false);
), }
bottom: TabBar( return Future.value(true);
indicatorColor: widget.primaryColor, });
tabs: tabs.map((e) => Tab(text: e)).toList(), }
controller: _tabController,
Widget _buildMain(BuildContext context) {
return AdvancedDrawer(
controller: _advancedDrawerController,
animationCurve: Curves.easeInOutCirc,
animationDuration: const Duration(milliseconds: 300),
animateChildDecoration: true,
rtlOpening: false,
disabledGestures: true,
childDecoration: const BoxDecoration(
// NOTICE: Uncomment if you want to add shadow behind the page.
// Keep in mind that it may cause animation jerks.
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black12,
blurRadius: 0.0,
),
],
borderRadius: BorderRadius.all(Radius.circular(16)),
), ),
drawer: _buildDrawer(),
child: Scaffold(
appBar: AppBar(
title: Text(tabItems[_selectIndex].title),
actions: [
IconButton(
icon: const Icon(Icons.developer_mode, size: 23),
tooltip: 'Debug',
onPressed: () =>
AppRoute(const DebugPage(), 'Debug Page').go(context),
),
],
leading: IconButton(
onPressed: () => _advancedDrawerController.showDrawer(),
icon: ValueListenableBuilder<AdvancedDrawerValue>(
valueListenable: _advancedDrawerController,
builder: (_, value, __) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: Icon(
value.visible ? Icons.clear : Icons.menu,
key: ValueKey<bool>(value.visible),
),
);
},
),
),
),
body: PageView(
physics: const ClampingScrollPhysics(),
controller: _pageController,
onPageChanged: (i) {
FocusScope.of(context).unfocus();
_selectIndex = i;
setState(() {});
},
children: const [ServerPage(), ConvertPage(), PingPage()],
),
bottomNavigationBar: _buildBottom(context),
));
}
Widget _buildItem(int idx, NavigationItem item, bool isSelected) {
bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
final width = _width / tabItems.length;
return AnimatedContainer(
duration: const Duration(milliseconds: 377),
curve: Curves.fastOutSlowIn,
height: 50,
width: isSelected ? width : width - 17,
decoration: BoxDecoration(
color: isSelected
? isDarkMode
? Colors.white12
: Colors.black.withOpacity(0.07)
: Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(50))),
child: IconButton(
icon: Icon(item.icon),
tooltip: item.title,
splashRadius: width / 3.3,
padding: const EdgeInsets.only(left: 17, right: 17),
onPressed: () {
setState(() {
_pageController.animateToPage(idx,
duration: const Duration(milliseconds: 677),
curve: Curves.fastLinearToSlowEaseIn);
});
},
), ),
drawer: _buildDrawer(),
body: TabBarView(
controller: _tabController,
children: const [ServerPage(), ConvertPage(), PingPage()]),
); );
} }
Widget _buildBottom(BuildContext context) {
return SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.only(left: 8, top: 4, bottom: 4, right: 8),
width: _width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: tabItems.map((item) {
int itemIndex = tabItems.indexOf(item);
return _buildItem(itemIndex, item, _selectIndex == itemIndex);
}).toList(),
),
));
}
Widget _buildDrawer() { Widget _buildDrawer() {
return Drawer( return SafeArea(
child: ListView( child: Column(
padding: EdgeInsets.zero, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
UserAccountsDrawerHeader( _buildIcon(),
accountName: const Text(BuildData.name), const Text(BuildData.name),
accountEmail: Text(_buildVersionStr()), Text(_buildVersionStr()),
currentAccountPicture: _buildIcon(), SizedBox(
height: MediaQuery.of(context).size.height * 0.07,
), ),
ListTile( ListTile(
leading: const Icon(Icons.settings), leading: const Icon(Icons.settings),
@@ -138,9 +253,19 @@ class _MyHomePageState extends State<MyHomePage>
} }
Widget _buildIcon() { Widget _buildIcon() {
return ConstrainedBox( return Stack(
constraints: const BoxConstraints(maxHeight: 60, maxWidth: 60), alignment: Alignment.center,
child: appIcon, children: [
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 53, maxWidth: 53),
child: Container(
color: primaryColor,
)),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 83, maxWidth: 83),
child: appIcon,
)
],
); );
} }

View File

@@ -5,6 +5,8 @@ import 'package:dart_ping_ios/dart_ping_ios.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/core/utils.dart'; import 'package:toolbox/core/utils.dart';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/view/widget/input_field.dart';
import 'package:toolbox/view/widget/round_rect_card.dart';
class PingPage extends StatefulWidget { class PingPage extends StatefulWidget {
const PingPage({Key? key}) : super(key: key); const PingPage({Key? key}) : super(key: key);
@@ -17,9 +19,8 @@ class _PingPageState extends State<PingPage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
late TextEditingController _textEditingController; late TextEditingController _textEditingController;
late TextEditingController _textEditingControllerResult; late TextEditingController _textEditingControllerResult;
late MediaQueryData _media;
late String _result; late String _result;
late Ping _ping; Ping? _ping;
@override @override
void initState() { void initState() {
@@ -34,7 +35,6 @@ class _PingPageState extends State<PingPage>
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_media = MediaQuery.of(context);
} }
@override @override
@@ -46,9 +46,9 @@ class _PingPageState extends State<PingPage>
padding: const EdgeInsets.symmetric(horizontal: 7), padding: const EdgeInsets.symmetric(horizontal: 7),
child: Column(children: [ child: Column(children: [
const SizedBox(height: 13), const SizedBox(height: 13),
_buildInputTop(), buildInput(context, _textEditingController, maxLines: 1),
_buildControl(), _buildControl(),
_buildResult(), buildInput(context, _textEditingControllerResult),
])), ])),
onTap: () => FocusScope.of(context).requestFocus(FocusNode()), onTap: () => FocusScope.of(context).requestFocus(FocusNode()),
), ),
@@ -58,7 +58,7 @@ class _PingPageState extends State<PingPage>
void doPing() { void doPing() {
_result = ''; _result = '';
_ping = Ping(_textEditingController.text.trim()); _ping = Ping(_textEditingController.text.trim());
_ping.stream.listen((event) { _ping!.stream.listen((event) {
final resp = event.response.toString(); final resp = event.response.toString();
if (resp == 'null') return; if (resp == 'null') return;
_result += '$resp\n'; _result += '$resp\n';
@@ -69,77 +69,55 @@ class _PingPageState extends State<PingPage>
Widget _buildControl() { Widget _buildControl() {
return SizedBox( return SizedBox(
height: 57, height: 57,
child: Card( child: RoundRectCard(
child: Row( InkWell(
mainAxisAlignment: MainAxisAlignment.spaceAround, onTap: () => FocusScope.of(context).unfocus(),
children: [ child: Row(
TextButton( mainAxisAlignment: MainAxisAlignment.spaceAround,
style: ButtonStyle( children: [
foregroundColor: MaterialStateProperty.all(primaryColor)), TextButton(
child: Row( style: ButtonStyle(
children: const [ foregroundColor: MaterialStateProperty.all(primaryColor)),
Icon(Icons.stop), child: Row(
SizedBox( children: const [
width: 7, Icon(Icons.stop),
), SizedBox(
Text('Stop') width: 7,
], ),
Text('Stop')
],
),
onPressed: () {
if (_ping != null) _ping!.stop();
},
), ),
onPressed: () { TextButton(
_ping.stop(); style: ButtonStyle(
}, foregroundColor: MaterialStateProperty.all(primaryColor)),
), child: Row(
TextButton( children: const [
style: ButtonStyle( Icon(Icons.play_arrow),
foregroundColor: MaterialStateProperty.all(primaryColor)), SizedBox(
child: Row( width: 7,
children: const [ ),
Icon(Icons.play_arrow), Text('Start')
SizedBox( ],
width: 7, ),
), onPressed: () {
Text('Start') try {
], doPing();
), } catch (e) {
onPressed: () { showSnackBar(context, Text('Error: \n$e'));
try { }
doPing(); },
} catch (e) { )
showSnackBar(context, Text('Error: \n$e')); ],
} ),
},
)
],
), ),
), ),
); );
} }
Widget _buildInputTop() {
return _buildInput(_textEditingController, maxLines: 1, hint: 'Type here.');
}
Widget _buildResult() {
return SizedBox(
height: _media.size.height * 0.47,
child: _buildInput(_textEditingControllerResult, hint: 'Result here.'),
);
}
Widget _buildInput(TextEditingController controller, {int maxLines = 20, String? hint}) {
return Card(
child: TextField(
maxLines: maxLines,
decoration: InputDecoration(
fillColor: Theme.of(context).cardColor,
hintText: hint,
filled: true,
border: InputBorder.none),
controller: controller,
),
);
}
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }

View File

@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:toolbox/core/route.dart'; import 'package:toolbox/core/route.dart';
import 'package:toolbox/data/provider/private_key.dart'; import 'package:toolbox/data/provider/private_key.dart';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/res/padding.dart';
import 'package:toolbox/view/page/private_key/edit.dart'; import 'package:toolbox/view/page/private_key/edit.dart';
import 'package:toolbox/view/widget/round_rect_card.dart'; import 'package:toolbox/view/widget/round_rect_card.dart';
@@ -29,24 +30,27 @@ class _PrivateKeyListState extends State<StoredPrivateKeysPage> {
itemCount: key.infos.length, itemCount: key.infos.length,
itemExtent: 57, itemExtent: 57,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return RoundRectCard(Row( return RoundRectCard(Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: roundRectCardPadding,
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Text( mainAxisAlignment: MainAxisAlignment.spaceBetween,
key.infos[idx].id, children: [
textAlign: TextAlign.center, Text(
), key.infos[idx].id,
TextButton( textAlign: TextAlign.center,
onPressed: () => AppRoute( ),
PrivateKeyEditPage(info: key.infos[idx]), TextButton(
'private key edit page') onPressed: () => AppRoute(
.go(context), PrivateKeyEditPage(info: key.infos[idx]),
child: Text( 'private key edit page')
'Edit', .go(context),
style: _textStyle, child: Text(
)) 'Edit',
], style: _textStyle,
))
],
),
)); ));
}) })
: const Center(child: Text('No saved private keys.')); : const Center(child: Text('No saved private keys.'));

View File

@@ -6,6 +6,7 @@ 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/icon/linux_icons.dart'; import 'package:toolbox/data/res/icon/linux_icons.dart';
import 'package:toolbox/data/res/padding.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 style11 = TextStyle(fontSize: 11);
@@ -44,7 +45,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
title: Text(si.info.name), title: Text(si.info.name),
), ),
body: ListView( body: ListView(
padding: const EdgeInsets.all(17), padding: const EdgeInsets.all(13),
children: [ children: [
_buildLinuxIcon(si.status.sysVer), _buildLinuxIcon(si.status.sysVer),
SizedBox(height: _media.size.height * 0.03), SizedBox(height: _media.size.height * 0.03),
@@ -68,41 +69,44 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Widget _buildCPUView(ServerStatus ss) { Widget _buildCPUView(ServerStatus ss) {
return RoundRectCard( return RoundRectCard(
SizedBox( Padding(
height: 12 * ss.cpu2Status.coresCount + 67, padding: roundRectCardPadding,
child: Column(children: [ child: SizedBox(
SizedBox( height: 12 * ss.cpu2Status.coresCount + 67,
height: _media.size.height * 0.02, child: Column(children: [
), SizedBox(
Row( height: _media.size.height * 0.02,
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ Row(
Text( mainAxisAlignment: MainAxisAlignment.spaceBetween,
'${ss.cpu2Status.usedPercent(coreIdx: 0).toInt()}%', children: [
style: const TextStyle(fontSize: 27), Text(
textScaleFactor: 1.0, '${ss.cpu2Status.usedPercent(coreIdx: 0).toInt()}%',
), style: const TextStyle(fontSize: 27),
Row( textScaleFactor: 1.0,
children: [ ),
_buildCPUTimePercent(ss.cpu2Status.user, 'user'), Row(
SizedBox( children: [
width: _media.size.width * 0.03, _buildCPUTimePercent(ss.cpu2Status.user, 'user'),
), SizedBox(
_buildCPUTimePercent(ss.cpu2Status.sys, 'sys'), width: _media.size.width * 0.03,
SizedBox( ),
width: _media.size.width * 0.03, _buildCPUTimePercent(ss.cpu2Status.sys, 'sys'),
), SizedBox(
_buildCPUTimePercent(ss.cpu2Status.nice, 'nice'), width: _media.size.width * 0.03,
SizedBox( ),
width: _media.size.width * 0.03, _buildCPUTimePercent(ss.cpu2Status.nice, 'nice'),
), SizedBox(
_buildCPUTimePercent(ss.cpu2Status.idle, 'idle') width: _media.size.width * 0.03,
], ),
) _buildCPUTimePercent(ss.cpu2Status.idle, 'idle')
], ],
), )
_buildCPUProgress(ss) ],
]), ),
_buildCPUProgress(ss)
]),
),
), ),
); );
} }
@@ -158,7 +162,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Widget _buildUpTimeAndSys(ServerStatus ss) { Widget _buildUpTimeAndSys(ServerStatus ss) {
return RoundRectCard(Padding( return RoundRectCard(Padding(
padding: const EdgeInsets.symmetric(vertical: 13), padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 17),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -191,47 +195,50 @@ class _ServerDetailPageState extends State<ServerDetailPage>
final pColor = primaryColor; final pColor = primaryColor;
final used = ss.memory.used / ss.memory.total; 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(Padding(
height: 47, padding: roundRectCardPadding,
child: Column( child: SizedBox(
crossAxisAlignment: CrossAxisAlignment.center, height: 47,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: [ Row(
_buildMemExplain(convertMB(ss.memory.used), pColor), mainAxisAlignment: MainAxisAlignment.spaceBetween,
_buildMemExplain( children: [
convertMB(ss.memory.cache), pColor.withAlpha(77)), _buildMemExplain(convertMB(ss.memory.used), pColor),
_buildMemExplain(convertMB(ss.memory.total - ss.memory.used), _buildMemExplain(
progressColor.resolve(context)) convertMB(ss.memory.cache), pColor.withAlpha(77)),
], _buildMemExplain(convertMB(ss.memory.total - ss.memory.used),
), progressColor.resolve(context))
const SizedBox( ],
height: 7, ),
), const SizedBox(
Row( height: 7,
children: [ ),
SizedBox( Row(
width: width * used, children: [
SizedBox(
width: width * used,
child: LinearProgressIndicator(
value: 1,
color: pColor,
)),
SizedBox(
width: width * (1 - used),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: 1, // memory.total == 1: failed to get mem, now mem = [emptyMemory] which is initial value.
color: pColor, value: ss.memory.total == 1
)), ? 0
SizedBox( : ss.memory.cache / (ss.memory.total - ss.memory.used),
width: width * (1 - used), backgroundColor: progressColor.resolve(context),
child: LinearProgressIndicator( color: pColor.withAlpha(77),
// memory.total == 1: failed to get mem, now mem = [emptyMemory] which is initial value. ),
value: ss.memory.total == 1 )
? 0 ],
: ss.memory.cache / (ss.memory.total - ss.memory.used), )
backgroundColor: progressColor.resolve(context), ],
color: pColor.withAlpha(77), ),
),
)
],
)
],
), ),
)); ));
} }
@@ -265,7 +272,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
return RoundRectCard(SizedBox( return RoundRectCard(SizedBox(
height: 27 * clone.length + 25, height: 27 * clone.length + 25,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 13), padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 17),
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: clone.length, itemCount: clone.length,
itemBuilder: (_, idx) { itemBuilder: (_, idx) {
@@ -302,7 +309,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
]; ];
children.addAll(ns.devices.map((e) => _buildNetSpeedItem(ns, e))); children.addAll(ns.devices.map((e) => _buildNetSpeedItem(ns, e)));
return RoundRectCard(Padding( return RoundRectCard(Padding(
padding: const EdgeInsets.symmetric(vertical: 7), padding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17),
child: Column( child: Column(
children: children, children: children,
), ),

View File

@@ -8,6 +8,7 @@ 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';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/store/private_key.dart';
import 'package:toolbox/locator.dart'; import 'package:toolbox/locator.dart';
import 'package:toolbox/view/page/private_key/edit.dart'; import 'package:toolbox/view/page/private_key/edit.dart';
import 'package:toolbox/view/widget/input_decoration.dart'; import 'package:toolbox/view/widget/input_decoration.dart';
@@ -34,7 +35,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
bool usePublicKey = false; bool usePublicKey = false;
int _pubKeyIndex = -1; int _pubKeyIndex = -1;
late PrivateKeyInfo _keyInfo; PrivateKeyInfo? _keyInfo;
@override @override
void initState() { void initState() {
@@ -188,10 +189,15 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
if (portController.text == '') { if (portController.text == '') {
portController.text = '22'; portController.text = '22';
} }
if (widget.spi != null && widget.spi!.pubKeyId != null) {
_keyInfo ??= locator<PrivateKeyStore>().get(widget.spi!.pubKeyId!);
}
final authorization = usePublicKey final authorization = usePublicKey
? { ? {
"privateKey": _keyInfo.privateKey, "privateKey": _keyInfo!.privateKey,
"passphrase": _keyInfo.password "passphrase": _keyInfo!.password
} }
: passwordController.text; : passwordController.text;
final spi = ServerPrivateInfo( final spi = ServerPrivateInfo(
@@ -200,7 +206,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
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); pubKeyId: usePublicKey ? _keyInfo!.id : null);
if (widget.spi == null) { if (widget.spi == null) {
_serverProvider.addServer(spi); _serverProvider.addServer(spi);

View File

@@ -1,13 +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:dropdown_button2/dropdown_button2.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:marquee/marquee.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:pull_to_refresh/pull_to_refresh.dart';
import 'package:toolbox/core/route.dart'; import 'package:toolbox/core/route.dart';
import 'package:toolbox/core/utils.dart';
import 'package:toolbox/data/model/app/menu_item.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_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/provider/server.dart'; import 'package:toolbox/data/provider/server.dart';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
@@ -15,6 +19,8 @@ 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';
import 'package:toolbox/view/page/server/edit.dart'; import 'package:toolbox/view/page/server/edit.dart';
import 'package:toolbox/view/page/snippet/list.dart';
import 'package:toolbox/view/widget/round_rect_card.dart';
class ServerPage extends StatefulWidget { class ServerPage extends StatefulWidget {
const ServerPage({Key? key}) : super(key: key); const ServerPage({Key? key}) : super(key: key);
@@ -61,15 +67,19 @@ class _ServerPageState extends State<ServerPage>
), ),
); );
} }
return SingleChildScrollView( return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 7), padding: const EdgeInsets.all(7),
child: Column( itemBuilder: (ctx, idx) {
children: [ if (idx == pro.servers.length) {
const SizedBox(height: 13), return SizedBox(height: _media.padding.bottom);
...pro.servers.map((e) => _buildEachServerCard(e)), }
SizedBox(height: _media.padding.bottom), return _buildEachServerCard(pro.servers[idx]);
], },
)); itemCount: pro.servers.length + 1,
separatorBuilder: (_, __) => const SizedBox(
height: 3,
),
);
}); });
return Scaffold( return Scaffold(
body: autoUpdate body: autoUpdate
@@ -94,8 +104,8 @@ class _ServerPageState extends State<ServerPage>
} }
Widget _buildEachServerCard(ServerInfo si) { Widget _buildEachServerCard(ServerInfo si) {
return Card( return RoundRectCard(
child: InkWell( InkWell(
onLongPress: () => AppRoute( onLongPress: () => AppRoute(
ServerEditPage( ServerEditPage(
spi: si.info, spi: si.info,
@@ -105,7 +115,7 @@ class _ServerPageState extends State<ServerPage>
child: Padding( child: Padding(
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
child: _buildRealServerCard( child: _buildRealServerCard(
si.status, si.info.name, si.connectionState)), si.status, si.info.name, si.connectionState, si.info)),
onTap: () => AppRoute(ServerDetailPage('${si.info.ip}:${si.info.port}'), onTap: () => AppRoute(ServerDetailPage('${si.info.ip}:${si.info.port}'),
'server detail page') 'server detail page')
.go(context), .go(context),
@@ -113,8 +123,8 @@ class _ServerPageState extends State<ServerPage>
); );
} }
Widget _buildRealServerCard( Widget _buildRealServerCard(ServerStatus ss, String serverName,
ServerStatus ss, String serverName, ServerConnectionState cs) { ServerConnectionState cs, ServerPrivateInfo spi) {
final rootDisk = final rootDisk =
ss.disk.firstWhere((element) => element.mountLocation == '/'); ss.disk.firstWhere((element) => element.mountLocation == '/');
@@ -128,29 +138,111 @@ class _ServerPageState extends State<ServerPage>
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Padding(
mainAxisAlignment: MainAxisAlignment.spaceBetween, padding: const EdgeInsets.symmetric(horizontal: 7),
children: [ child: Row(
Text( mainAxisAlignment: MainAxisAlignment.spaceBetween,
serverName, children: [
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12), Row(
textScaleFactor: 1.0, children: [
), Text(
hasError serverName,
? ConstrainedBox( style: const TextStyle(
constraints: BoxConstraints( fontWeight: FontWeight.bold, fontSize: 12),
maxWidth: _media.size.width * 0.57, maxHeight: 15), textScaleFactor: 1.0,
child: Marquee( ),
accelerationDuration: const Duration(seconds: 3), const Icon(
accelerationCurve: Curves.linear, Icons.keyboard_arrow_right,
decelerationDuration: const Duration(seconds: 3), size: 17,
decelerationCurve: Curves.linear, color: Colors.grey,
text: topRightStr,
textScaleFactor: 1.0,
style: style),
) )
: Text(topRightStr, style: style, textScaleFactor: 1.0), ],
], ),
Row(
children: [
hasError
? ConstrainedBox(
constraints: BoxConstraints(
maxWidth: _media.size.width * 0.57,
maxHeight: 15),
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),
DropdownButtonHideUnderline(
child: DropdownButton2(
customButton: const Padding(
padding: EdgeInsets.only(left: 7),
child: Icon(
Icons.more_vert,
size: 17,
),
),
customItemsIndexes: const [3],
customItemsHeight: 8,
items: [
...MenuItems.firstItems.map(
(item) => DropdownMenuItem<MenuItem>(
value: item,
child: MenuItems.buildItem(item),
),
),
const DropdownMenuItem<Divider>(
enabled: false, child: Divider()),
...MenuItems.secondItems.map(
(item) => DropdownMenuItem<MenuItem>(
value: item,
child: MenuItems.buildItem(item),
),
),
],
onChanged: (value) {
final item = value as MenuItem;
switch (item) {
case MenuItems.apt:
case MenuItems.sftp:
showSnackBar(
context, const Text('Now is not supported'));
break;
case MenuItems.snippet:
AppRoute(
SnippetListPage(
spi: spi,
),
'snippet list')
.go(context);
break;
case MenuItems.edit:
AppRoute(
ServerEditPage(
spi: spi,
),
'Edit server info page')
.go(context);
break;
}
},
itemHeight: 37,
itemPadding: const EdgeInsets.only(left: 17, right: 17),
dropdownWidth: 160,
dropdownPadding: const EdgeInsets.symmetric(vertical: 7),
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(7),
),
dropdownElevation: 8,
offset: const Offset(0, 8),
),
),
],
)
],
),
), ),
const SizedBox( const SizedBox(
height: 17, height: 17,
@@ -198,7 +290,19 @@ class _ServerPageState extends State<ServerPage>
case ServerConnectionState.disconnected: case ServerConnectionState.disconnected:
return 'Disconnected'; return 'Disconnected';
case ServerConnectionState.connected: case ServerConnectionState.connected:
return temp == '' ? (upTime == '' ? 'Loading...' : upTime) : temp; if (temp == '') {
if (upTime == '') {
return 'Loading...';
} else {
return upTime;
}
} else {
if (upTime == '') {
return temp;
} else {
return '$temp | $upTime';
}
}
case ServerConnectionState.connecting: case ServerConnectionState.connecting:
return 'Connecting...'; return 'Connecting...';
case ServerConnectionState.failed: case ServerConnectionState.failed:

View File

@@ -6,6 +6,7 @@ import 'package:toolbox/data/provider/app.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/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/res/padding.dart';
import 'package:toolbox/data/res/tab.dart'; import 'package:toolbox/data/res/tab.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';
@@ -77,7 +78,7 @@ class _SettingPageState extends State<SettingPage> {
display = 'Current: v1.0.${BuildData.build}'; display = 'Current: v1.0.${BuildData.build}';
} }
return ListTile( return ListTile(
contentPadding: EdgeInsets.zero, contentPadding: roundRectCardPadding,
trailing: const Icon(Icons.keyboard_arrow_right), trailing: const Icon(Icons.keyboard_arrow_right),
title: Text( title: Text(
display, display,
@@ -90,8 +91,8 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildUpdateInterval() { Widget _buildUpdateInterval() {
return ExpansionTile( return ExpansionTile(
tilePadding: EdgeInsets.zero, tilePadding: roundRectCardPadding,
childrenPadding: EdgeInsets.zero, childrenPadding: roundRectCardPadding,
textColor: priColor, textColor: priColor,
title: const Text( title: const Text(
'Server status update interval', 'Server status update interval',
@@ -142,8 +143,8 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildAppColorPreview() { Widget _buildAppColorPreview() {
return ExpansionTile( return ExpansionTile(
textColor: priColor, textColor: priColor,
tilePadding: EdgeInsets.zero, tilePadding: roundRectCardPadding,
childrenPadding: EdgeInsets.zero, childrenPadding: roundRectCardPadding,
children: [ children: [
_buildAppColorPicker(priColor), _buildAppColorPicker(priColor),
_buildColorPickerConfirmBtn() _buildColorPickerConfirmBtn()
@@ -182,8 +183,9 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildLaunchPage() { Widget _buildLaunchPage() {
return ExpansionTile( return ExpansionTile(
tilePadding: EdgeInsets.zero, textColor: priColor,
childrenPadding: EdgeInsets.zero, tilePadding: roundRectCardPadding,
childrenPadding: roundRectCardPadding,
title: const Text( title: const Text(
'Launch page', 'Launch page',
style: textStyle, style: textStyle,
@@ -192,7 +194,7 @@ class _SettingPageState extends State<SettingPage> {
constraints: BoxConstraints(maxWidth: _media.size.width * 0.35), constraints: BoxConstraints(maxWidth: _media.size.width * 0.35),
child: Text( child: Text(
tabs[_launchPageIdx], tabs[_launchPageIdx],
textScaleFactor: 1.0, style: textStyle,
textAlign: TextAlign.right, textAlign: TextAlign.right,
), ),
), ),
@@ -202,6 +204,7 @@ class _SettingPageState extends State<SettingPage> {
title: Text( title: Text(
e, e,
style: TextStyle( style: TextStyle(
fontSize: 14,
color: _theme.textTheme.bodyText2!.color!.withAlpha(177)), color: _theme.textTheme.bodyText2!.color!.withAlpha(177)),
), ),
trailing: _buildRadio(tabs.indexOf(e)), trailing: _buildRadio(tabs.indexOf(e)),

View File

@@ -2,23 +2,26 @@ 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/server_private_info.dart';
import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/model/server/snippet.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/provider/snippet.dart';
import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/res/padding.dart';
import 'package:toolbox/locator.dart'; import 'package:toolbox/locator.dart';
import 'package:toolbox/view/page/snippet/edit.dart'; import 'package:toolbox/view/page/snippet/edit.dart';
import 'package:toolbox/view/widget/round_rect_card.dart'; import 'package:toolbox/view/widget/round_rect_card.dart';
class SnippetListPage extends StatefulWidget { class SnippetListPage extends StatefulWidget {
const SnippetListPage({Key? key}) : super(key: key); const SnippetListPage({Key? key, this.spi}) : super(key: key);
final ServerPrivateInfo? spi;
@override @override
_SnippetListPageState createState() => _SnippetListPageState(); _SnippetListPageState createState() => _SnippetListPageState();
} }
class _SnippetListPageState extends State<SnippetListPage> { class _SnippetListPageState extends State<SnippetListPage> {
int _selectedIndex = 0; late ServerPrivateInfo _selectedIndex;
final _textStyle = TextStyle(color: primaryColor); final _textStyle = TextStyle(color: primaryColor);
@override @override
@@ -45,32 +48,43 @@ class _SnippetListPageState extends State<SnippetListPage> {
itemCount: key.snippets.length, itemCount: key.snippets.length,
itemExtent: 57, itemExtent: 57,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return RoundRectCard(Row( return RoundRectCard(Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: roundRectCardPadding,
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Text( mainAxisAlignment: MainAxisAlignment.spaceBetween,
key.snippets[idx].name, children: [
textAlign: TextAlign.center, Text(
), key.snippets[idx].name,
Row(children: [ textAlign: TextAlign.center,
TextButton( ),
onPressed: () => AppRoute( Row(children: [
SnippetEditPage(snippet: key.snippets[idx]), TextButton(
'snippet edit page') onPressed: () => AppRoute(
.go(context), SnippetEditPage(
child: Text( snippet: key.snippets[idx]),
'Edit', 'snippet edit page')
style: _textStyle, .go(context),
)), child: Text(
TextButton( 'Edit',
onPressed: () => _showRunDialog(key.snippets[idx]), style: _textStyle,
child: Text( )),
'Run', TextButton(
style: _textStyle, onPressed: () {
)) final snippet = key.snippets[idx];
]) if (widget.spi == null) {
], _showRunDialog(snippet);
return;
}
run(context, snippet);
},
child: Text(
'Run',
style: _textStyle,
))
])
],
),
)); ));
}) })
: const Center(child: Text('No saved snippets.')); : const Center(child: Text('No saved snippets.'));
@@ -84,6 +98,7 @@ class _SnippetListPageState extends State<SnippetListPage> {
if (provider.servers.isEmpty) { if (provider.servers.isEmpty) {
return const Text('No server available'); return const Text('No server available');
} }
_selectedIndex = provider.servers.first.info;
return SizedBox( return SizedBox(
height: 111, height: 111,
child: Stack(children: [ child: Stack(children: [
@@ -104,7 +119,8 @@ class _SnippetListPageState extends State<SnippetListPage> {
itemExtent: 37, itemExtent: 37,
diameterRatio: 1.2, diameterRatio: 1.2,
controller: FixedExtentScrollController(initialItem: 0), controller: FixedExtentScrollController(initialItem: 0),
onSelectedItemChanged: (idx) => _selectedIndex = idx, onSelectedItemChanged: (idx) =>
_selectedIndex = provider.servers[idx].info,
physics: const FixedExtentScrollPhysics(), physics: const FixedExtentScrollPhysics(),
childDelegate: ListWheelChildBuilderDelegate( childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center( builder: (context, index) => Center(
@@ -118,22 +134,24 @@ class _SnippetListPageState extends State<SnippetListPage> {
])); ]));
}), [ }), [
TextButton( TextButton(
onPressed: () async { onPressed: () async => run(context, snippet),
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')), child: const Text('Run')),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel')), child: const Text('Cancel')),
]); ]);
} }
Future<void> run(BuildContext context, Snippet snippet) async {
final result = await locator<ServerProvider>()
.runSnippet(widget.spi ?? _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'))
]);
}
}
} }

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:toolbox/view/widget/round_rect_card.dart';
Widget buildInput(BuildContext context, TextEditingController controller,
{int maxLines = 20, String? hint}) {
return RoundRectCard(
TextField(
maxLines: maxLines,
decoration: InputDecoration(
fillColor: Theme.of(context).cardColor,
hintText: hint,
filled: true,
border: InputBorder.none),
controller: controller,
),
);
}

View File

@@ -8,11 +8,8 @@ class RoundRectCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
child: Padding( child: child,
padding: const EdgeInsets.symmetric(horizontal: 17), clipBehavior: Clip.antiAlias,
child: child,
),
margin: const EdgeInsets.symmetric(vertical: 7),
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(17))), borderRadius: BorderRadius.all(Radius.circular(17))),
); );

View File

@@ -67,6 +67,15 @@ Future<void> updateBuildData() async {
await writeStaicConfigFile(data, 'BuildData', path); await writeStaicConfigFile(data, 'BuildData', path);
} }
void dartFormat() {
final result = Process.runSync('dart', ['format', '.']);
print(result.stdout);
if (result.exitCode != 0) {
print(result.stderr);
exit(1);
}
}
void flutterRun(String? mode) { void flutterRun(String? mode) {
Process.start('flutter', ['run', mode == null ? '' : '--$mode'], Process.start('flutter', ['run', mode == null ? '' : '--$mode'],
mode: ProcessStartMode.inheritStdio, runInShell: true); mode: ProcessStartMode.inheritStdio, runInShell: true);
@@ -126,6 +135,7 @@ void main(List<String> args) async {
case 'build': case 'build':
if (args.length > 1) { if (args.length > 1) {
await updateBuildData(); await updateBuildData();
dartFormat();
if (args[1] == 'android' || args[1] == 'harmony') { if (args[1] == 'android' || args[1] == 'harmony') {
return flutterBuildAndroid(); return flutterBuildAndroid();
} else if (args[1] == 'ios') { } else if (args[1] == 'ios') {

View File

@@ -14,7 +14,7 @@ packages:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.8" version: "3.1.11"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -28,7 +28,7 @@ packages:
name: asn1lib name: asn1lib
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.1.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -137,7 +137,7 @@ packages:
name: dartssh2 name: dartssh2
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1-pre" version: "2.4.4"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -145,6 +145,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.4" version: "4.0.4"
dropdown_button2:
dependency: "direct main"
description:
name: dropdown_button2
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
extended_image: extended_image:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -158,7 +165,7 @@ packages:
name: extended_image_library name: extended_image_library
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.1" version: "3.1.2"
fading_edge_scrollview: fading_edge_scrollview:
dependency: transitive dependency: transitive
description: description:
@@ -192,6 +199,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_advanced_drawer:
dependency: "direct main"
description:
name: flutter_advanced_drawer
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
flutter_icmp_ping: flutter_icmp_ping:
dependency: transitive dependency: transitive
description: description:
@@ -285,7 +299,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.1"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -321,6 +335,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -369,28 +390,28 @@ packages:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.4" version: "2.1.5"
path_provider_macos: path_provider_macos:
dependency: transitive dependency: transitive
description: description:
name: path_provider_macos name: path_provider_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@@ -507,7 +528,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.3" version: "0.4.8"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -528,35 +549,35 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.17" version: "6.0.18"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.13" version: "6.0.14"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.13" version: "6.0.14"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -570,7 +591,7 @@ packages:
name: url_launcher_web name: url_launcher_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.0.6"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@@ -578,13 +599,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
validators:
dependency: transitive
description:
name: validators
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -598,14 +612,14 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.3" version: "2.3.11"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0+1"
xml: xml:
dependency: transitive dependency: transitive
description: description:

View File

@@ -56,6 +56,8 @@ dependencies:
marquee: ^2.2.0 marquee: ^2.2.0
dart_ping: ^6.1.1 dart_ping: ^6.1.1
dart_ping_ios: ^1.0.0 dart_ping_ios: ^1.0.0
dropdown_button2: ^1.1.1
flutter_advanced_drawer: ^1.3.0
dev_dependencies: dev_dependencies:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

BIN
screenshots/convert.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

BIN
screenshots/drawer.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
screenshots/ping.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
screenshots/server.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

BIN
screenshots/server_edit.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
screenshots/setting.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB