Files
flutter_server_box/lib/view/page/server/tab/utils.dart
GT610 39a3e0800b opt: Improve container parsing and error handling (#1001)
* fix(ssh): Modify the return type of execWithPwd to include the output content

Adjust the return type of the `execWithPwd` method to `(int?, String)` so that it can simultaneously return the exit code and output content

Fix the issue in ContainerNotifier where the return result of execWithPwd is not handled correctly

Ensure that server operations (shutdown/restart/suspend) are correctly pending until the command execution is completed

* refactor(container): Change single error handling to multiple error lists

Support the simultaneous display of multiple container operation errors, enhancing error handling capabilities

* fix(container): Adjust the layout width and optimize the handling of text overflow

Adjust the width calculation for the container page layout, changing from subtracting a fixed value to subtracting a smaller value to improve the layout

Add overflow ellipsis processing to the text to prevent anomalies when the text is too long

* Revert "refactor(container): Change single error handling to multiple error lists"

This reverts commit 72aaa173f5.

* feat(container): Add Podman Docker emulation detection function

Add detection for Podman Docker emulation in the container module. When detected, a prompt message will be displayed and users will be advised to switch to Podman settings.

Updated the multilingual translation files to support the new features.

* fix: Fix error handling in SSH client and container operations

Fix the issue where the SSH client does not handle stderr when executing commands

Error handling for an empty client in the container addition operation

Fix the issue where null may be returned during server page operations

* fix(container): Check if client is empty before running the command

When the client is null, directly return an error to avoid null pointer exception

* fix: Revert `stderr` ignore

* fix(container): Detect Podman simulation in advance and optimize error handling

Move the Podman simulation detection to the initial parsing stage to avoid redundant checks

Remove duplicated error handling code and simplify the logic

* fix(container): Fix the error handling logic during container command execution

Increase the inspection of error outputs, including handling situations such as sudo password prompts and Podman not being installed

* refactor(macOS): Remove unused path_provider_foundation plugin
2026-01-17 14:40:44 +08:00

225 lines
6.5 KiB
Dart

// ignore_for_file: invalid_use_of_protected_member
part of 'tab.dart';
extension _Actions on _ServerPageState {
void _onTapCard(ServerState srv) {
if (srv.canViewDetails) {
// _splitViewCtrl.replace(ServerDetailPage(
// key: ValueKey(srv.spi.id),
// args: SpiRequiredArgs(srv.spi),
// ));
ServerDetailPage.route.go(context, SpiRequiredArgs(srv.spi));
} else {
// _splitViewCtrl.replace(ServerEditPage(
// key: ValueKey(srv.spi.id),
// args: SpiRequiredArgs(srv.spi),
// ));
ServerEditPage.route.go(context, args: SpiRequiredArgs(srv.spi));
}
}
void _onLongPressCard(ServerState srv) {
if (srv.conn == ServerConn.finished) {
final id = srv.spi.id;
final cardStatus = _getCardNoti(id);
cardStatus.value = cardStatus.value.copyWith(flip: !cardStatus.value.flip);
} else {
ServerEditPage.route.go(context, args: SpiRequiredArgs(srv.spi));
}
}
void _onTapAddServer() {
// final isMobile = ResponsiveBreakpoints.of(context).isMobile;
// if (isMobile) {
ServerEditPage.route.go(context);
// } else {
// _splitViewCtrl.replace(const ServerEditPage(
// key: ValueKey('addServer'),
// ));
// }
}
}
extension _Operation on _ServerPageState {
void _onTapSuspend(ServerState srv) {
_askFor(
func: () async {
if (Stores.setting.showSuspendTip.fetch()) {
await context.showRoundDialog(title: libL10n.attention, child: Text(l10n.suspendTip));
Stores.setting.showSuspendTip.put(false);
}
await srv.client?.execWithPwd(
ShellFunc.suspend.exec(srv.spi.id, systemType: srv.status.system, customDir: null),
context: context,
id: srv.id,
) ??
(null, '');
},
typ: l10n.suspend,
name: srv.spi.name,
);
}
void _onTapShutdown(ServerState srv) {
_askFor(
func: () async {
await srv.client?.execWithPwd(
ShellFunc.shutdown.exec(srv.spi.id, systemType: srv.status.system, customDir: null),
context: context,
id: srv.id,
);
},
typ: l10n.shutdown,
name: srv.spi.name,
);
}
void _onTapReboot(ServerState srv) {
_askFor(
func: () async {
await srv.client?.execWithPwd(
ShellFunc.reboot.exec(srv.spi.id, systemType: srv.status.system, customDir: null),
context: context,
id: srv.id,
) ??
(null, '');
},
typ: l10n.reboot,
name: srv.spi.name,
);
}
void _onTapEdit(ServerState srv) {
if (srv.canViewDetails) {
ServerDetailPage.route.go(context, SpiRequiredArgs(srv.spi));
} else {
ServerEditPage.route.go(context, args: SpiRequiredArgs(srv.spi));
}
}
}
extension _Utils on _ServerPageState {
List<String> _filterServers(List<String> order) {
final tag = _tag.value;
if (tag == TagSwitcher.kDefaultTag) return order;
return order.where((e) {
final tags = ref.read(serversProvider).servers[e]?.tags;
if (tags == null) return false;
return tags.contains(tag);
}).toList();
}
double? _calcCardHeight(ServerConn cs, bool flip) {
if (_textFactorDouble != 1.0) return null;
if (cs != ServerConn.finished) {
return _ServerPageState._kCardHeightMin;
}
if (flip) {
return _ServerPageState._kCardHeightFlip;
}
if (Stores.setting.moveServerFuncs.fetch()) {
return _ServerPageState._kCardHeightMoveOutFuncs;
}
return _ServerPageState._kCardHeightNormal;
}
void _askFor({required void Function() func, required String typ, required String name}) {
context.showRoundDialog(
title: libL10n.attention,
child: Text(libL10n.askContinue('$typ ${l10n.server}($name)')),
actions: Btn.ok(
onTap: () {
context.pop();
func();
},
).toList,
);
}
_CardNotifier _getCardNoti(String id) =>
_cardsStatus.putIfAbsent(id, () => _CardNotifier(const _CardStatus()));
void _updateOffset() {
if (!Stores.setting.fullScreenJitter.fetch()) return;
final x = MediaQuery.sizeOf(context).height * 0.03;
final r = math.Random().nextDouble();
final n = math.Random().nextBool() ? 1 : -1;
_offset = x * r * n;
}
void _updateTextScaler(double val) {
_textFactorDouble = val;
_textFactor = TextScaler.linear(_textFactorDouble);
}
void _startAvoidJitterTimer() {
if (!Stores.setting.fullScreenJitter.fetch()) return;
_timer = Timer.periodic(const Duration(seconds: 30), (_) {
if (mounted) {
_updateOffset();
setState(() {});
} else {
_timer?.cancel();
}
});
}
}
extension _ServerX on ServerState {
String? _getTopRightStr(Spi spi) {
if (status.err != null) {
return l10n.viewErr;
}
switch (conn) {
case ServerConn.disconnected:
return null;
case ServerConn.finished:
// Highest priority of temperature display
final cmdTemp = () {
final val = status.customCmds['server_card_top_right'];
if (val == null) return null;
// This returned value is used on server card top right, so it should
// be a single line string.
return val.split('\n').lastOrNull;
}();
final temperatureVal = () {
// Second priority
final preferTempDev = spi.custom?.preferTempDev;
if (preferTempDev != null) {
final preferTemp = status.sensors
.firstWhereOrNull((e) => e.device == preferTempDev)
?.summary
?.split(' ')
.firstOrNull;
if (preferTemp != null) {
return double.tryParse(preferTemp.replaceFirst('°C', ''));
}
}
// Last priority
final temp = status.temps.first;
if (temp != null) {
return temp;
}
return null;
}();
final upTime = status.more[StatusCmdType.uptime];
final items = [
cmdTemp ?? (temperatureVal != null ? '${temperatureVal.toStringAsFixed(1)}°C' : null),
upTime,
];
final str = items.where((e) => e != null && e.isNotEmpty).join(' | ');
if (str.isEmpty) return libL10n.empty;
return str;
case ServerConn.loading:
return null;
case ServerConn.connected:
return null;
case ServerConn.connecting:
return null;
case ServerConn.failed:
return libL10n.fail;
}
}
}