diff --git a/lib/core/update.dart b/lib/core/update.dart index 8a62726e..96955a67 100644 --- a/lib/core/update.dart +++ b/lib/core/update.dart @@ -6,7 +6,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:logging/logging.dart'; import 'package:r_upgrade/r_upgrade.dart'; import 'package:toolbox/core/extension/navigator.dart'; -import 'package:toolbox/core/utils/misc.dart'; import 'package:toolbox/data/res/path.dart'; import '../data/provider/app.dart'; diff --git a/lib/core/utils/misc.dart b/lib/core/utils/misc.dart index ba3dc00d..3784eebb 100644 --- a/lib/core/utils/misc.dart +++ b/lib/core/utils/misc.dart @@ -73,16 +73,7 @@ String getTime(int? unixMill) { .replaceFirst('.000', ''); } +/// Join two path with `/` String pathJoin(String path1, String path2) { return path1 + (path1.endsWith('/') ? '' : '/') + path2; } - -String? getHomeDir() { - final envVars = Platform.environment; - if (isMacOS || isLinux) { - return envVars['HOME']; - } else if (isWindows) { - return envVars['UserProfile']; - } - return null; -} diff --git a/lib/core/utils/platform.dart b/lib/core/utils/platform.dart index 7f5a23f4..88e79004 100644 --- a/lib/core/utils/platform.dart +++ b/lib/core/utils/platform.dart @@ -10,7 +10,7 @@ enum PlatformType { windows, web, fuchsia, - unknown, + unknown; } final _p = () { @@ -38,7 +38,15 @@ final _p = () { return PlatformType.unknown; }(); +final _pathSep = () { + if (Platform.isWindows) { + return '\\'; + } + return '/'; +}(); + PlatformType get platform => _p; +String get pathSeparator => _pathSep; bool get isAndroid => _p == PlatformType.android; bool get isIOS => _p == PlatformType.ios; @@ -51,3 +59,23 @@ bool get isDesktop => _p == PlatformType.linux || _p == PlatformType.macos || _p == PlatformType.windows; + +/// Available only on desktop, +/// return null on mobile +String? getHomeDir() { + final envVars = Platform.environment; + if (isMacOS || isLinux) { + return envVars['HOME']; + } else if (isWindows) { + return envVars['UserProfile']; + } + return null; +} + +/// Join two paths with platform specific separator +String pathJoin(String path1, String path2) { + if (isWindows) { + return path1 + (path1.endsWith('\\') ? '' : '\\') + path2; + } + return path1 + (path1.endsWith('/') ? '' : '/') + path2; +} diff --git a/lib/data/model/app/path_with_prefix.dart b/lib/data/model/app/path_with_prefix.dart index acab1deb..6f195eba 100644 --- a/lib/data/model/app/path_with_prefix.dart +++ b/lib/data/model/app/path_with_prefix.dart @@ -1,4 +1,4 @@ -import 'package:toolbox/core/utils/misc.dart'; +import '../../../core/utils/platform.dart'; class PathWithPrefix { final String _prefixPath; diff --git a/lib/view/page/private_key/list.dart b/lib/view/page/private_key/list.dart index 420391be..e12e33b8 100644 --- a/lib/view/page/private_key/list.dart +++ b/lib/view/page/private_key/list.dart @@ -9,7 +9,6 @@ import 'package:toolbox/data/store/private_key.dart'; import 'package:toolbox/locator.dart'; import '../../../core/route.dart'; -import '../../../core/utils/misc.dart'; import '../../../core/utils/platform.dart'; import '../../../data/model/server/private_key_info.dart'; import '../../../data/provider/private_key.dart'; diff --git a/lib/view/page/process.dart b/lib/view/page/process.dart index 07911a40..8d1c1998 100644 --- a/lib/view/page/process.dart +++ b/lib/view/page/process.dart @@ -43,8 +43,7 @@ class _ProcessPageState extends State { showSnackBar(context, Text(_s.noClient)); return; } - _timer = - Timer.periodic(const Duration(seconds: 3), (_) => _refresh()); + _timer = Timer.periodic(const Duration(seconds: 3), (_) => _refresh()); } Future _refresh() async { diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index d84c0f5d..8381fddf 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -1,3 +1,5 @@ +import 'dart:io' show Directory, File, Platform, Process; + import 'package:after_layout/after_layout.dart'; import 'package:circle_chart/circle_chart.dart'; import 'package:flutter/material.dart'; @@ -6,7 +8,6 @@ import 'package:get_it/get_it.dart'; import 'package:nil/nil.dart'; import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/order.dart'; -import 'package:toolbox/core/utils/misc.dart'; import 'package:toolbox/data/model/app/net_view.dart'; import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/provider/snippet.dart'; @@ -15,6 +16,9 @@ import 'package:toolbox/view/widget/tag/picker.dart'; import 'package:toolbox/view/widget/tag/switcher.dart'; import '../../../core/route.dart'; +import '../../../core/utils/misc.dart' hide pathJoin; +import '../../../core/utils/platform.dart'; +import '../../../core/utils/server.dart'; import '../../../core/utils/ui.dart'; import '../../../data/model/server/disk.dart'; import '../../../data/model/server/server.dart'; @@ -225,7 +229,6 @@ class _ServerPageState extends State _buildTopRightText(ss, cs), width13, _buildSSHBtn(spi), -// SizedBox(width: 5,), _buildMoreBtn(spi), ], ) @@ -271,12 +274,9 @@ class _ServerPageState extends State } Widget _buildSSHBtn(ServerPrivateInfo spi) { - return IconButton( - icon: const Icon( - Icons.terminal, - size: 21, - ), - onPressed: () => startSSH(spi, context), + return GestureDetector( + child: const Icon(Icons.terminal, size: 21), + onTap: () => gotoSSH(spi), ); } @@ -447,6 +447,53 @@ class _ServerPageState extends State ); } + Future gotoSSH(ServerPrivateInfo spi) async { + // as a `Mobile first` app -> handle mobile first + if (!isDesktop) { + AppRoute(SSHPage(spi: spi), 'ssh page').go(context); + return; + } + final extraArgs = []; + if (spi.port != 22) { + extraArgs.addAll(['-p', '${spi.port}']); + } + + final path = () { + final tempKeyFileName = 'srvbox_pk_${spi.pubKeyId}'; + return pathJoin(Directory.systemTemp.path, tempKeyFileName); + }(); + final file = File(path); + final shouldGenKey = spi.pubKeyId != null; + if (shouldGenKey) { + await file.delete(); + await file.writeAsString(getPrivateKey(spi.pubKeyId!)); + extraArgs.addAll(["-i", path]); + } + + List sshCommand = ["ssh", "${spi.user}@${spi.ip}"] + extraArgs; + final system = Platform.operatingSystem; + switch (system) { + case "windows": + await Process.start("cmd", ["/c", "start"] + sshCommand); + break; + case "linux": + await Process.start("x-terminal-emulator", ["-e"] + sshCommand); + break; + case "macos": + await Process.start("osascript", [ + "-e", + 'tell application "Terminal" to do script "${sshCommand.join(" ")}"' + ]); + break; + default: + showSnackBar(context, Text('Mismatch system: $system')); + } + // For security reason, delete the private key file after use + if (shouldGenKey) { + await Future.delayed(const Duration(seconds: 2), file.delete); + } + } + @override bool get wantKeepAlive => true; diff --git a/lib/view/page/ssh/term.dart b/lib/view/page/ssh/term.dart index 947c8c7c..88d13393 100644 --- a/lib/view/page/ssh/term.dart +++ b/lib/view/page/ssh/term.dart @@ -1,4 +1,3 @@ -import 'dart:io' show Process, File; import 'dart:async'; import 'dart:convert'; @@ -26,33 +25,6 @@ import '../../../data/store/setting.dart'; import '../../../locator.dart'; import '../storage/sftp.dart'; -startSSH(ServerPrivateInfo spi, BuildContext context) { - if (isLinux || isMacOS) { - unawaited(() async { - List extarArgs = []; - if (spi.pubKeyId != null) { - String path = "/tmp/.serverbox_pk_${spi.pubKeyId}"; - File(path).openWrite().write(getPrivateKey(spi.pubKeyId!)); - extarArgs += ["-i", path]; - } - List sshCommand = ["ssh", spi.user + "@" + spi.ip] + extarArgs; - if (isLinux) { - Process.start("x-terminal-emulator", ["-e"] + sshCommand); - return; - } - if (isMacOS) { - Process.start("osascript", [ - "-e", - 'tell application "Terminal" to do script "${sshCommand.join(" ")}"' - ]); - return; - } - }()); - return; - } - AppRoute(SSHPage(spi: spi), 'ssh page').go(context); -} - class SSHPage extends StatefulWidget { final ServerPrivateInfo spi; final String? initCmd;