From 417cb4c89d9f4f91c2a0a1c4cb5752ecee07d61b Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Tue, 22 Aug 2023 21:07:17 +0800 Subject: [PATCH] #43 new: `bsd` base support --- ios/Runner.xcodeproj/project.pbxproj | 24 ++--- lib/data/model/app/menu.dart | 20 ++-- lib/data/model/app/shell_func.dart | 101 ++++++++++++++++-- lib/data/model/docker/ps.dart | 34 +++--- lib/data/model/server/cpu.dart | 21 ++++ lib/data/model/server/net_speed.dart | 48 +++++++++ lib/data/model/server/server_status.dart | 3 + .../server/server_status_update_req.dart | 54 ++++++++-- lib/data/model/server/system.dart | 34 ++++++ lib/data/model/server/time_seq.dart | 2 - lib/data/provider/docker.dart | 49 ++++----- lib/data/provider/server.dart | 21 +++- lib/data/res/build_data.dart | 8 +- lib/data/res/server_cmd.dart | 40 ------- lib/data/res/status.dart | 6 +- lib/view/page/docker.dart | 36 +++---- lib/view/page/server/tab.dart | 91 ++++++++-------- lib/view/page/ssh_term.dart | 2 +- lib/view/widget/input_field.dart | 13 +-- macos/Runner.xcodeproj/project.pbxproj | 14 +-- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- make.dart | 76 ++++++++++--- pubspec.lock | 34 +++--- 23 files changed, 499 insertions(+), 234 deletions(-) create mode 100644 lib/data/model/server/system.dart diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a4d3ea0d..77fbd36b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -470,7 +470,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -478,7 +478,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -602,7 +602,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -610,7 +610,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -628,7 +628,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -636,7 +636,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -657,7 +657,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -670,7 +670,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -696,7 +696,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -709,7 +709,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -732,7 +732,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -745,7 +745,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/lib/data/model/app/menu.dart b/lib/data/model/app/menu.dart index 72287ff1..e045a23f 100644 --- a/lib/data/model/app/menu.dart +++ b/lib/data/model/app/menu.dart @@ -35,11 +35,19 @@ enum DockerMenuType { rm, logs, terminal, - stats; + //stats, + ; static List items(bool running) { if (running) { - return [stop, restart, rm, logs, terminal, stats]; + return [ + stop, + restart, + rm, + logs, + terminal, + //stats, + ]; } else { return [start, rm, logs]; } @@ -59,8 +67,8 @@ enum DockerMenuType { return Icons.logo_dev; case DockerMenuType.terminal: return Icons.terminal; - case DockerMenuType.stats: - return Icons.bar_chart; + // case DockerMenuType.stats: + // return Icons.bar_chart; } } @@ -78,8 +86,8 @@ enum DockerMenuType { return s.log; case DockerMenuType.terminal: return s.terminal; - case DockerMenuType.stats: - return s.stats; + // case DockerMenuType.stats: + // return s.stats; } } diff --git a/lib/data/model/app/shell_func.dart b/lib/data/model/app/shell_func.dart index 28b5d750..b6c56d7b 100644 --- a/lib/data/model/app/shell_func.dart +++ b/lib/data/model/app/shell_func.dart @@ -1,7 +1,12 @@ +import '../../res/build_data.dart'; import '../../res/server_cmd.dart'; +import '../server/system.dart'; const _cmdDivider = '\necho $seperator\n'; +const _serverBoxDir = r'$HOME/.config/server_box'; +const _shellPath = '$_serverBoxDir/mobile_app.sh'; + enum AppShellFuncType { status, docker; @@ -15,7 +20,7 @@ enum AppShellFuncType { } } - String get exec => 'sh $shellPath -$flag'; + String get exec => 'sh $_shellPath -$flag'; String get name { switch (this) { @@ -31,16 +36,20 @@ enum AppShellFuncType { String get cmd { switch (this) { case AppShellFuncType.status: - return statusCmds.join(_cmdDivider); + return ''' +result=\$(uname 2>&1 | grep "Linux") +if [ "\$result" != "" ]; then +${_statusCmds.join(_cmdDivider)} +else +${_bsdStatusCmd.join(_cmdDivider)} +fi'''; case AppShellFuncType.docker: return ''' -result=\$(docker version 2>&1) -deniedStr="permission denied" -containStr=\$(echo \$result | grep "\${deniedStr}") -if [[ \$containStr != "" ]]; then -${dockerCmds.join(_cmdDivider)} +result=\$(docker version 2>&1 | grep "permission denied") +if [ "\$result" != "" ]; then +${_dockerCmds.join(_cmdDivider)} else -${dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)} +${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)} fi'''; } } @@ -83,6 +92,7 @@ extension EnumX on Enum { } enum StatusCmdType { + echo, time, net, sys, @@ -94,12 +104,81 @@ enum StatusCmdType { tempType, tempVal, host, - sysRhel; + ; } +/// Cmds for linux server +const _statusCmds = [ + 'echo $linuxSign', + 'date +%s', + 'cat /proc/net/dev', + 'cat /etc/*-release | grep PRETTY_NAME', + 'cat /proc/stat | grep cpu', + 'uptime', + 'cat /proc/net/snmp', + 'df -h', + 'cat /proc/meminfo', + 'cat /sys/class/thermal/thermal_zone*/type', + 'cat /sys/class/thermal/thermal_zone*/temp', + 'hostname', +]; + enum DockerCmdType { version, ps, - stats, - images; + //stats, + images, + ; } + +const _dockerCmds = [ + 'docker version', + 'docker ps -a', + //'docker stats --no-stream', + 'docker image ls', +]; + +enum BSDStatusCmdType { + echo, + time, + net, + sys, + cpu, + uptime, + disk, + mem, + //temp, + host, + ; +} + +/// Cmds for BSD server +const _bsdStatusCmd = [ + 'echo $bsdSign', + 'date +%s', + 'netstat -ibn', + 'uname -or', + 'top -l 1 | grep "CPU usage"', + 'uptime', + 'df -h', + 'top -l 1 | grep PhysMem', + //'sysctl -a | grep temperature', + 'hostname', +]; + +final _shellCmd = """ +#!/bin/sh +# +# Script for ServerBox app v1.0.${BuildData.build} +# +# DO NOT delete this file while app is running +# DO NOT run multi ServerBox apps with different version at the same time + +export LANG=en_US.UTF-8 + +${AppShellFuncType.shellScript} +"""; + +final installShellCmd = "mkdir -p $_serverBoxDir && " + "echo '$_shellCmd' > $_shellPath && " + "chmod +x $_shellPath"; diff --git a/lib/data/model/docker/ps.dart b/lib/data/model/docker/ps.dart index ad5c910b..90350b3c 100644 --- a/lib/data/model/docker/ps.dart +++ b/lib/data/model/docker/ps.dart @@ -8,10 +8,10 @@ class DockerPsItem { late String status; late String ports; late String name; - String? cpu; - String? mem; - String? net; - String? disk; + // String? cpu; + // String? mem; + // String? net; + // String? disk; DockerPsItem( this.containerId, @@ -41,19 +41,19 @@ class DockerPsItem { } } - void parseStats(String rawString) { - if (rawString.isEmpty) { - return; - } - final parts = rawString.split(_seperator); - if (parts.length != 8) { - return; - } - cpu = parts[2]; - mem = parts[3]; - net = parts[5]; - disk = parts[6]; - } + // void parseStats(String rawString) { + // if (rawString.isEmpty) { + // return; + // } + // final parts = rawString.split(_seperator); + // if (parts.length != 8) { + // return; + // } + // cpu = parts[2]; + // mem = parts[3]; + // net = parts[5]; + // disk = parts[6]; + // } bool get running => status.contains('Up '); diff --git a/lib/data/model/server/cpu.dart b/lib/data/model/server/cpu.dart index fefdb7f4..8acece4e 100644 --- a/lib/data/model/server/cpu.dart +++ b/lib/data/model/server/cpu.dart @@ -1,3 +1,5 @@ +import 'package:toolbox/data/res/status.dart'; + import 'time_seq.dart'; class Cpus extends TimeSeq { @@ -95,3 +97,22 @@ List parseCPU(String raw) { } return cpus; } + +final _bsdCpuPercentReg = RegExp(r'(\d+\.\d+)%'); + +/// TODO: Change this implementation to parse cpu status on BSD system +/// +/// [raw]: +/// CPU usage: 14.70% user, 12.76% sys, 72.52% idle +Cpus parseBsdCpu(String raw) { + final percents = _bsdCpuPercentReg + .allMatches(raw) + .map((e) => double.parse(e.group(1) ?? '0') * 100) + .toList(); + if (percents.length != 3) return initCpuStatus; + return initCpuStatus + ..now = [ + OneTimeCpuStatus('cpu', percents[0].toInt(), percents[1].toInt(), 0, + percents[2].toInt(), 0, 0, 0) + ]; +} diff --git a/lib/data/model/server/net_speed.dart b/lib/data/model/server/net_speed.dart index 9b1103ad..4ab1649c 100644 --- a/lib/data/model/server/net_speed.dart +++ b/lib/data/model/server/net_speed.dart @@ -115,3 +115,51 @@ List parseNetSpeed(String raw, int time) { } return results; } + +/// [raw] example: +/// Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll +/// lo0 16384 17296531 0 2524959720 17296531 0 2524959720 0 +/// lo0 16384 127 127.0.0.1 17296531 - 2524959720 17296531 - 2524959720 - +/// lo0 16384 ::1/128 ::1 17296531 - 2524959720 17296531 - 2524959720 - +/// lo0 16384 fe80::1%lo0 fe80:1::1 17296531 - 2524959720 17296531 - 2524959720 - +/// gif0* 1280 0 0 0 0 0 0 0 +/// stf0* 1280 0 0 0 0 0 0 0 +/// en0 1500 22:20:xx:xx:xx:e6 739447 0 693997876 535600 0 79008877 0 +/// en0 1500 fe80::f1:xx fe80:4::f1:xxxx:9 739447 - 693997876 535600 - 79008877 - +/// en0 1500 192.168.2 192.168.2.111 739447 - 693997876 535600 - 79008877 - +/// en0 1500 fd6b:xxxx:3 fd6b:xxxx:xxxx:0: 739447 - 693997876 535600 - 79008877 - +/// en1 1500 88:d8:xx:xx:xx:1d 0 0 0 0 0 0 0 +/// utun0 1380 0 0 0 3 0 280 0 +/// utun0 1380 fe80::xxxx: fe80:6::xxxx:xxxx 0 - 0 3 - 280 - +/// utun1 2000 0 0 0 3 0 280 0 +/// utun1 2000 fe80::xxxx: fe80:7::xxxx:xxxx 0 - 0 3 - 280 - +/// utun2 1000 0 0 0 3 0 280 0 +/// utun2 1000 fe80::xxxx: fe80:8::xxxx:xxx: 0 - 0 3 - 280 - +/// utun4 9000 746744 0 845373390 386111 0 424400998 0 +/// utun4 9000 198.18.0/16 198.18.0.1 746744 - 845373390 386111 - 424400998 - +/// en2* 1500 36:7c:xx:xx:xx:xx 0 0 0 0 0 0 0 +List parseBsdNetSpeed(String raw, int time) { + final split = raw.split('\n'); + if (split.length < 2) { + return []; + } + + final results = []; + for (final item in split.sublist(1)) { + final data = item.trim().split(RegExp(r'\s+')); + final device = data[0]; + if (device.endsWith('*')) { + continue; + } + if (results.any((element) => element.device == device)) { + continue; + } + if (data.length != 11) { + continue; + } + final bytesIn = BigInt.parse(data[6]); + final bytesOut = BigInt.parse(data[9]); + results.add(NetSpeedPart(device, bytesIn, bytesOut, time)); + } + return results; +} diff --git a/lib/data/model/server/server_status.dart b/lib/data/model/server/server_status.dart index 3eccd31a..a93424bc 100644 --- a/lib/data/model/server/server_status.dart +++ b/lib/data/model/server/server_status.dart @@ -1,3 +1,4 @@ +import 'package:toolbox/data/model/server/system.dart'; import 'package:toolbox/data/model/server/temp.dart'; import 'cpu.dart'; @@ -16,6 +17,7 @@ class ServerStatus { Conn tcp; NetSpeed netSpeed; Temperatures temps; + SystemType system; String? failedInfo; ServerStatus({ @@ -28,6 +30,7 @@ class ServerStatus { required this.netSpeed, required this.swap, required this.temps, + required this.system, this.failedInfo, }); } diff --git a/lib/data/model/server/server_status_update_req.dart b/lib/data/model/server/server_status_update_req.dart index dc1b069f..fad8421e 100644 --- a/lib/data/model/server/server_status_update_req.dart +++ b/lib/data/model/server/server_status_update_req.dart @@ -1,3 +1,5 @@ +import 'package:toolbox/data/model/server/system.dart'; + import '../app/shell_func.dart'; import 'cpu.dart'; import 'disk.dart'; @@ -9,11 +11,25 @@ import 'conn.dart'; class ServerStatusUpdateReq { final ServerStatus ss; final List segments; + final SystemType system; - const ServerStatusUpdateReq(this.ss, this.segments); + const ServerStatusUpdateReq({ + required this.system, + required this.ss, + required this.segments, + }); } Future getStatus(ServerStatusUpdateReq req) async { + switch (req.system) { + case SystemType.linux: + return _getLinuxStatus(req); + case SystemType.bsd: + return _getBsdStatus(req); + } +} + +Future _getLinuxStatus(ServerStatusUpdateReq req) async { final segments = req.segments; final time = int.parse(StatusCmdType.time.find(segments)); @@ -24,7 +40,6 @@ Future getStatus(ServerStatusUpdateReq req) async { final sys = _parseSysVer( StatusCmdType.sys.find(segments), StatusCmdType.host.find(segments), - StatusCmdType.sysRhel.find(segments), ); if (sys != null) { req.ss.sysVer = sys; @@ -56,6 +71,30 @@ Future getStatus(ServerStatusUpdateReq req) async { return req.ss; } +Future _getBsdStatus(ServerStatusUpdateReq req) async { + final segments = req.segments; + + final time = int.parse(BSDStatusCmdType.time.find(segments)); + + final net = parseBsdNetSpeed(BSDStatusCmdType.net.find(segments), time); + req.ss.netSpeed.update(net); + + req.ss.sysVer = BSDStatusCmdType.sys.find(segments); + + req.ss.cpu = parseBsdCpu(BSDStatusCmdType.cpu.find(segments)); + + //req.ss.mem = parseBsdMem(BSDStatusCmdType.mem.find(segments)); + + final uptime = _parseUpTime(BSDStatusCmdType.uptime.find(segments)); + if (uptime != null) { + req.ss.uptime = uptime; + } + + req.ss.disk = parseDisk(BSDStatusCmdType.disk.find(segments)); + + return req.ss; +} + // raw: // 19:39:15 up 61 days, 18:16, 1 user, load average: 0.00, 0.00, 0.00 String? _parseUpTime(String raw) { @@ -69,17 +108,14 @@ String? _parseUpTime(String raw) { return null; } -String? _parseSysVer(String raw, String hostname, String rawRhel) { +String? _parseSysVer(String raw, String hostname) { try { final s = raw.split('='); if (s.length == 2) { return s[1].replaceAll('"', '').replaceFirst('\n', ''); } - } catch (e) { - if (!rawRhel.contains('cat: /etc/redhat-release:')) { - return rawRhel; - } - if (hostname.isNotEmpty) return hostname; + } finally { + // ignore: control_flow_in_finally + return hostname.isEmpty ? null : hostname; } - return null; } diff --git a/lib/data/model/server/system.dart b/lib/data/model/server/system.dart new file mode 100644 index 00000000..d5719f34 --- /dev/null +++ b/lib/data/model/server/system.dart @@ -0,0 +1,34 @@ +import 'package:toolbox/data/model/app/shell_func.dart'; + +enum SystemType { + linux._(linuxSign), + bsd._(bsdSign), + ; + + final String value; + + const SystemType._(this.value); + + static SystemType? parse(String? value) { + if (value == null) return null; + switch (value) { + case linuxSign: + return SystemType.linux; + case bsdSign: + return SystemType.bsd; + } + return null; + } + + bool isSegmentsLenMatch(int len) { + switch (this) { + case SystemType.linux: + return len == StatusCmdType.values.length; + case SystemType.bsd: + return len == BSDStatusCmdType.values.length; + } + } +} + +const linuxSign = 'linux'; +const bsdSign = 'bsd'; diff --git a/lib/data/model/server/time_seq.dart b/lib/data/model/server/time_seq.dart index d693706e..4fcfb4fd 100644 --- a/lib/data/model/server/time_seq.dart +++ b/lib/data/model/server/time_seq.dart @@ -1,5 +1,3 @@ -// ignore_for_file: prefer_final_fields - abstract class TimeSeq { List pre; List now; diff --git a/lib/data/provider/docker.dart b/lib/data/provider/docker.dart index 2d6553a9..b15c032a 100644 --- a/lib/data/provider/docker.dart +++ b/lib/data/provider/docker.dart @@ -10,10 +10,11 @@ import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/docker/image.dart'; import 'package:toolbox/data/model/docker/ps.dart'; import 'package:toolbox/data/model/app/error.dart'; -import 'package:toolbox/data/res/server_cmd.dart'; import 'package:toolbox/data/store/docker.dart'; import 'package:toolbox/locator.dart'; +import '../res/server_cmd.dart'; + final _dockerNotFound = RegExp(r'command not found|Unknown command'); final _versionReg = RegExp(r'(Version:)\s+([0-9]+\.[0-9]+\.[0-9]+)'); // eg: `Docker Engine - Community` @@ -72,7 +73,7 @@ class DockerProvider extends ChangeNotifier { // Check result segments count final segments = raw.split(seperator); - if (segments.length != dockerCmds.length) { + if (segments.length != DockerCmdType.values.length) { error = DockerErr(type: DockerErrType.segmentsNotMatch); _logger.warning('Docker segments not match: ${segments.length}'); notifyListeners(); @@ -119,28 +120,28 @@ class DockerProvider extends ChangeNotifier { } // Parse docker stats - final statsRaw = DockerCmdType.stats.find(segments); - try { - final statsLines = statsRaw.split('\n'); - statsLines.removeWhere((element) => element.isEmpty); - if (statsLines.isNotEmpty) statsLines.removeAt(0); - for (var item in items!) { - final statsLine = statsLines.firstWhere( - (element) => element.contains(item.containerId), - orElse: () => '', - ); - if (statsLine.isEmpty) continue; - item.parseStats(statsLine); - } - } catch (e, trace) { - error = DockerErr( - type: DockerErrType.parseStats, - message: '$statsRaw\n-\n$e', - ); - _logger.warning('Parse docker stats: $statsRaw', e, trace); - } finally { - notifyListeners(); - } + // final statsRaw = DockerCmdType.stats.find(segments); + // try { + // final statsLines = statsRaw.split('\n'); + // statsLines.removeWhere((element) => element.isEmpty); + // if (statsLines.isNotEmpty) statsLines.removeAt(0); + // for (var item in items!) { + // final statsLine = statsLines.firstWhere( + // (element) => element.contains(item.containerId), + // orElse: () => '', + // ); + // if (statsLine.isEmpty) continue; + // item.parseStats(statsLine); + // } + // } catch (e, trace) { + // error = DockerErr( + // type: DockerErrType.parseStats, + // message: '$statsRaw\n-\n$e', + // ); + // _logger.warning('Parse docker stats: $statsRaw', e, trace); + // } finally { + // notifyListeners(); + // } } Future _onPwd(String event, StreamSink stdin) async { diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index c754ce49..db305ff0 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:toolbox/data/model/app/shell_func.dart'; +import 'package:toolbox/data/model/server/system.dart'; import '../../core/extension/order.dart'; import '../../core/extension/uint8list.dart'; @@ -265,18 +266,28 @@ class ServerProvider extends ChangeNotifier { final raw = await s.client?.run(AppShellFuncType.status.exec).string; final segments = raw?.split(seperator).map((e) => e.trim()).toList(); - if (raw == null || - raw.isEmpty || - segments == null || - segments.length != StatusCmdType.values.length) { + if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) { _limiter.inc(sid); s.status.failedInfo = 'Seperate segments failed, raw:\n$raw'; _setServerState(s, ServerState.failed); return; } + final systemType = SystemType.parse(segments[0]); + if (systemType == null || !systemType.isSegmentsLenMatch(segments.length)) { + _limiter.inc(sid); + s.status.failedInfo = 'Segments not match: ${segments.length}'; + _setServerState(s, ServerState.failed); + return; + } + s.status.system = systemType; + try { - final req = ServerStatusUpdateReq(s.status, segments); + final req = ServerStatusUpdateReq( + ss: s.status, + segments: segments, + system: systemType, + ); s.status = await compute(getStatus, req); } catch (e, trace) { _limiter.inc(sid); diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 35b9a138..487aeecd 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,8 +2,8 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 491; - static const String engine = "3.10.6"; - static const String buildAt = "2023-08-20 23:32:07.343451"; - static const int modifications = 4; + static const int build = 493; + static const String engine = "3.13.0"; + static const String buildAt = "2023-08-22 16:14:41.022063"; + static const int modifications = 8; } diff --git a/lib/data/res/server_cmd.dart b/lib/data/res/server_cmd.dart index ab8d8d46..407c2516 100644 --- a/lib/data/res/server_cmd.dart +++ b/lib/data/res/server_cmd.dart @@ -1,41 +1 @@ -import '../model/app/shell_func.dart'; -import 'build_data.dart'; - const seperator = 'SrvBoxSep'; -const serverBoxDir = r'$HOME/.config/server_box'; -const shellPath = '$serverBoxDir/mobile_app.sh'; - -const statusCmds = [ - 'date +%s', - 'cat /proc/net/dev', - 'cat /etc/os-release | grep PRETTY_NAME', - 'cat /proc/stat | grep cpu', - 'uptime', - 'cat /proc/net/snmp', - 'df -h', - 'cat /proc/meminfo', - 'cat /sys/class/thermal/thermal_zone*/type', - 'cat /sys/class/thermal/thermal_zone*/temp', - 'hostname', - 'cat /etc/redhat-release', -]; - -const dockerCmds = [ - 'docker version', - 'docker ps -a', - 'docker stats --no-stream', - 'docker image ls', -]; - -final shellCmd = """ -# Script for app `${BuildData.name} v1.0.${BuildData.build}` -# Delete this file while app is running will cause app crash - -export LANG=en_US.UTF-8 - -${AppShellFuncType.shellScript} -"""; - -final installShellCmd = "mkdir -p $serverBoxDir && " - "echo '$shellCmd' > $shellPath && " - "chmod +x $shellPath"; diff --git a/lib/data/res/status.dart b/lib/data/res/status.dart index af14f83f..b04e2e40 100644 --- a/lib/data/res/status.dart +++ b/lib/data/res/status.dart @@ -6,6 +6,7 @@ import '../model/server/memory.dart'; import '../model/server/net_speed.dart'; import '../model/server/server_status.dart'; import '../model/server/conn.dart'; +import '../model/server/system.dart'; Memory get _initMemory => Memory( total: 1, @@ -38,8 +39,8 @@ NetSpeed get initNetSpeed => NetSpeed( [_initNetSpeedPart], ); Swap get _initSwap => Swap( - total: 1, - free: 1, + total: 0, + free: 0, cached: 0, ); ServerStatus get initStatus => ServerStatus( @@ -60,5 +61,6 @@ ServerStatus get initStatus => ServerStatus( tcp: Conn(maxConn: 0, active: 0, passive: 0, fail: 0), netSpeed: initNetSpeed, swap: _initSwap, + system: SystemType.linux, temps: Temperatures(), ); diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index bb18474e..25f9cd7e 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -474,24 +474,24 @@ class _DockerManagePageState extends State { 'Docker terminal', ).go(context); break; - case DockerMenuType.stats: - showRoundDialog( - context: context, - title: Text(_s.stats), - child: Text( - 'CPU: ${dItem.cpu}\n' - 'Mem: ${dItem.mem}\n' - 'Net: ${dItem.net}\n' - 'Block: ${dItem.disk}', - ), - actions: [ - TextButton( - onPressed: () => context.pop(), - child: Text(_s.ok), - ), - ], - ); - break; + // case DockerMenuType.stats: + // showRoundDialog( + // context: context, + // title: Text(_s.stats), + // child: Text( + // 'CPU: ${dItem.cpu}\n' + // 'Mem: ${dItem.mem}\n' + // 'Net: ${dItem.net}\n' + // 'Block: ${dItem.disk}', + // ), + // actions: [ + // TextButton( + // onPressed: () => context.pop(), + // child: Text(_s.ok), + // ), + // ], + // ); + // break; } }, ); diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 658eb4b6..cadbde2b 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -108,12 +108,6 @@ class _ServerPageState extends State ); } - List _filterServers(ServerProvider pro) => pro.serverOrder - .where((e) => pro.servers.containsKey(e)) - .where((e) => - _tag == null || (pro.servers[e]?.spi.tags?.contains(_tag) ?? false)) - .toList(); - Widget _buildTagsSwitcher(ServerProvider provider) { return TagSwitcher( tags: provider.tags, @@ -224,14 +218,11 @@ class _ServerPageState extends State ) { final rootDisk = findRootDisk(ss.disk); late final List children; - double? height; if (cs != ServerState.finished) { - height = 23.0; children = [ _buildServerCardTitle(ss, cs, spi), ]; } else { - height = 107; children = [ _buildServerCardTitle(ss, cs, spi), height13, @@ -262,7 +253,7 @@ class _ServerPageState extends State return AnimatedContainer( duration: const Duration(milliseconds: 377), curve: Curves.fastEaseInToSlowEaseOut, - height: height, + height: _calcCardHeight(cs), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -355,38 +346,6 @@ class _ServerPageState extends State ); } - String _getTopRightStr( - ServerState cs, - double? temp, - String upTime, - String? failedInfo, - ) { - switch (cs) { - case ServerState.disconnected: - return _s.disconnected; - case ServerState.finished: - final tempStr = temp == null ? '' : '${temp.toStringAsFixed(1)}°C'; - final items = [tempStr, upTime]; - final str = items.where((element) => element.isNotEmpty).join(' | '); - if (str.isEmpty) return _s.noResult; - return str; - case ServerState.loading: - return _s.serverTabLoading; - case ServerState.connected: - return _s.connected; - case ServerState.connecting: - return _s.serverTabConnecting; - case ServerState.failed: - if (failedInfo == null) { - return _s.serverTabFailed; - } - if (failedInfo.contains('encypted')) { - return _s.serverTabPlzSave; - } - return failedInfo; - } - } - Widget _buildIOData(String up, String down) { return Column( children: [ @@ -448,4 +407,52 @@ class _ServerPageState extends State } _serverProvider.startAutoRefresh(); } + + List _filterServers(ServerProvider pro) => pro.serverOrder + .where((e) => pro.servers.containsKey(e)) + .where((e) => + _tag == null || (pro.servers[e]?.spi.tags?.contains(_tag) ?? false)) + .toList(); + + String _getTopRightStr( + ServerState cs, + double? temp, + String upTime, + String? failedInfo, + ) { + switch (cs) { + case ServerState.disconnected: + return _s.disconnected; + case ServerState.finished: + final tempStr = temp == null ? '' : '${temp.toStringAsFixed(1)}°C'; + final items = [tempStr, upTime]; + final str = items.where((element) => element.isNotEmpty).join(' | '); + if (str.isEmpty) return _s.noResult; + return str; + case ServerState.loading: + return _s.serverTabLoading; + case ServerState.connected: + return _s.connected; + case ServerState.connecting: + return _s.serverTabConnecting; + case ServerState.failed: + if (failedInfo == null) { + return _s.serverTabFailed; + } + if (failedInfo.contains('encypted')) { + return _s.serverTabPlzSave; + } + return failedInfo; + } + } + + double _calcCardHeight(ServerState cs) { + if (cs != ServerState.finished) { + return 23.0; + } + if (_settingStore.moveOutServerTabFuncBtns.fetch()!) { + return 132; + } + return 107; + } } diff --git a/lib/view/page/ssh_term.dart b/lib/view/page/ssh_term.dart index ca8e6e23..079b6555 100644 --- a/lib/view/page/ssh_term.dart +++ b/lib/view/page/ssh_term.dart @@ -130,7 +130,7 @@ class _SSHPageState extends State { deleteDetection: isIOS, autofocus: true, keyboardAppearance: _isDark ? Brightness.dark : Brightness.light, - hideScrollBar: isMobile, + hideScrollBar: false, ), ), ); diff --git a/lib/view/widget/input_field.dart b/lib/view/widget/input_field.dart index 964d93e0..bec153c2 100644 --- a/lib/view/widget/input_field.dart +++ b/lib/view/widget/input_field.dart @@ -55,12 +55,13 @@ class Input extends StatelessWidget { autocorrect: autoCorrect, enableSuggestions: suggestiion, decoration: InputDecoration( - label: label != null ? Text(label!) : null, - hintText: hint, - icon: icon != null ? Icon(icon) : null, - border: InputBorder.none, - errorText: errorText, - prefix: prefix,), + label: label != null ? Text(label!) : null, + hintText: hint, + icon: icon != null ? Icon(icon) : null, + border: InputBorder.none, + errorText: errorText, + prefix: prefix, + ), controller: controller, obscureText: obscureText, ), diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index b8486dd0..4026af48 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -258,7 +258,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { @@ -474,9 +474,9 @@ baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -489,9 +489,9 @@ baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -504,9 +504,9 @@ baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 491; + CURRENT_PROJECT_VERSION = 493; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.491; + MARKETING_VERSION = 1.0.493; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index d66a89e7..c36dcc0c 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ flutterBuildIOS() async { Future flutterBuildMacOS() async { await flutterBuild('macos'); + await scpMacOS2CDN(); } Future flutterBuildAndroid() async { await flutterBuild('apk'); await killJava(); - await scp2CDN(); + await scpApk2CDN(); } -Future scp2CDN() async { +Future flutterBuildLinux() async { + await flutterBuild('linux'); + await scpLinux2CDN(); +} + +Future scpApk2CDN() async { final sha256 = await getFileSha256(apkPath); print('SHA256: $sha256'); final result = await Process.run( @@ -155,6 +162,45 @@ Future scp2CDN() async { } } +Future scpMacOS2CDN() async { + final zipName = '$build.app.zip'; + // Zip the .app + await Process.run('zip', [ + '-r', + './release/$zipName', + './build/macos/Build/Products/Release/server_box.app', + ]); + final result = await Process.run( + 'scp', + [ + './release/$zipName', + 'hk:/var/www/res/serverbox/$build.app.zip', + ], + runInShell: true, + ); + if (result.exitCode != 0) { + print(result.stderr); + exit(1); + } + print('Upload macOS $zipName finished.'); +} + +Future scpLinux2CDN() async { + final result = await Process.run( + 'scp', + [ + './build/linux/x64/release/bundle/server_box.tar.gz', + 'hk:/var/www/res/serverbox/$build.tar.gz', + ], + runInShell: true, + ); + if (result.exitCode != 0) { + print(result.stderr); + exit(1); + } + print('Upload Linux $build.tar.gz finished.'); +} + Future changeAppleVersion() async { for (final path in ['ios', 'macos']) { final file = File('$path/$appleXCConfigPath'); @@ -185,38 +231,40 @@ void main(List args) async { } final command = args[0]; - switch (command) { case 'build': - final stopwatch = Stopwatch()..start(); await dartFormat(); await getGitCommitCount(); // always change version to avoid dismatch version between different // platforms await changeAppleVersion(); await updateBuildData(); + + final funcs = Function()>[]; + if (args.length > 1) { final platforms = args[1]; for (final platform in platforms.split(',')) { if (buildFuncs.keys.contains(platform)) { - await buildFuncs[platform]!(); - print('Build finished in [${stopwatch.elapsed}]'); - stopwatch.reset(); - stopwatch.start(); + funcs.add(buildFuncs[platform]!); } else { print('Unknown platform: $platform'); } } + } else { + funcs.addAll(buildFuncs.values); + } - return; - } - for (final func in buildFuncs.values) { + final stopwatch = Stopwatch(); + for (final func in funcs) { + stopwatch.start(); await func(); + print('Build finished in ${stopwatch.elapsed}\n'); + stopwatch.reset(); } - print('Build finished in ${stopwatch.elapsed}\n'); - return; + break; default: print('Unsupported command: $command'); - return; + break; } } diff --git a/pubspec.lock b/pubspec.lock index ab3bb6f5..b39ec726 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -182,10 +182,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -458,10 +458,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" io: dependency: transitive description: @@ -522,18 +522,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -839,10 +839,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -887,10 +887,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: @@ -1003,6 +1003,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1061,5 +1069,5 @@ packages: source: hosted version: "0.0.6" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0"