feat & fix

- support openwrt
- save server info failed if connect error
- support ssh 'none' auth
This commit is contained in:
Junyuan Feng
2022-05-29 18:34:55 +08:00
parent 3ed476275f
commit bb1bf9219c
12 changed files with 70 additions and 46 deletions

BIN
assets/linux/wrt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -2,14 +2,12 @@ class Memory {
int total; int total;
int used; int used;
int free; int free;
int shared;
int cache; int cache;
int avail; int avail;
Memory( Memory(
{required this.total, {required this.total,
required this.used, required this.used,
required this.free, required this.free,
required this.shared,
required this.cache, required this.cache,
required this.avail}); required this.avail});
} }

View File

@@ -37,13 +37,14 @@ const shellCmd = "export LANG=en_US.utf-8 \necho '$seperator' \n"
"uptime \necho $seperator \n" "uptime \necho $seperator \n"
"cat /proc/net/snmp \necho $seperator \n" "cat /proc/net/snmp \necho $seperator \n"
"df -h \necho $seperator \n" "df -h \necho $seperator \n"
"free -m \necho $seperator \n" "cat /proc/meminfo \necho $seperator \n"
"cat /sys/class/thermal/thermal_zone*/type \necho $seperator \n" "cat /sys/class/thermal/thermal_zone*/type \necho $seperator \n"
"cat /sys/class/thermal/thermal_zone*/temp"; "cat /sys/class/thermal/thermal_zone*/temp";
const shellPath = '.serverbox.sh'; const shellPath = '.serverbox.sh';
const memPrefix = 'Mem:'; const memPrefix = 'Mem:';
final cpuTempReg = RegExp('(x86_pkg_temp|cpu_thermal)'); final cpuTempReg = RegExp('(x86_pkg_temp|cpu_thermal)');
final numReg = RegExp(r'\s{1,}'); final numReg = RegExp(r'\s{1,}');
final memItemReg = RegExp(r'([A-Z].+:)\s+([0-9]+) kB');
class ServerProvider extends BusyProvider { class ServerProvider extends BusyProvider {
List<ServerInfo> _servers = []; List<ServerInfo> _servers = [];
@@ -54,7 +55,7 @@ class ServerProvider extends BusyProvider {
final logger = Logger('ServerProvider'); final logger = Logger('ServerProvider');
Memory get emptyMemory => Memory get emptyMemory =>
Memory(total: 1, used: 0, free: 1, shared: 0, cache: 0, avail: 1); Memory(total: 1, used: 0, free: 1, cache: 0, avail: 1);
NetSpeedPart get emptyNetSpeedPart => NetSpeedPart('', 0, 0, 0); NetSpeedPart get emptyNetSpeedPart => NetSpeedPart('', 0, 0, 0);
@@ -156,8 +157,8 @@ class ServerProvider extends BusyProvider {
throw RangeError.index(idx, _servers); throw RangeError.index(idx, _servers);
} }
_servers[idx].info = newSpi; _servers[idx].info = newSpi;
_servers[idx].client = await genClient(newSpi);
locator<ServerStore>().update(old, newSpi); locator<ServerStore>().update(old, newSpi);
_servers[idx].client = await genClient(newSpi);
notifyListeners(); notifyListeners();
refreshData(spi: newSpi); refreshData(spi: newSpi);
} }
@@ -194,8 +195,8 @@ class ServerProvider extends BusyProvider {
// if client is null, return // if client is null, return
if (s.client == null) return; if (s.client == null) return;
final raw = await s.client!.run("sh $shellPath").string; final raw = await s.client!.run("sh $shellPath").string;
final lines = raw.split(seperator).map((e) => e.trim()).toList(); final segments = raw.split(seperator).map((e) => e.trim()).toList();
if (raw.isEmpty || lines.length == 1) { if (raw.isEmpty || segments.length == 1) {
s.connectionState = ServerConnectionState.failed; s.connectionState = ServerConnectionState.failed;
if (s.status.failedInfo == null || s.status.failedInfo!.isEmpty) { if (s.status.failedInfo == null || s.status.failedInfo!.isEmpty) {
s.status.failedInfo = 'No data received'; s.status.failedInfo = 'No data received';
@@ -203,16 +204,16 @@ class ServerProvider extends BusyProvider {
notifyListeners(); notifyListeners();
return; return;
} }
lines.removeAt(0); segments.removeAt(0);
try { try {
_getCPU(spi, lines[2], lines[7], lines[8]); _getCPU(spi, segments[2], segments[7], segments[8]);
_getMem(spi, lines[6]); _getMem(spi, segments[6]);
_getSysVer(spi, lines[1]); _getSysVer(spi, segments[1]);
_getUpTime(spi, lines[3]); _getUpTime(spi, segments[3]);
_getDisk(spi, lines[5]); _getDisk(spi, segments[5]);
_getTcp(spi, lines[4]); _getTcp(spi, segments[4]);
_getNetSpeed(spi, lines[0]); _getNetSpeed(spi, segments[0]);
} catch (e) { } catch (e) {
s.connectionState = ServerConnectionState.failed; s.connectionState = ServerConnectionState.failed;
s.status.failedInfo = e.toString(); s.status.failedInfo = e.toString();
@@ -333,21 +334,22 @@ class ServerProvider extends BusyProvider {
void _getMem(ServerPrivateInfo spi, String raw) { void _getMem(ServerPrivateInfo spi, String raw) {
final info = _servers.firstWhere((e) => e.info == spi); final info = _servers.firstWhere((e) => e.info == spi);
for (var item in raw.split('\n')) { final items = raw.split('\n').map((e) => memItemReg.firstMatch(e)).toList();
if (item.contains(memPrefix)) { final total = int.parse(
final split = item.replaceFirst(memPrefix, '').split(' '); items.firstWhere((e) => e?.group(1) == 'MemTotal:')?.group(2) ?? '1');
split.removeWhere((e) => e == ''); final free = int.parse(
final memList = split.map((e) => int.parse(e)).toList(); items.firstWhere((e) => e?.group(1) == 'MemFree:')?.group(2) ?? '0');
final cached = int.parse(
items.firstWhere((e) => e?.group(1) == 'Cached:')?.group(2) ?? '0');
final available = int.parse(
items.firstWhere((e) => e?.group(1) == 'MemAvailable:')?.group(2) ??
'0');
info.status.memory = Memory( info.status.memory = Memory(
total: memList[0], total: total,
used: memList[1], used: total - available,
free: memList[2], free: free,
shared: memList[3], cache: cached,
cache: memList[4], avail: available);
avail: memList[5]);
break;
}
}
} }
Future<String?> runSnippet(ServerPrivateInfo spi, Snippet snippet) async { Future<String?> runSnippet(ServerPrivateInfo spi, Snippet snippet) async {

View File

@@ -1,4 +1,12 @@
import 'package:toolbox/data/model/server/linux_icon.dart'; import 'package:toolbox/data/model/server/linux_icon.dart';
final linuxIcons = LinuxIcons( final linuxIcons = LinuxIcons([
['ubuntu', 'arch', 'centos', 'debian', 'fedora', 'opensuse', 'kali']); 'ubuntu',
'arch',
'centos',
'debian',
'fedora',
'opensuse',
'kali',
'wrt'
]);

View File

@@ -164,8 +164,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Current platform does not support in app update.\nPlease build from source and install it."), "Current platform does not support in app update.\nPlease build from source and install it."),
"plzEnterHost": "plzEnterHost":
MessageLookupByLibrary.simpleMessage("Please enter host."), MessageLookupByLibrary.simpleMessage("Please enter host."),
"plzEnterPwd":
MessageLookupByLibrary.simpleMessage("Please enter password."),
"plzSelectKey": "plzSelectKey":
MessageLookupByLibrary.simpleMessage("Please select a key."), MessageLookupByLibrary.simpleMessage("Please select a key."),
"port": MessageLookupByLibrary.simpleMessage("Port"), "port": MessageLookupByLibrary.simpleMessage("Port"),
@@ -204,6 +202,8 @@ class MessageLookup extends MessageLookupByLibrary {
"start": MessageLookupByLibrary.simpleMessage("Start"), "start": MessageLookupByLibrary.simpleMessage("Start"),
"stop": MessageLookupByLibrary.simpleMessage("Stop"), "stop": MessageLookupByLibrary.simpleMessage("Stop"),
"sureDelete": m11, "sureDelete": m11,
"sureNoPwd": MessageLookupByLibrary.simpleMessage(
"Are you sure to use no password?"),
"sureToDeleteServer": m12, "sureToDeleteServer": m12,
"ttl": MessageLookupByLibrary.simpleMessage("ttl"), "ttl": MessageLookupByLibrary.simpleMessage("ttl"),
"unknown": MessageLookupByLibrary.simpleMessage("unknown"), "unknown": MessageLookupByLibrary.simpleMessage("unknown"),

View File

@@ -145,7 +145,6 @@ class MessageLookup extends MessageLookupByLibrary {
"platformNotSupportUpdate": "platformNotSupportUpdate":
MessageLookupByLibrary.simpleMessage("当前平台不支持更新,请编译最新源码后手动安装"), MessageLookupByLibrary.simpleMessage("当前平台不支持更新,请编译最新源码后手动安装"),
"plzEnterHost": MessageLookupByLibrary.simpleMessage("请输入主机"), "plzEnterHost": MessageLookupByLibrary.simpleMessage("请输入主机"),
"plzEnterPwd": MessageLookupByLibrary.simpleMessage("请输入密码"),
"plzSelectKey": MessageLookupByLibrary.simpleMessage("请选择私钥"), "plzSelectKey": MessageLookupByLibrary.simpleMessage("请选择私钥"),
"port": MessageLookupByLibrary.simpleMessage("端口"), "port": MessageLookupByLibrary.simpleMessage("端口"),
"privateKey": MessageLookupByLibrary.simpleMessage("私钥"), "privateKey": MessageLookupByLibrary.simpleMessage("私钥"),
@@ -178,6 +177,7 @@ class MessageLookup extends MessageLookupByLibrary {
"start": MessageLookupByLibrary.simpleMessage("开始"), "start": MessageLookupByLibrary.simpleMessage("开始"),
"stop": MessageLookupByLibrary.simpleMessage("停止"), "stop": MessageLookupByLibrary.simpleMessage("停止"),
"sureDelete": m11, "sureDelete": m11,
"sureNoPwd": MessageLookupByLibrary.simpleMessage("确认使用无密码?"),
"sureToDeleteServer": m12, "sureToDeleteServer": m12,
"ttl": MessageLookupByLibrary.simpleMessage("缓存时间"), "ttl": MessageLookupByLibrary.simpleMessage("缓存时间"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknown": MessageLookupByLibrary.simpleMessage("未知"),

View File

@@ -790,11 +790,11 @@ class S {
); );
} }
/// `Please enter password.` /// `Are you sure to use no password?`
String get plzEnterPwd { String get sureNoPwd {
return Intl.message( return Intl.message(
'Please enter password.', 'Are you sure to use no password?',
name: 'plzEnterPwd', name: 'sureNoPwd',
desc: '', desc: '',
args: [], args: [],
); );

View File

@@ -73,7 +73,7 @@
"addPrivateKey": "Add private key", "addPrivateKey": "Add private key",
"choosePrivateKey": "Choose private key", "choosePrivateKey": "Choose private key",
"plzEnterHost": "Please enter host.", "plzEnterHost": "Please enter host.",
"plzEnterPwd": "Please enter password.", "sureNoPwd": "Are you sure to use no password?",
"plzSelectKey": "Please select a key.", "plzSelectKey": "Please select a key.",
"exampleName": "Example name", "exampleName": "Example name",
"stop": "Stop", "stop": "Stop",

View File

@@ -73,7 +73,7 @@
"addPrivateKey": "添加一个私钥", "addPrivateKey": "添加一个私钥",
"choosePrivateKey": "选择私钥", "choosePrivateKey": "选择私钥",
"plzEnterHost": "请输入主机", "plzEnterHost": "请输入主机",
"plzEnterPwd": "请输入密码", "sureNoPwd": "确认使用无密码",
"plzSelectKey": "请选择私钥", "plzSelectKey": "请选择私钥",
"exampleName": "名称示例", "exampleName": "名称示例",
"stop": "停止", "stop": "停止",

View File

@@ -155,6 +155,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
} }
Widget _buildProgress(double percent) { Widget _buildProgress(double percent) {
if (percent > 100) percent = 100;
final pColor = primaryColor; final pColor = primaryColor;
final percentWithinOne = percent / 100; final percentWithinOne = percent / 100;
return LinearProgressIndicator( return LinearProgressIndicator(
@@ -186,7 +187,7 @@ 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;
const mb = 1024 * 1024; const mb = 1024;
return RoundRectCard(Padding( return RoundRectCard(Padding(
padding: roundRectCardPadding, padding: roundRectCardPadding,
child: SizedBox( child: SizedBox(
@@ -329,8 +330,8 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Icons.device_hub, Icons.device_hub,
size: 17, size: 17,
), ),
Icon(Icons.arrow_downward, size: 17),
Icon(Icons.arrow_upward, size: 17), Icon(Icons.arrow_upward, size: 17),
Icon(Icons.arrow_downward, size: 17)
], ],
), ),
); );

View File

@@ -190,15 +190,29 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send), child: const Icon(Icons.send),
onPressed: () { onPressed: () async {
if (ipController.text == '') { if (ipController.text == '') {
showSnackBar(context, Text(s.plzEnterHost)); showSnackBar(context, Text(s.plzEnterHost));
return; return;
} }
if (!usePublicKey && passwordController.text == '') { if (!usePublicKey && passwordController.text == '') {
showSnackBar(context, Text(s.plzEnterPwd)); final cancel = await showRoundDialog<bool>(
context,
s.attention,
Text(s.sureNoPwd),
[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(s.ok)),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(s.cancel))
],
barrierDismiss: false);
if (cancel ?? true) {
return; return;
} }
}
if (usePublicKey && _pubKeyIndex == -1) { if (usePublicKey && _pubKeyIndex == -1) {
showSnackBar(context, Text(s.plzSelectKey)); showSnackBar(context, Text(s.plzSelectKey));
return; return;

View File

@@ -93,6 +93,7 @@ flutter:
- assets/linux/arch.png - assets/linux/arch.png
- assets/linux/fedora.png - assets/linux/fedora.png
- assets/linux/opensuse.png - assets/linux/opensuse.png
- assets/linux/wrt.png
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see