mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
fix: macOS ssh term unusable (#838)
This commit is contained in:
249
lib/data/model/app/scripts/cmd_types.dart
Normal file
249
lib/data/model/app/scripts/cmd_types.dart
Normal file
@@ -0,0 +1,249 @@
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
|
||||
/// Base class for all command type enums
|
||||
abstract class CommandType {
|
||||
String get cmd;
|
||||
}
|
||||
|
||||
/// Linux/Unix status commands
|
||||
enum StatusCmdType implements CommandType {
|
||||
echo('echo ${SystemType.linuxSign}'),
|
||||
time('date +%s'),
|
||||
net('cat /proc/net/dev'),
|
||||
sys('cat /etc/*-release | grep ^PRETTY_NAME'),
|
||||
cpu('cat /proc/stat | grep cpu'),
|
||||
uptime('uptime'),
|
||||
conn('cat /proc/net/snmp'),
|
||||
disk(
|
||||
'lsblk --bytes --json --output '
|
||||
'FSTYPE,PATH,NAME,KNAME,MOUNTPOINT,FSSIZE,FSUSED,FSAVAIL,FSUSE%,UUID',
|
||||
),
|
||||
mem("cat /proc/meminfo | grep -E 'Mem|Swap'"),
|
||||
tempType('cat /sys/class/thermal/thermal_zone*/type'),
|
||||
tempVal('cat /sys/class/thermal/thermal_zone*/temp'),
|
||||
host('cat /etc/hostname'),
|
||||
diskio('cat /proc/diskstats'),
|
||||
|
||||
/// Get battery information from Linux power supply subsystem
|
||||
///
|
||||
/// Reads battery data from sysfs power supply interface:
|
||||
/// - Iterates through all power supply devices in /sys/class/power_supply/
|
||||
/// - Each device has a uevent file with key-value pairs of power supply properties
|
||||
/// - Includes battery level, status, technology type, and other attributes
|
||||
/// - Works with laptops, UPS devices, and other power supplies
|
||||
/// - Adds echo after each file to separate multiple power supplies
|
||||
/// - Returns empty if no power supplies are detected (e.g., desktop systems)
|
||||
battery('for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
||||
|
||||
/// Get NVIDIA GPU information using nvidia-smi in XML format
|
||||
/// Requires NVIDIA drivers and nvidia-smi utility to be installed
|
||||
nvidia('nvidia-smi -q -x'),
|
||||
|
||||
/// Get AMD GPU information using multiple fallback methods
|
||||
///
|
||||
/// This command tries three different AMD monitoring tools in order of preference:
|
||||
/// 1. amd-smi: Modern AMD System Management Interface (ROCm 5.0+)
|
||||
/// - Uses 'amd-smi list --json' to get GPU list
|
||||
/// - Uses 'amd-smi metric --json' to get performance metrics
|
||||
/// 2. rocm-smi: ROCm System Management Interface (older versions)
|
||||
/// - First tries '--json' output format if supported
|
||||
/// - Falls back to human-readable format with comprehensive metrics
|
||||
/// 3. radeontop: Real-time GPU usage monitor for older AMD cards
|
||||
/// - Uses 2-second timeout to avoid hanging
|
||||
/// - Skips header line with 'tail -n +2'
|
||||
/// - Outputs single line of usage data
|
||||
///
|
||||
/// If none of these tools are available, outputs error message
|
||||
amd(
|
||||
'if command -v amd-smi >/dev/null 2>&1; then '
|
||||
'amd-smi list --json && amd-smi metric --json; '
|
||||
'elif command -v rocm-smi >/dev/null 2>&1; then '
|
||||
'rocm-smi --json || rocm-smi --showunique --showuse --showtemp '
|
||||
'--showfan --showclocks --showmemuse --showpower; '
|
||||
'elif command -v radeontop >/dev/null 2>&1; then '
|
||||
'timeout 2s radeontop -d - -l 1 | tail -n +2; '
|
||||
'else echo "No AMD GPU monitoring tools found"; fi',
|
||||
),
|
||||
sensors('sensors'),
|
||||
|
||||
/// Get SMART disk health information for all storage devices
|
||||
///
|
||||
/// Uses a combination of lsblk and smartctl to collect disk health data:
|
||||
/// - lsblk -dn -o KNAME lists all block devices (kernel names only, no dependencies)
|
||||
/// - For each device, runs smartctl with -a (all info) and -j (JSON output)
|
||||
/// - Targets raw device nodes in /dev/ (e.g., /dev/sda, /dev/nvme0n1)
|
||||
/// - Adds echo after each device to separate output blocks
|
||||
/// - May require elevated privileges for some drives
|
||||
/// - smartctl must be installed (part of smartmontools package)
|
||||
diskSmart('for d in \$(lsblk -dn -o KNAME); do smartctl -a -j /dev/\$d; echo; done'),
|
||||
cpuBrand('cat /proc/cpuinfo | grep "model name"');
|
||||
|
||||
@override
|
||||
final String cmd;
|
||||
|
||||
const StatusCmdType(this.cmd);
|
||||
}
|
||||
|
||||
/// BSD/macOS status commands
|
||||
enum BSDStatusCmdType implements CommandType {
|
||||
echo('echo ${SystemType.bsdSign}'),
|
||||
time('date +%s'),
|
||||
net('netstat -ibn'),
|
||||
sys('uname -or'),
|
||||
cpu('top -l 1 | grep "CPU usage"'),
|
||||
uptime('uptime'),
|
||||
disk('df -k'), // Keep df -k for BSD systems as lsblk is not available on macOS/BSD
|
||||
mem('top -l 1 | grep PhysMem'),
|
||||
host('hostname'),
|
||||
cpuBrand('sysctl -n machdep.cpu.brand_string');
|
||||
|
||||
@override
|
||||
final String cmd;
|
||||
|
||||
const BSDStatusCmdType(this.cmd);
|
||||
}
|
||||
|
||||
/// Windows PowerShell status commands
|
||||
enum WindowsStatusCmdType implements CommandType {
|
||||
echo('echo ${SystemType.windowsSign}'),
|
||||
time('[DateTimeOffset]::UtcNow.ToUnixTimeSeconds()'),
|
||||
|
||||
/// Get network interface statistics using Windows Performance Counters
|
||||
///
|
||||
/// Uses Get-Counter to collect network I/O metrics from all network interfaces:
|
||||
/// - Collects bytes received and sent per second for all network interfaces
|
||||
/// - Takes 2 samples with 1 second interval to calculate rates
|
||||
/// - Outputs results in JSON format for easy parsing
|
||||
/// - Counter paths use double backslashes to escape PowerShell string literals
|
||||
net(
|
||||
r'Get-Counter -Counter '
|
||||
r'"\\NetworkInterface(*)\\Bytes Received/sec", '
|
||||
r'"\\NetworkInterface(*)\\Bytes Sent/sec" '
|
||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
||||
),
|
||||
sys('(Get-ComputerInfo).OsName'),
|
||||
cpu(
|
||||
'Get-WmiObject -Class Win32_Processor | '
|
||||
'Select-Object Name, LoadPercentage | ConvertTo-Json',
|
||||
),
|
||||
uptime('(Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime'),
|
||||
conn('(netstat -an | findstr ESTABLISHED | Measure-Object -Line).Count'),
|
||||
disk(
|
||||
'Get-WmiObject -Class Win32_LogicalDisk | '
|
||||
'Select-Object DeviceID, Size, FreeSpace, FileSystem | ConvertTo-Json',
|
||||
),
|
||||
mem(
|
||||
'Get-WmiObject -Class Win32_OperatingSystem | '
|
||||
'Select-Object TotalVisibleMemorySize, FreePhysicalMemory | ConvertTo-Json',
|
||||
),
|
||||
|
||||
/// Get system temperature using Windows Management Instrumentation (WMI)
|
||||
///
|
||||
/// Queries the MSAcpi_ThermalZoneTemperature class from the WMI root/wmi namespace:
|
||||
/// - Uses Get-CimInstance to access ACPI thermal zone data
|
||||
/// - ErrorAction SilentlyContinue prevents errors on systems without thermal sensors
|
||||
/// - Converts temperature from 10ths of Kelvin to Celsius: (temp - 2732) / 10
|
||||
/// - Uses calculated property to perform the temperature conversion
|
||||
/// - Returns JSON with InstanceName and converted Temperature values
|
||||
/// - May return empty result on systems without ACPI thermal sensor support
|
||||
temp(
|
||||
'Get-CimInstance -ClassName MSAcpi_ThermalZoneTemperature '
|
||||
'-Namespace root/wmi -ErrorAction SilentlyContinue | '
|
||||
'Select-Object InstanceName, @{Name=\'Temperature\';'
|
||||
'Expression={[math]::Round((\$_.CurrentTemperature - 2732) / 10, 1)}} | '
|
||||
'ConvertTo-Json',
|
||||
),
|
||||
host(r'Write-Output $env:COMPUTERNAME'),
|
||||
|
||||
/// Get disk I/O statistics using Windows Performance Counters
|
||||
///
|
||||
/// Uses Get-Counter to collect disk I/O metrics from all physical disks:
|
||||
/// - Monitors read and write bytes per second for all physical disks
|
||||
/// - Takes 2 samples with 1 second interval to calculate I/O rates
|
||||
/// - Physical disk counters provide hardware-level I/O statistics
|
||||
/// - Outputs results in JSON format for parsing
|
||||
/// - Counter names use wildcard (*) to capture all disk instances
|
||||
diskio(
|
||||
r'Get-Counter -Counter '
|
||||
r'"\\PhysicalDisk(*)\\Disk Read Bytes/sec", '
|
||||
r'"\\PhysicalDisk(*)\\Disk Write Bytes/sec" '
|
||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
||||
),
|
||||
battery(
|
||||
'Get-WmiObject -Class Win32_Battery | '
|
||||
'Select-Object EstimatedChargeRemaining, BatteryStatus | ConvertTo-Json',
|
||||
),
|
||||
|
||||
/// Get NVIDIA GPU information on Windows
|
||||
///
|
||||
/// Checks if nvidia-smi is available before attempting to use it:
|
||||
/// - Uses Get-Command to test if nvidia-smi.exe exists in PATH
|
||||
/// - ErrorAction SilentlyContinue prevents PowerShell errors if not found
|
||||
/// - If available, runs nvidia-smi with -q (query) and -x (XML output) flags
|
||||
/// - If not available, outputs standard error message for consistent handling
|
||||
nvidia(
|
||||
'if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) { '
|
||||
'nvidia-smi -q -x } else { echo "NVIDIA driver not found" }',
|
||||
),
|
||||
|
||||
/// Get AMD GPU information on Windows
|
||||
///
|
||||
/// Checks for AMD monitoring tools using similar pattern to Linux version:
|
||||
/// - Uses Get-Command to test if amd-smi.exe exists in PATH
|
||||
/// - ErrorAction SilentlyContinue prevents PowerShell errors if not found
|
||||
/// - If available, runs amd-smi list command with JSON output
|
||||
/// - If not available, outputs standard error message for consistent handling
|
||||
/// - Windows version is simpler than Linux due to fewer AMD tool variations
|
||||
amd(
|
||||
'if (Get-Command amd-smi -ErrorAction SilentlyContinue) { '
|
||||
'amd-smi list --json } else { echo "AMD driver not found" }',
|
||||
),
|
||||
sensors(
|
||||
'Get-CimInstance -ClassName Win32_TemperatureProbe '
|
||||
'-ErrorAction SilentlyContinue | '
|
||||
'Select-Object Name, CurrentReading | ConvertTo-Json',
|
||||
),
|
||||
|
||||
/// Get SMART disk health information on Windows using Storage cmdlets
|
||||
///
|
||||
/// Uses Windows PowerShell storage management cmdlets:
|
||||
/// - Get-PhysicalDisk retrieves all physical storage devices
|
||||
/// - Get-StorageReliabilityCounter gets SMART health data via pipeline
|
||||
/// - Selects key health metrics: DeviceId, Temperature, TemperatureMax, Wear, PowerOnHours
|
||||
/// - Outputs results in JSON format for consistent parsing
|
||||
/// - Works with NVMe, SATA, and other storage interfaces supported by Windows
|
||||
/// - May require elevated privileges on some systems
|
||||
diskSmart(
|
||||
'Get-PhysicalDisk | Get-StorageReliabilityCounter | '
|
||||
'Select-Object DeviceId, Temperature, TemperatureMax, Wear, PowerOnHours | '
|
||||
'ConvertTo-Json',
|
||||
),
|
||||
cpuBrand('(Get-WmiObject -Class Win32_Processor).Name');
|
||||
|
||||
@override
|
||||
final String cmd;
|
||||
|
||||
const WindowsStatusCmdType(this.cmd);
|
||||
}
|
||||
|
||||
/// Extensions for StatusCmdType
|
||||
extension StatusCmdTypeX on StatusCmdType {
|
||||
String get i18n => switch (this) {
|
||||
StatusCmdType.sys => l10n.system,
|
||||
StatusCmdType.host => l10n.host,
|
||||
StatusCmdType.uptime => l10n.uptime,
|
||||
StatusCmdType.battery => l10n.battery,
|
||||
StatusCmdType.sensors => l10n.sensors,
|
||||
StatusCmdType.disk => l10n.disk,
|
||||
final val => val.name,
|
||||
};
|
||||
}
|
||||
|
||||
/// Generic extension for Enum types
|
||||
extension EnumX on Enum {
|
||||
/// Find out the required segment from [segments]
|
||||
String find(List<String> segments) {
|
||||
return segments[index];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||
import 'package:server_box/data/model/app/scripts/shell_func.dart';
|
||||
|
||||
/// Abstract base class for platform-specific script builders
|
||||
abstract class ScriptBuilder {
|
||||
@@ -7,21 +8,24 @@ abstract class ScriptBuilder {
|
||||
|
||||
/// Generate a complete script for all shell functions
|
||||
String buildScript(Map<String, String>? customCmds);
|
||||
|
||||
|
||||
/// Get the script file name for this platform
|
||||
String get scriptFileName;
|
||||
|
||||
|
||||
/// Get the command to install the script
|
||||
String getInstallCommand(String scriptDir, String scriptPath);
|
||||
|
||||
|
||||
/// Get the execution command for a specific function
|
||||
String getExecCommand(String scriptPath, ShellFunc func);
|
||||
|
||||
|
||||
/// Get custom commands string for this platform
|
||||
String getCustomCmdsString(
|
||||
ShellFunc func,
|
||||
Map<String, String>? customCmds,
|
||||
);
|
||||
String getCustomCmdsString(ShellFunc func, Map<String, String>? customCmds);
|
||||
|
||||
/// Get the script header for this platform
|
||||
String get scriptHeader;
|
||||
|
||||
/// Get the command divider for this platform
|
||||
String get cmdDivider => ScriptConstants.cmdDivider;
|
||||
}
|
||||
|
||||
/// Windows PowerShell script builder
|
||||
@@ -29,13 +33,16 @@ class WindowsScriptBuilder extends ScriptBuilder {
|
||||
const WindowsScriptBuilder();
|
||||
|
||||
@override
|
||||
String get scriptFileName => 'srvboxm_v${BuildData.script}.ps1';
|
||||
String get scriptFileName => ScriptConstants.scriptFileWindows;
|
||||
|
||||
@override
|
||||
String get scriptHeader => ScriptConstants.windowsScriptHeader;
|
||||
|
||||
@override
|
||||
String getInstallCommand(String scriptDir, String scriptPath) {
|
||||
return 'New-Item -ItemType Directory -Force -Path \'$scriptDir\' | Out-Null; '
|
||||
'\$content = [System.Console]::In.ReadToEnd(); '
|
||||
'Set-Content -Path \'$scriptPath\' -Value \$content -Encoding UTF8';
|
||||
'\$content = [System.Console]::In.ReadToEnd(); '
|
||||
'Set-Content -Path \'$scriptPath\' -Value \$content -Encoding UTF8';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -44,10 +51,7 @@ class WindowsScriptBuilder extends ScriptBuilder {
|
||||
}
|
||||
|
||||
@override
|
||||
String getCustomCmdsString(
|
||||
ShellFunc func,
|
||||
Map<String, String>? customCmds,
|
||||
) {
|
||||
String getCustomCmdsString(ShellFunc func, Map<String, String>? customCmds) {
|
||||
if (func == ShellFunc.status && customCmds != null && customCmds.isNotEmpty) {
|
||||
return '\n${customCmds.values.map((cmd) => '\t$cmd').join('\n')}';
|
||||
}
|
||||
@@ -57,13 +61,7 @@ class WindowsScriptBuilder extends ScriptBuilder {
|
||||
@override
|
||||
String buildScript(Map<String, String>? customCmds) {
|
||||
final sb = StringBuffer();
|
||||
sb.write('''
|
||||
# PowerShell script for ServerBox app v1.0.${BuildData.build}
|
||||
# DO NOT delete this file while app is running
|
||||
|
||||
\$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
''');
|
||||
sb.write(scriptHeader);
|
||||
|
||||
// Write each function
|
||||
for (final func in ShellFunc.values) {
|
||||
@@ -93,22 +91,26 @@ switch (\$args[0]) {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/// Get Windows-specific command for a shell function
|
||||
String _getWindowsCommand(ShellFunc func) => switch (func) {
|
||||
ShellFunc.status => WindowsStatusCmdType.values.map((e) => e.cmd).join(ShellFunc.cmdDivider),
|
||||
ShellFunc.status => WindowsStatusCmdType.values.map((e) => e.cmd).join(cmdDivider),
|
||||
ShellFunc.process => 'Get-Process | Select-Object ProcessName, Id, CPU, WorkingSet | ConvertTo-Json',
|
||||
ShellFunc.shutdown => 'Stop-Computer -Force',
|
||||
ShellFunc.reboot => 'Restart-Computer -Force',
|
||||
ShellFunc.suspend =>
|
||||
ShellFunc.suspend =>
|
||||
'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Application]::SetSuspendState(\'Suspend\', \$false, \$false)',
|
||||
};
|
||||
}
|
||||
|
||||
/// Unix shell script builder
|
||||
/// Unix shell script builder
|
||||
class UnixScriptBuilder extends ScriptBuilder {
|
||||
const UnixScriptBuilder();
|
||||
|
||||
@override
|
||||
String get scriptFileName => 'srvboxm_v${BuildData.script}.sh';
|
||||
String get scriptFileName => ScriptConstants.scriptFile;
|
||||
|
||||
@override
|
||||
String get scriptHeader => ScriptConstants.unixScriptHeader;
|
||||
|
||||
@override
|
||||
String getInstallCommand(String scriptDir, String scriptPath) {
|
||||
@@ -125,12 +127,9 @@ chmod 755 $scriptPath
|
||||
}
|
||||
|
||||
@override
|
||||
String getCustomCmdsString(
|
||||
ShellFunc func,
|
||||
Map<String, String>? customCmds,
|
||||
) {
|
||||
String getCustomCmdsString(ShellFunc func, Map<String, String>? customCmds) {
|
||||
if (func == ShellFunc.status && customCmds != null && customCmds.isNotEmpty) {
|
||||
return '${ShellFunc.cmdDivider}\n\t${customCmds.values.join(ShellFunc.cmdDivider)}';
|
||||
return '$cmdDivider\n\t${customCmds.values.join(cmdDivider)}';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -138,25 +137,7 @@ chmod 755 $scriptPath
|
||||
@override
|
||||
String buildScript(Map<String, String>? customCmds) {
|
||||
final sb = StringBuffer();
|
||||
sb.write('''
|
||||
#!/bin/sh
|
||||
# Script for ServerBox app v1.0.${BuildData.build}
|
||||
# DO NOT delete this file while app is running
|
||||
|
||||
export LANG=en_US.UTF-8
|
||||
|
||||
# If macSign & bsdSign are both empty, then it's linux
|
||||
macSign=\$(uname -a 2>&1 | grep "Darwin")
|
||||
bsdSign=\$(uname -a 2>&1 | grep "BSD")
|
||||
|
||||
# Link /bin/sh to busybox?
|
||||
isBusybox=\$(ls -l /bin/sh | grep "busybox")
|
||||
|
||||
userId=\$(id -u)
|
||||
|
||||
exec 2>/dev/null
|
||||
|
||||
''');
|
||||
sb.write(scriptHeader);
|
||||
// Write each function
|
||||
for (final func in ShellFunc.values) {
|
||||
final customCmdsStr = getCustomCmdsString(func, customCmds);
|
||||
@@ -186,17 +167,40 @@ esac''');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/// Get Unix-specific command for a shell function
|
||||
String _getUnixCommand(ShellFunc func) {
|
||||
switch (func) {
|
||||
case ShellFunc.status:
|
||||
return '''
|
||||
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||
\t${StatusCmdType.values.map((e) => e.cmd).join(ShellFunc.cmdDivider)}
|
||||
else
|
||||
\t${BSDStatusCmdType.values.map((e) => e.cmd).join(ShellFunc.cmdDivider)}
|
||||
fi''';
|
||||
return _getUnixStatusCommand();
|
||||
case ShellFunc.process:
|
||||
return '''
|
||||
return _getUnixProcessCommand();
|
||||
case ShellFunc.shutdown:
|
||||
return _getUnixShutdownCommand();
|
||||
case ShellFunc.reboot:
|
||||
return _getUnixRebootCommand();
|
||||
case ShellFunc.suspend:
|
||||
return _getUnixSuspendCommand();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get Unix status command with OS detection
|
||||
String _getUnixStatusCommand() {
|
||||
// Generate command lists for better readability
|
||||
final linuxCommands = StatusCmdType.values.map((e) => e.cmd).join(cmdDivider);
|
||||
|
||||
final bsdCommands = BSDStatusCmdType.values.map((e) => e.cmd).join(cmdDivider);
|
||||
|
||||
return '''
|
||||
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||
\t$linuxCommands
|
||||
else
|
||||
\t$bsdCommands
|
||||
fi''';
|
||||
}
|
||||
|
||||
/// Get Unix process command with busybox detection
|
||||
String _getUnixProcessCommand() {
|
||||
return '''
|
||||
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||
\tif [ "\$isBusybox" != "" ]; then
|
||||
\t\tps w
|
||||
@@ -205,30 +209,37 @@ if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||
\tfi
|
||||
else
|
||||
\tps -ax
|
||||
fi
|
||||
''';
|
||||
case ShellFunc.shutdown:
|
||||
return '''
|
||||
fi''';
|
||||
}
|
||||
|
||||
/// Get Unix shutdown command with privilege detection
|
||||
String _getUnixShutdownCommand() {
|
||||
return '''
|
||||
if [ "\$userId" = "0" ]; then
|
||||
\tshutdown -h now
|
||||
else
|
||||
\tsudo -S shutdown -h now
|
||||
fi''';
|
||||
case ShellFunc.reboot:
|
||||
return '''
|
||||
}
|
||||
|
||||
/// Get Unix reboot command with privilege detection
|
||||
String _getUnixRebootCommand() {
|
||||
return '''
|
||||
if [ "\$userId" = "0" ]; then
|
||||
\treboot
|
||||
else
|
||||
\tsudo -S reboot
|
||||
fi''';
|
||||
case ShellFunc.suspend:
|
||||
return '''
|
||||
}
|
||||
|
||||
/// Get Unix suspend command with privilege detection
|
||||
String _getUnixSuspendCommand() {
|
||||
return '''
|
||||
if [ "\$userId" = "0" ]; then
|
||||
\tsystemctl suspend
|
||||
else
|
||||
\tsudo -S systemctl suspend
|
||||
fi''';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +247,13 @@ fi''';
|
||||
class ScriptBuilderFactory {
|
||||
const ScriptBuilderFactory._();
|
||||
|
||||
/// Get the appropriate script builder based on platform
|
||||
static ScriptBuilder getBuilder(bool isWindows) {
|
||||
return isWindows ? const WindowsScriptBuilder() : const UnixScriptBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all available builders (useful for testing)
|
||||
static List<ScriptBuilder> getAllBuilders() {
|
||||
return const [WindowsScriptBuilder(), UnixScriptBuilder()];
|
||||
}
|
||||
}
|
||||
100
lib/data/model/app/scripts/script_consts.dart
Normal file
100
lib/data/model/app/scripts/script_consts.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
|
||||
/// Constants used throughout the script system
|
||||
class ScriptConstants {
|
||||
const ScriptConstants._();
|
||||
|
||||
// Script file names
|
||||
static const String scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
||||
static const String scriptFileWindows = 'srvboxm_v${BuildData.script}.ps1';
|
||||
|
||||
// Script directories
|
||||
static const String scriptDirHome = '~/.config/server_box';
|
||||
static const String scriptDirTmp = '/tmp/server_box';
|
||||
static const String scriptDirHomeWindows = '%USERPROFILE%/.config/server_box';
|
||||
static const String scriptDirTmpWindows = '%TEMP%/server_box';
|
||||
|
||||
// Command separators and dividers
|
||||
static const String separator = 'SrvBoxSep';
|
||||
|
||||
/// The suffix `\t` is for formatting
|
||||
static const String cmdDivider = '\necho $separator\n\t';
|
||||
|
||||
// Path separators
|
||||
static const String unixPathSeparator = '/';
|
||||
static const String windowsPathSeparator = '\\';
|
||||
|
||||
// Script headers
|
||||
static const String unixScriptHeader =
|
||||
'''
|
||||
#!/bin/sh
|
||||
# Script for ServerBox app v1.0.${BuildData.build}
|
||||
# DO NOT delete this file while app is running
|
||||
|
||||
export LANG=en_US.UTF-8
|
||||
|
||||
# If macSign & bsdSign are both empty, then it's linux
|
||||
macSign=\$(uname -a 2>&1 | grep "Darwin")
|
||||
bsdSign=\$(uname -a 2>&1 | grep "BSD")
|
||||
|
||||
# Link /bin/sh to busybox?
|
||||
isBusybox=\$(ls -l /bin/sh | grep "busybox")
|
||||
|
||||
userId=\$(id -u)
|
||||
|
||||
exec 2>/dev/null
|
||||
|
||||
''';
|
||||
|
||||
static const String windowsScriptHeader =
|
||||
'''
|
||||
# PowerShell script for ServerBox app v1.0.${BuildData.build}
|
||||
# DO NOT delete this file while app is running
|
||||
|
||||
\$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
''';
|
||||
}
|
||||
|
||||
/// Script path configuration and management
|
||||
class ScriptPaths {
|
||||
ScriptPaths._();
|
||||
|
||||
static final Map<String, String> _scriptDirMap = <String, String>{};
|
||||
|
||||
/// Get the script directory for the given [id].
|
||||
///
|
||||
/// Default is [ScriptConstants.scriptDirTmp]/[ScriptConstants.scriptFile],
|
||||
/// if this path is not accessible, it will be changed to
|
||||
/// [ScriptConstants.scriptDirHome]/[ScriptConstants.scriptFile].
|
||||
static String getScriptDir(String id, {bool isWindows = false}) {
|
||||
final defaultTmpDir = isWindows ? ScriptConstants.scriptDirTmpWindows : ScriptConstants.scriptDirTmp;
|
||||
_scriptDirMap[id] ??= defaultTmpDir;
|
||||
return _scriptDirMap[id]!;
|
||||
}
|
||||
|
||||
/// Switch between tmp and home directories for script storage
|
||||
static String switchScriptDir(String id, {bool isWindows = false}) {
|
||||
return switch (_scriptDirMap[id]) {
|
||||
ScriptConstants.scriptDirTmp => _scriptDirMap[id] = ScriptConstants.scriptDirHome,
|
||||
ScriptConstants.scriptDirTmpWindows => _scriptDirMap[id] = ScriptConstants.scriptDirHomeWindows,
|
||||
ScriptConstants.scriptDirHome => _scriptDirMap[id] = ScriptConstants.scriptDirTmp,
|
||||
ScriptConstants.scriptDirHomeWindows => _scriptDirMap[id] = ScriptConstants.scriptDirTmpWindows,
|
||||
_ =>
|
||||
_scriptDirMap[id] = isWindows ? ScriptConstants.scriptDirHomeWindows : ScriptConstants.scriptDirHome,
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the full script path for the given [id]
|
||||
static String getScriptPath(String id, {bool isWindows = false}) {
|
||||
final dir = getScriptDir(id, isWindows: isWindows);
|
||||
final fileName = isWindows ? ScriptConstants.scriptFileWindows : ScriptConstants.scriptFile;
|
||||
final separator = isWindows ? ScriptConstants.windowsPathSeparator : ScriptConstants.unixPathSeparator;
|
||||
return '$dir$separator$fileName';
|
||||
}
|
||||
|
||||
/// Clear cached script directories (useful for testing)
|
||||
static void clearCache() {
|
||||
_scriptDirMap.clear();
|
||||
}
|
||||
}
|
||||
102
lib/data/model/app/scripts/shell_func.dart
Normal file
102
lib/data/model/app/scripts/shell_func.dart
Normal file
@@ -0,0 +1,102 @@
|
||||
import 'package:server_box/data/model/app/scripts/script_builders.dart';
|
||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
|
||||
/// Shell functions available in the ServerBox application
|
||||
enum ShellFunc {
|
||||
status('SbStatus'),
|
||||
process('SbProcess'),
|
||||
shutdown('SbShutdown'),
|
||||
reboot('SbReboot'),
|
||||
suspend('SbSuspend');
|
||||
|
||||
/// The function name used in scripts
|
||||
final String name;
|
||||
|
||||
const ShellFunc(this.name);
|
||||
|
||||
/// Get the command line flag for this function
|
||||
String get flag => switch (this) {
|
||||
ShellFunc.process => 'p',
|
||||
ShellFunc.shutdown => 'sd',
|
||||
ShellFunc.reboot => 'r',
|
||||
ShellFunc.suspend => 'sp',
|
||||
ShellFunc.status => 's',
|
||||
};
|
||||
|
||||
/// Execute this shell function on the specified server
|
||||
String exec(String id, {SystemType? systemType}) {
|
||||
final scriptPath = ShellFuncManager.getScriptPath(id, systemType: systemType);
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
return builder.getExecCommand(scriptPath, this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Manager class for shell function operations
|
||||
class ShellFuncManager {
|
||||
const ShellFuncManager._();
|
||||
|
||||
/// Normalize a directory path to ensure it doesn't end with trailing separators
|
||||
static String _normalizeDir(String dir, bool isWindows) {
|
||||
final separator = isWindows ? ScriptConstants.windowsPathSeparator : ScriptConstants.unixPathSeparator;
|
||||
|
||||
// Remove all trailing separators
|
||||
final pattern = RegExp('${RegExp.escape(separator)}+\$');
|
||||
return dir.replaceAll(pattern, '');
|
||||
}
|
||||
|
||||
/// Get the script directory for the given [id].
|
||||
///
|
||||
/// Checks for custom script directory first, then falls back to default.
|
||||
static String getScriptDir(String id, {SystemType? systemType}) {
|
||||
final customScriptDir = ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
|
||||
if (customScriptDir != null) return _normalizeDir(customScriptDir, isWindows);
|
||||
return ScriptPaths.getScriptDir(id, isWindows: isWindows);
|
||||
}
|
||||
|
||||
/// Switch between tmp and home directories for script storage
|
||||
static void switchScriptDir(String id, {SystemType? systemType}) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
ScriptPaths.switchScriptDir(id, isWindows: isWindows);
|
||||
}
|
||||
|
||||
/// Get the full script path for the given [id]
|
||||
static String getScriptPath(String id, {SystemType? systemType}) {
|
||||
final customScriptDir = ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||
if (customScriptDir != null) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final normalizedDir = _normalizeDir(customScriptDir, isWindows);
|
||||
final fileName = isWindows ? ScriptConstants.scriptFileWindows : ScriptConstants.scriptFile;
|
||||
final separator = isWindows ? ScriptConstants.windowsPathSeparator : ScriptConstants.unixPathSeparator;
|
||||
return '$normalizedDir$separator$fileName';
|
||||
}
|
||||
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
return ScriptPaths.getScriptPath(id, isWindows: isWindows);
|
||||
}
|
||||
|
||||
/// Get the installation shell command for the script
|
||||
static String getInstallShellCmd(String id, {SystemType? systemType}) {
|
||||
final scriptDir = getScriptDir(id, systemType: systemType);
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final normalizedDir = _normalizeDir(scriptDir, isWindows);
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
final separator = isWindows ? ScriptConstants.windowsPathSeparator : ScriptConstants.unixPathSeparator;
|
||||
final scriptPath = '$normalizedDir$separator${builder.scriptFileName}';
|
||||
|
||||
return builder.getInstallCommand(normalizedDir, scriptPath);
|
||||
}
|
||||
|
||||
/// Generate complete script based on system type
|
||||
static String allScript(Map<String, String>? customCmds, {SystemType? systemType}) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
return builder.buildScript(customCmds);
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/script_builders.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
|
||||
enum ShellFunc {
|
||||
status('SbStatus'),
|
||||
//docker,
|
||||
process('SbProcess'),
|
||||
shutdown('SbShutdown'),
|
||||
reboot('SbReboot'),
|
||||
suspend('SbSuspend');
|
||||
|
||||
final String name;
|
||||
|
||||
const ShellFunc(this.name);
|
||||
|
||||
static const seperator = 'SrvBoxSep';
|
||||
|
||||
/// The suffix `\t` is for formatting
|
||||
static const cmdDivider = '\necho $seperator\n\t';
|
||||
|
||||
/// srvboxm -> ServerBox Mobile
|
||||
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
||||
static const scriptFileWindows = 'srvboxm_v${BuildData.script}.ps1';
|
||||
static const scriptDirHome = '~/.config/server_box';
|
||||
static const scriptDirTmp = '/tmp/server_box';
|
||||
static const scriptDirHomeWindows = '%USERPROFILE%/.config/server_box';
|
||||
static const scriptDirTmpWindows = '%TEMP%/server_box';
|
||||
|
||||
static final _scriptDirMap = <String, String>{};
|
||||
|
||||
/// Get the script directory for the given [id].
|
||||
///
|
||||
/// Default is [scriptDirTmp]/[scriptFile], if this path is not accessible,
|
||||
/// it will be changed to [scriptDirHome]/[scriptFile].
|
||||
static String getScriptDir(String id, {SystemType? systemType}) {
|
||||
final customScriptDir = ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||
if (customScriptDir != null) return customScriptDir;
|
||||
|
||||
final defaultTmpDir = systemType == SystemType.windows ? scriptDirTmpWindows : scriptDirTmp;
|
||||
_scriptDirMap[id] ??= defaultTmpDir;
|
||||
return _scriptDirMap[id]!;
|
||||
}
|
||||
|
||||
static void switchScriptDir(String id, {SystemType? systemType}) => switch (_scriptDirMap[id]) {
|
||||
scriptDirTmp => _scriptDirMap[id] = scriptDirHome,
|
||||
scriptDirTmpWindows => _scriptDirMap[id] = scriptDirHomeWindows,
|
||||
scriptDirHome => _scriptDirMap[id] = scriptDirTmp,
|
||||
scriptDirHomeWindows => _scriptDirMap[id] = scriptDirTmpWindows,
|
||||
_ => _scriptDirMap[id] = systemType == SystemType.windows ? scriptDirHomeWindows : scriptDirHome,
|
||||
};
|
||||
|
||||
static String getScriptPath(String id, {SystemType? systemType}) {
|
||||
final dir = getScriptDir(id, systemType: systemType);
|
||||
final fileName = systemType == SystemType.windows ? scriptFileWindows : scriptFile;
|
||||
final separator = systemType == SystemType.windows ? '\\' : '/';
|
||||
return '$dir$separator$fileName';
|
||||
}
|
||||
|
||||
static String getInstallShellCmd(String id, {SystemType? systemType}) {
|
||||
final scriptDir = getScriptDir(id, systemType: systemType);
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
final separator = isWindows ? '\\' : '/';
|
||||
final scriptPath = '$scriptDir$separator${builder.scriptFileName}';
|
||||
|
||||
return builder.getInstallCommand(scriptDir, scriptPath);
|
||||
}
|
||||
|
||||
String get flag => switch (this) {
|
||||
ShellFunc.process => 'p',
|
||||
ShellFunc.shutdown => 'sd',
|
||||
ShellFunc.reboot => 'r',
|
||||
ShellFunc.suspend => 'sp',
|
||||
ShellFunc.status => 's',
|
||||
// ShellFunc.docker=> 'd',
|
||||
};
|
||||
|
||||
String exec(String id, {SystemType? systemType}) {
|
||||
final scriptPath = getScriptPath(id, systemType: systemType);
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
return builder.getExecCommand(scriptPath, this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Generate script based on system type
|
||||
static String allScript(Map<String, String>? customCmds, {SystemType? systemType}) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
return builder.buildScript(customCmds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum StatusCmdType {
|
||||
echo._('echo ${SystemType.linuxSign}'),
|
||||
time._('date +%s'),
|
||||
net._('cat /proc/net/dev'),
|
||||
sys._('cat /etc/*-release | grep ^PRETTY_NAME'),
|
||||
cpu._('cat /proc/stat | grep cpu'),
|
||||
uptime._('uptime'),
|
||||
conn._('cat /proc/net/snmp'),
|
||||
disk._(
|
||||
'lsblk --bytes --json --output '
|
||||
'FSTYPE,PATH,NAME,KNAME,MOUNTPOINT,FSSIZE,FSUSED,FSAVAIL,FSUSE%,UUID',
|
||||
),
|
||||
mem._("cat /proc/meminfo | grep -E 'Mem|Swap'"),
|
||||
tempType._('cat /sys/class/thermal/thermal_zone*/type'),
|
||||
tempVal._('cat /sys/class/thermal/thermal_zone*/temp'),
|
||||
host._('cat /etc/hostname'),
|
||||
diskio._('cat /proc/diskstats'),
|
||||
battery._('for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
||||
nvidia._('nvidia-smi -q -x'),
|
||||
amd._(
|
||||
'if command -v amd-smi >/dev/null 2>&1; then '
|
||||
'amd-smi list --json && amd-smi metric --json; '
|
||||
'elif command -v rocm-smi >/dev/null 2>&1; then '
|
||||
'rocm-smi --json || rocm-smi --showunique --showuse --showtemp '
|
||||
'--showfan --showclocks --showmemuse --showpower; '
|
||||
'elif command -v radeontop >/dev/null 2>&1; then '
|
||||
'timeout 2s radeontop -d - -l 1 | tail -n +2; '
|
||||
'else echo "No AMD GPU monitoring tools found"; fi',
|
||||
),
|
||||
sensors._('sensors'),
|
||||
diskSmart._('for d in \$(lsblk -dn -o KNAME); do smartctl -a -j /dev/\$d; echo; done'),
|
||||
cpuBrand._('cat /proc/cpuinfo | grep "model name"');
|
||||
|
||||
final String cmd;
|
||||
|
||||
const StatusCmdType._(this.cmd);
|
||||
}
|
||||
|
||||
enum BSDStatusCmdType {
|
||||
echo._('echo ${SystemType.bsdSign}'),
|
||||
time._('date +%s'),
|
||||
net._('netstat -ibn'),
|
||||
sys._('uname -or'),
|
||||
cpu._('top -l 1 | grep "CPU usage"'),
|
||||
uptime._('uptime'),
|
||||
// Keep df -k for BSD systems as lsblk is not available on macOS/BSD
|
||||
disk._('df -k'),
|
||||
mem._('top -l 1 | grep PhysMem'),
|
||||
//temp,
|
||||
host._('hostname'),
|
||||
cpuBrand._('sysctl -n machdep.cpu.brand_string');
|
||||
|
||||
final String cmd;
|
||||
|
||||
const BSDStatusCmdType._(this.cmd);
|
||||
}
|
||||
|
||||
extension StatusCmdTypeX on StatusCmdType {
|
||||
String get i18n => switch (this) {
|
||||
StatusCmdType.sys => l10n.system,
|
||||
StatusCmdType.host => l10n.host,
|
||||
StatusCmdType.uptime => l10n.uptime,
|
||||
StatusCmdType.battery => l10n.battery,
|
||||
StatusCmdType.sensors => l10n.sensors,
|
||||
StatusCmdType.disk => l10n.disk,
|
||||
final val => val.name,
|
||||
};
|
||||
}
|
||||
|
||||
enum WindowsStatusCmdType {
|
||||
echo._('echo ${SystemType.windowsSign}'),
|
||||
time._('[DateTimeOffset]::UtcNow.ToUnixTimeSeconds()'),
|
||||
net._(
|
||||
r'Get-Counter -Counter '
|
||||
r'"\\NetworkInterface(*)\\Bytes Received/sec", '
|
||||
r'"\\NetworkInterface(*)\\Bytes Sent/sec" '
|
||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
||||
),
|
||||
sys._('(Get-ComputerInfo).OsName'),
|
||||
cpu._(
|
||||
'Get-WmiObject -Class Win32_Processor | '
|
||||
'Select-Object Name, LoadPercentage | ConvertTo-Json',
|
||||
),
|
||||
uptime._('(Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime'),
|
||||
conn._('(netstat -an | findstr ESTABLISHED | Measure-Object -Line).Count'),
|
||||
disk._(
|
||||
'Get-WmiObject -Class Win32_LogicalDisk | '
|
||||
'Select-Object DeviceID, Size, FreeSpace, FileSystem | ConvertTo-Json',
|
||||
),
|
||||
mem._(
|
||||
'Get-WmiObject -Class Win32_OperatingSystem | '
|
||||
'Select-Object TotalVisibleMemorySize, FreePhysicalMemory | ConvertTo-Json',
|
||||
),
|
||||
temp._(
|
||||
'Get-CimInstance -ClassName MSAcpi_ThermalZoneTemperature '
|
||||
'-Namespace root/wmi -ErrorAction SilentlyContinue | '
|
||||
'Select-Object InstanceName, @{Name=\'Temperature\';'
|
||||
'Expression={[math]::Round((\$_.CurrentTemperature - 2732) / 10, 1)}} | '
|
||||
'ConvertTo-Json',
|
||||
),
|
||||
host._(r'Write-Output $env:COMPUTERNAME'),
|
||||
diskio._(
|
||||
r'Get-Counter -Counter '
|
||||
r'"\\PhysicalDisk(*)\\Disk Read Bytes/sec", '
|
||||
r'"\\PhysicalDisk(*)\\Disk Write Bytes/sec" '
|
||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
||||
),
|
||||
battery._(
|
||||
'Get-WmiObject -Class Win32_Battery | '
|
||||
'Select-Object EstimatedChargeRemaining, BatteryStatus | ConvertTo-Json',
|
||||
),
|
||||
nvidia._(
|
||||
'if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) { '
|
||||
'nvidia-smi -q -x } else { echo "NVIDIA driver not found" }',
|
||||
),
|
||||
amd._(
|
||||
'if (Get-Command amd-smi -ErrorAction SilentlyContinue) { '
|
||||
'amd-smi list --json } else { echo "AMD driver not found" }',
|
||||
),
|
||||
sensors._(
|
||||
'Get-CimInstance -ClassName Win32_TemperatureProbe '
|
||||
'-ErrorAction SilentlyContinue | '
|
||||
'Select-Object Name, CurrentReading | ConvertTo-Json',
|
||||
),
|
||||
diskSmart._(
|
||||
'Get-PhysicalDisk | Get-StorageReliabilityCounter | '
|
||||
'Select-Object DeviceId, Temperature, TemperatureMax, Wear, PowerOnHours | '
|
||||
'ConvertTo-Json',
|
||||
),
|
||||
cpuBrand._('(Get-WmiObject -Class Win32_Processor).Name');
|
||||
|
||||
final String cmd;
|
||||
|
||||
const WindowsStatusCmdType._(this.cmd);
|
||||
}
|
||||
|
||||
extension EnumX on Enum {
|
||||
/// Find out the required segment from [segments]
|
||||
String find(List<String> segments) {
|
||||
return segments[index];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/server/amd.dart';
|
||||
import 'package:server_box/data/model/server/battery.dart';
|
||||
import 'package:server_box/data/model/server/conn.dart';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||
import 'package:server_box/data/model/server/amd.dart';
|
||||
import 'package:server_box/data/model/server/battery.dart';
|
||||
import 'package:server_box/data/model/server/conn.dart';
|
||||
@@ -295,7 +296,7 @@ String? _parseSysVer(String raw) {
|
||||
|
||||
String? _parseHostName(String raw) {
|
||||
if (raw.isEmpty) return null;
|
||||
if (raw.contains(ShellFunc.scriptFile)) return null;
|
||||
if (raw.contains(ScriptConstants.scriptFile)) return null;
|
||||
return raw;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
|
||||
enum SystemType {
|
||||
linux(linuxSign),
|
||||
|
||||
Reference in New Issue
Block a user