opt.: ssh page

This commit is contained in:
lollipopkit
2023-11-13 00:31:59 -06:00
parent 55ba013977
commit b2f6094a1d
5 changed files with 36 additions and 50 deletions

View File

@@ -53,7 +53,7 @@ class ServerStatus {
NetSpeed netSpeed; NetSpeed netSpeed;
Temperatures temps; Temperatures temps;
SystemType system; SystemType system;
String? failedInfo; String? err;
DiskIO diskIO; DiskIO diskIO;
/// Whether is connectting, parsing and etc. /// Whether is connectting, parsing and etc.
@@ -71,7 +71,7 @@ class ServerStatus {
required this.temps, required this.temps,
required this.system, required this.system,
required this.diskIO, required this.diskIO,
this.failedInfo, this.err,
}); });
} }

View File

@@ -259,7 +259,7 @@ class ServerProvider extends ChangeNotifier {
return; return;
} }
s.status.failedInfo = null; s.status.err = null;
/// If busy, it may be because of network reasons that the last request /// If busy, it may be because of network reasons that the last request
/// has not been completed, and the request should not be made again at this time. /// has not been completed, and the request should not be made again at this time.
@@ -286,7 +286,7 @@ class ServerProvider extends ChangeNotifier {
} }
} catch (e) { } catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.failedInfo = e.toString(); s.status.err = e.toString();
_setServerState(s, ServerState.failed); _setServerState(s, ServerState.failed);
/// In order to keep privacy, print [spi.name] instead of [spi.id] /// In order to keep privacy, print [spi.name] instead of [spi.id]
@@ -329,7 +329,7 @@ class ServerProvider extends ChangeNotifier {
} }
} catch (e) { } catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.failedInfo = e.toString(); s.status.err = e.toString();
_setServerState(s, ServerState.failed); _setServerState(s, ServerState.failed);
Loggers.app.warning('Write script to ${spi.name} by sftp', e); Loggers.app.warning('Write script to ${spi.name} by sftp', e);
return; return;
@@ -354,13 +354,13 @@ class ServerProvider extends ChangeNotifier {
segments = raw?.split(seperator).map((e) => e.trim()).toList(); segments = raw?.split(seperator).map((e) => e.trim()).toList();
if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) { if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.failedInfo = 'Seperate segments failed, raw:\n$raw'; s.status.err = 'Seperate segments failed, raw:\n$raw';
_setServerState(s, ServerState.failed); _setServerState(s, ServerState.failed);
return; return;
} }
} catch (e) { } catch (e) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.failedInfo = e.toString(); s.status.err = e.toString();
_setServerState(s, ServerState.failed); _setServerState(s, ServerState.failed);
Loggers.app.warning('Get status from ${spi.name} failed', e); Loggers.app.warning('Get status from ${spi.name} failed', e);
return; return;
@@ -369,7 +369,7 @@ class ServerProvider extends ChangeNotifier {
final systemType = SystemType.parse(segments[0]); final systemType = SystemType.parse(segments[0]);
if (!systemType.isSegmentsLenMatch(segments.length)) { if (!systemType.isSegmentsLenMatch(segments.length)) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.failedInfo = s.status.err =
'Segments not match: expect ${systemType.segmentsLen}, got ${segments.length}'; 'Segments not match: expect ${systemType.segmentsLen}, got ${segments.length}';
_setServerState(s, ServerState.failed); _setServerState(s, ServerState.failed);
return; return;
@@ -385,7 +385,7 @@ class ServerProvider extends ChangeNotifier {
s.status = await compute(getStatus, req); s.status = await compute(getStatus, req);
} catch (e, trace) { } catch (e, trace) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
s.status.failedInfo = 'Parse failed: $e\n\n$raw'; s.status.err = 'Parse failed: $e\n\n$raw';
_setServerState(s, ServerState.failed); _setServerState(s, ServerState.failed);
Loggers.parse.warning('Server status', e, trace); Loggers.parse.warning('Server status', e, trace);
return; return;

View File

@@ -239,7 +239,7 @@ class _FullScreenPageState extends State<FullScreenPage> with AfterLayoutMixin {
cs, cs,
ss.temps.first, ss.temps.first,
ss.uptime, ss.uptime,
ss.failedInfo, ss.err,
); );
return Text( return Text(
topRightStr, topRightStr,

View File

@@ -184,7 +184,7 @@ class _ServerPageState extends State<ServerPage>
onTap: () { onTap: () {
if (srv.canViewDetails) { if (srv.canViewDetails) {
AppRoute.serverDetail(spi: srv.spi).go(context); AppRoute.serverDetail(spi: srv.spi).go(context);
} else if (srv.status.failedInfo != null) { } else if (srv.status.err != null) {
_showFailReason(srv.status); _showFailReason(srv.status);
} }
}, },
@@ -398,9 +398,9 @@ class _ServerPageState extends State<ServerPage>
cs, cs,
ss.temps.first, ss.temps.first,
ss.uptime, ss.uptime,
ss.failedInfo, ss.err,
); );
if (cs == ServerState.failed && ss.failedInfo != null) { if (cs == ServerState.failed && ss.err != null) {
return GestureDetector( return GestureDetector(
onTap: () => _showFailReason(ss), onTap: () => _showFailReason(ss),
child: Text( child: Text(
@@ -421,11 +421,11 @@ class _ServerPageState extends State<ServerPage>
context.showRoundDialog( context.showRoundDialog(
title: Text(l10n.error), title: Text(l10n.error),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Text(ss.failedInfo ?? l10n.unknownError), child: Text(ss.err ?? l10n.unknownError),
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Shares.copy(ss.failedInfo!), onPressed: () => Shares.copy(ss.err!),
child: Text(l10n.copy), child: Text(l10n.copy),
) )
], ],

View File

@@ -9,7 +9,6 @@ import 'package:provider/provider.dart';
import 'package:toolbox/core/extension/context/common.dart'; import 'package:toolbox/core/extension/context/common.dart';
import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/dialog.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/core/extension/context/snackbar.dart';
import 'package:toolbox/core/utils/platform/base.dart'; import 'package:toolbox/core/utils/platform/base.dart';
import 'package:toolbox/core/utils/share.dart'; import 'package:toolbox/core/utils/share.dart';
import 'package:toolbox/data/model/server/server.dart'; import 'package:toolbox/data/model/server/server.dart';
@@ -27,7 +26,7 @@ import '../../../data/model/ssh/virtual_key.dart';
import '../../../data/res/color.dart'; import '../../../data/res/color.dart';
import '../../../data/res/terminal.dart'; import '../../../data/res/terminal.dart';
const echoPWD = 'echo \$PWD'; const _echoPWD = 'echo \$PWD';
class SSHPage extends StatefulWidget { class SSHPage extends StatefulWidget {
final ServerPrivateInfo spi; final ServerPrivateInfo spi;
@@ -55,7 +54,6 @@ class _SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin {
Timer? _virtKeyLongPressTimer; Timer? _virtKeyLongPressTimer;
late final Server? _server = widget.spi.server; late final Server? _server = widget.spi.server;
late final SSHClient? _client = _server?.client; late final SSHClient? _client = _server?.client;
late final SSHSession? _session;
Timer? _discontinuityTimer; Timer? _discontinuityTimer;
@override @override
@@ -68,8 +66,11 @@ class _SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin {
); );
_terminalStyle = TerminalStyle.fromTextStyle(textStyle); _terminalStyle = TerminalStyle.fromTextStyle(textStyle);
_keyboardType = TextInputType.values[Stores.setting.keyboardType.fetch()]; _keyboardType = TextInputType.values[Stores.setting.keyboardType.fetch()];
_initTerminal();
_initVirtKeys(); _initVirtKeys();
Future.delayed(const Duration(milliseconds: 77), () async {
await _initTerminal();
});
} }
@override @override
@@ -270,13 +271,13 @@ class _SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin {
break; break;
case VirtualKeyFunc.file: case VirtualKeyFunc.file:
// get $PWD from SSH session // get $PWD from SSH session
_terminal.textInput(echoPWD); _terminal.textInput(_echoPWD);
_terminal.keyInput(TerminalKey.enter); _terminal.keyInput(TerminalKey.enter);
final cmds = _terminal.buffer.lines.toList(); final cmds = _terminal.buffer.lines.toList();
// wait for the command to finish // wait for the command to finish
await Future.delayed(const Duration(milliseconds: 777)); await Future.delayed(const Duration(milliseconds: 777));
// the line below `echo $PWD` is the current path // the line below `echo $PWD` is the current path
final idx = cmds.lastIndexWhere((e) => e.toString().contains(echoPWD)); final idx = cmds.lastIndexWhere((e) => e.toString().contains(_echoPWD));
final initPath = cmds[idx + 1].toString(); final initPath = cmds[idx + 1].toString();
if (initPath.isEmpty || !initPath.startsWith('/')) { if (initPath.isEmpty || !initPath.startsWith('/')) {
context.showRoundDialog( context.showRoundDialog(
@@ -323,27 +324,12 @@ class _SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin {
Future<void> _initTerminal() async { Future<void> _initTerminal() async {
_write('Connecting...\r\n'); _write('Connecting...\r\n');
if (_client == null) {
// _client = await genClient( await Pros.server.refreshData(spi: widget.spi);
// widget.spi, }
// onStatus: (p0) {
// switch (p0) {
// case GenSSHClientStatus.socket:
// _write('Destination: ${widget.spi.id}');
// return _write('Establishing socket...');
// case GenSSHClientStatus.key:
// return _write('Using private key to connect...');
// case GenSSHClientStatus.pwd:
// return _write('Sending password to auth...');
// }
// },
// timeout: Stores.setting.timeoutD,
// );
// _write('Connected\r\n');
_write('Terminal size: ${_terminal.viewWidth}x${_terminal.viewHeight}\r\n');
_write('Starting shell...\r\n'); _write('Starting shell...\r\n');
_session = await _client?.shell( final session = await _client?.shell(
pty: SSHPtyConfig( pty: SSHPtyConfig(
width: _terminal.viewWidth, width: _terminal.viewWidth,
height: _terminal.viewHeight, height: _terminal.viewHeight,
@@ -352,30 +338,30 @@ class _SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin {
_setupDiscontinuityTimer(); _setupDiscontinuityTimer();
if (_session == null) { if (session == null) {
context.showSnackBar('Null session'); _write(_server?.status.err ?? 'Null session');
return; return;
} }
// _terminal.buffer.clear(); _terminal.buffer.clear();
// _terminal.buffer.setCursor(0, 0); _terminal.buffer.setCursor(0, 0);
_terminal.onOutput = (data) { _terminal.onOutput = (data) {
_session?.write(utf8.encode(data) as Uint8List); session.write(utf8.encode(data) as Uint8List);
}; };
_terminal.onResize = (width, height, pixelWidth, pixelHeight) { _terminal.onResize = (width, height, pixelWidth, pixelHeight) {
_session?.resizeTerminal(width, height); session.resizeTerminal(width, height);
}; };
_listen(_session?.stdout); _listen(session.stdout);
_listen(_session?.stderr); _listen(session.stderr);
if (widget.initCmd != null) { if (widget.initCmd != null) {
_terminal.textInput(widget.initCmd!); _terminal.textInput(widget.initCmd!);
_terminal.keyInput(TerminalKey.enter); _terminal.keyInput(TerminalKey.enter);
} }
await _session?.done; await session.done;
if (mounted) { if (mounted) {
context.pop(); context.pop();
} }