From 68734a9e52bd5e09714b034d7092ca2be9eb22eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:32:22 +0800 Subject: [PATCH] fix: disable command menu doesnt work (#852) --- lib/data/model/app/scripts/cmd_types.dart | 67 ++++++++-- .../model/app/scripts/script_builders.dart | 6 +- lib/view/page/server/edit.dart | 51 +++++--- test/disabled_cmd_types_test.dart | 119 ++++++++++++++++++ 4 files changed, 208 insertions(+), 35 deletions(-) create mode 100644 test/disabled_cmd_types_test.dart diff --git a/lib/data/model/app/scripts/cmd_types.dart b/lib/data/model/app/scripts/cmd_types.dart index 2e2b7ade..ab889476 100644 --- a/lib/data/model/app/scripts/cmd_types.dart +++ b/lib/data/model/app/scripts/cmd_types.dart @@ -1,20 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:icons_plus/icons_plus.dart'; import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/data/model/app/scripts/script_consts.dart'; import 'package:server_box/data/model/server/system.dart'; +/// Enum representing different command types for various systems +enum CmdTypeSys { + linux('Linux'), + bsd('BSD'), + windows('Windows'); + + final String sign; + const CmdTypeSys(this.sign); + + IconData get icon { + return switch (this) { + CmdTypeSys.linux => MingCute.linux_line, + CmdTypeSys.bsd => LineAwesome.freebsd, + CmdTypeSys.windows => MingCute.windows_line, + }; + } +} + /// Base class for all command type enums -abstract class CommandType implements Enum { +sealed class ShellCmdType implements Enum { String get cmd; - + /// Get command-specific separator String get separator; - + /// Get command-specific divider (separator with echo and formatting) String get divider; + + /// Get corresponding system type + CmdTypeSys get sysType; + + static Set get all { + return {...StatusCmdType.values, ...BSDStatusCmdType.values, ...WindowsStatusCmdType.values}; + } +} + +extension ShellCmdTypeX on ShellCmdType { + /// Display name of the command type + String get displayName => '${sysType.sign}.$name'; } /// Linux/Unix status commands -enum StatusCmdType implements CommandType { +enum StatusCmdType implements ShellCmdType { echo('echo ${SystemType.linuxSign}'), time('date +%s'), net('cat /proc/net/dev'), @@ -90,16 +122,19 @@ enum StatusCmdType implements CommandType { final String cmd; const StatusCmdType(this.cmd); - + @override String get separator => ScriptConstants.getCmdSeparator(name); - + @override String get divider => ScriptConstants.getCmdDivider(name); + + @override + CmdTypeSys get sysType => CmdTypeSys.linux; } /// BSD/macOS status commands -enum BSDStatusCmdType implements CommandType { +enum BSDStatusCmdType implements ShellCmdType { echo('echo ${SystemType.bsdSign}'), time('date +%s'), net('netstat -ibn'), @@ -115,16 +150,19 @@ enum BSDStatusCmdType implements CommandType { final String cmd; const BSDStatusCmdType(this.cmd); - + @override String get separator => ScriptConstants.getCmdSeparator(name); - + @override String get divider => ScriptConstants.getCmdDivider(name); + + @override + CmdTypeSys get sysType => CmdTypeSys.bsd; } /// Windows PowerShell status commands -enum WindowsStatusCmdType implements CommandType { +enum WindowsStatusCmdType implements ShellCmdType { echo('echo ${SystemType.windowsSign}'), time('[DateTimeOffset]::UtcNow.ToUnixTimeSeconds()'), @@ -244,12 +282,15 @@ enum WindowsStatusCmdType implements CommandType { final String cmd; const WindowsStatusCmdType(this.cmd); - + @override String get separator => ScriptConstants.getCmdSeparator(name); - + @override String get divider => ScriptConstants.getCmdDivider(name); + + @override + CmdTypeSys get sysType => CmdTypeSys.windows; } /// Extensions for StatusCmdType @@ -266,7 +307,7 @@ extension StatusCmdTypeX on StatusCmdType { } /// Extension for CommandType to find content in parsed map -extension CommandTypeX on CommandType { +extension CommandTypeX on ShellCmdType { /// Find the command output from the parsed script output map String findInMap(Map parsedOutput) { return parsedOutput[name] ?? ''; diff --git a/lib/data/model/app/scripts/script_builders.dart b/lib/data/model/app/scripts/script_builders.dart index e4a16307..4271f7b7 100644 --- a/lib/data/model/app/scripts/script_builders.dart +++ b/lib/data/model/app/scripts/script_builders.dart @@ -106,7 +106,7 @@ switch (\$args[0]) { /// Get Windows status command with command-specific separators String _getWindowsStatusCommand({required List disabledCmdTypes}) { - final cmdTypes = WindowsStatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.name)); + final cmdTypes = WindowsStatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.displayName)); return cmdTypes.map((e) => '${e.divider}${e.cmd}').join('').trimRight(); // Remove trailing divider } } @@ -196,10 +196,10 @@ esac'''); /// Get Unix status command with OS detection String _getUnixStatusCommand({required List disabledCmdTypes}) { // Generate command lists with command-specific separators, filtering disabled commands - final filteredLinuxCmdTypes = StatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.name)); + final filteredLinuxCmdTypes = StatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.displayName)); final linuxCommands = filteredLinuxCmdTypes.map((e) => '${e.divider}${e.cmd}').join('').trimRight(); - final filteredBsdCmdTypes = BSDStatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.name)); + final filteredBsdCmdTypes = BSDStatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.displayName)); final bsdCommands = filteredBsdCmdTypes.map((e) => '${e.divider}${e.cmd}').join('').trimRight(); return ''' diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index 8f4f16a2..0f39b683 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -597,21 +597,16 @@ extension on _ServerEditPageState { } void _onTapDisabledCmdTypes() async { - final allCmdTypes = {}; - allCmdTypes.addAll(StatusCmdType.values.map((e) => e.name)); - allCmdTypes.addAll(BSDStatusCmdType.values.map((e) => e.name)); - allCmdTypes.addAll(WindowsStatusCmdType.values.map((e) => e.name)); + final allCmdTypes = ShellCmdType.all; // [TimeSeq] depends on the `time` cmd type, so it should be removed from the list - allCmdTypes.remove(StatusCmdType.time.name); + allCmdTypes.remove(StatusCmdType.time); - final selected = await _showCmdTypesDialog(allCmdTypes); - if (selected == null) return; - _disabledCmdTypes.value = selected; + await _showCmdTypesDialog(allCmdTypes); } - Future?> _showCmdTypesDialog(Set allCmdTypes) { - return context.showRoundDialog>( + Future _showCmdTypesDialog(Set allCmdTypes) { + return context.showRoundDialog( title: '${libL10n.disabled} ${l10n.cmd}', child: SizedBox( width: 270, @@ -622,16 +617,30 @@ extension on _ServerEditPageState { itemBuilder: (context, index) { final cmdType = allCmdTypes.elementAtOrNull(index); if (cmdType == null) return UIs.placeholder; - return CheckboxListTile( - title: Text(cmdType), - value: disabled.contains(cmdType), - onChanged: (value) { - if (value == null) return; - if (value) { - _disabledCmdTypes.value.add(cmdType); + final display = cmdType.displayName; + return ListTile( + leading: Icon(cmdType.sysType.icon, size: 20), + title: Text(cmdType.name, style: const TextStyle(fontSize: 16)), + trailing: Checkbox( + value: disabled.contains(display), + onChanged: (value) { + if (value == null) return; + if (value) { + _disabledCmdTypes.value.add(display); + } else { + _disabledCmdTypes.value.remove(display); + } + _disabledCmdTypes.notify(); + }, + ), + onTap: () { + final isDisabled = disabled.contains(display); + if (isDisabled) { + _disabledCmdTypes.value.remove(display); } else { - _disabledCmdTypes.value.remove(cmdType); + _disabledCmdTypes.value.add(display); } + _disabledCmdTypes.notify(); }, ); }, @@ -764,6 +773,10 @@ extension on _ServerEditPageState { _scriptDirCtrl.text = spi.custom?.scriptDir ?? ''; _systemType.value = spi.customSystemType; - _disabledCmdTypes.value = spi.disabledCmdTypes?.toSet() ?? {}; + + final disabledCmdTypes = spi.disabledCmdTypes?.toSet() ?? {}; + final allAvailableCmdTypes = ShellCmdType.all.map((e) => e.displayName); + disabledCmdTypes.removeWhere((e) => !allAvailableCmdTypes.contains(e)); + _disabledCmdTypes.value = disabledCmdTypes; } } diff --git a/test/disabled_cmd_types_test.dart b/test/disabled_cmd_types_test.dart new file mode 100644 index 00000000..998cdcbe --- /dev/null +++ b/test/disabled_cmd_types_test.dart @@ -0,0 +1,119 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:server_box/data/model/app/scripts/cmd_types.dart'; +import 'package:server_box/data/model/app/scripts/shell_func.dart'; +import 'package:server_box/data/model/server/system.dart'; + +void main() { + group('disabledCmdTypes filtering', () { + test('filters Linux status commands when disabled', () { + final disabled = { + StatusCmdType.net.displayName, + StatusCmdType.sys.displayName, + }.toList(); + + final script = ShellFuncManager.allScript( + null, + systemType: SystemType.linux, + disabledCmdTypes: disabled, + ); + + // Linux-specific commands should be removed + expect(script, isNot(contains('cat /proc/net/dev'))); // net + expect(script, isNot(contains('cat /etc/*-release | grep ^PRETTY_NAME'))); // sys + + // Other commands should remain + expect(script, contains('uptime')); + expect(script, contains('date +%s')); + }); + + test('filters BSD status commands when disabled', () { + final disabled = { + BSDStatusCmdType.sys.displayName, + BSDStatusCmdType.mem.displayName, + }.toList(); + + final script = ShellFuncManager.allScript( + null, + systemType: SystemType.linux, // Unix builder is used for Linux/BSD + disabledCmdTypes: disabled, + ); + + // BSD-specific commands should be removed + expect(script, isNot(contains('uname -or'))); // sys + expect(script, isNot(contains('top -l 1 | grep PhysMem'))); // mem + + // Linux equivalents should remain + expect(script, contains('cat /etc/*-release | grep ^PRETTY_NAME')); + expect(script, contains("cat /proc/meminfo | grep -E 'Mem|Swap'")); + }); + + test('filters Windows status commands when disabled', () { + final disabled = { + WindowsStatusCmdType.net.displayName, + WindowsStatusCmdType.uptime.displayName, + WindowsStatusCmdType.temp.displayName, + }.toList(); + + final script = ShellFuncManager.allScript( + null, + systemType: SystemType.windows, + disabledCmdTypes: disabled, + ); + + // Windows-specific commands should be removed + expect(script, isNot(contains('LastBootUpTime'))); // uptime + expect(script, isNot(contains('MSAcpi_ThermalZoneTemperature'))); // temp + + // Other Windows commands should remain + expect(script, contains('Get-Process')); + expect(script, contains('Get-WmiObject -Class Win32_OperatingSystem')); + }); + + test('ignores disabled names for other platforms', () { + final disabled = { + WindowsStatusCmdType.sys.displayName, + WindowsStatusCmdType.net.displayName, + }.toList(); + + final script = ShellFuncManager.allScript( + null, + systemType: SystemType.linux, + disabledCmdTypes: disabled, + ); + + // Linux commands should not be affected by Windows-only disables + expect(script, contains('cat /etc/*-release | grep ^PRETTY_NAME')); + expect(script, contains('cat /proc/net/dev')); + }); + + test('disabling all status commands removes separators', () { + final allUnixDisabled = { + ...StatusCmdType.values.map((e) => e.displayName), + ...BSDStatusCmdType.values.map((e) => e.displayName), + }.toList(); + + final unixScript = ShellFuncManager.allScript( + null, + systemType: SystemType.linux, + disabledCmdTypes: allUnixDisabled, + ); + + // No status separators for Unix script + expect(unixScript, isNot(contains('SrvBoxSep.'))); + + final allWinDisabled = { + ...WindowsStatusCmdType.values.map((e) => e.displayName), + }.toList(); + + final windowsScript = ShellFuncManager.allScript( + null, + systemType: SystemType.windows, + disabledCmdTypes: allWinDisabled, + ); + + // No status separators for Windows script + expect(windowsScript, isNot(contains('SrvBoxSep.'))); + }); + }); +} +